Skip to content

Resolve a code ref's module without a virtual network - #6073

Draft
backspace wants to merge 2 commits into
mainfrom
cs-12890-resolve-code-refs-without-a-virtualnetwork-carrying-loader
Draft

Resolve a code ref's module without a virtual network#6073
backspace wants to merge 2 commits into
mainfrom
cs-12890-resolve-code-refs-without-a-virtualnetwork-carrying-loader

Conversation

@backspace

Copy link
Copy Markdown
Contributor

loadCardDef pulled the virtual network off the loader and refused to run without one:

let virtualNetwork = loader.getVirtualNetwork();
if (!virtualNetwork) {
  throw new Error(`loadCardDef requires a Loader configured with a VirtualNetwork`);
}

It needed far less than it asked for. The only thing it did with the network was hand it to resolveModuleHref, which used two capabilities: isRegisteredPrefix and resolveURL.

Resolving a code ref is path math

A code ref's module is canonical RRI. @scope/name/... and anything carrying a URL scheme are already absolute; a relative reference joins against relativeTo. That is exactly resolveRRIReference, which consults no mappings — and codeRefWithAbsoluteIdentifier already took that path when handed no network, so the direction was half-built.

No call site changes. All 67 loadCardDef callers pass a loader rather than a network, so the diff is confined to code-ref.ts.

The bare-specifier error survives, without a registry

Dropping isRegisteredPrefix risked losing the clear failure for a bare specifier, which would otherwise join against the consumer and fetch a URL nobody wrote. It does not need a registry: a bare specifier is recognisable by shape — neither URL-like (relative, rooted, or schemed) nor scoped.

if (!isUrlLike(module) && !module.startsWith('@')) {
  throw new Error(`Cannot resolve bare package specifier "${module}" — …`);
}

What this deliberately stops rejecting is a scoped reference whose prefix this process has not registered. Such a reference is absolute and cross-realm by construction — the rule the read path already applies via isScopedReference — and an unresolvable one now fails at fetch, naming the module the caller actually wrote rather than failing a prefix check.

Scope

codeRefWithAbsoluteIdentifier keeps its optional network parameter, now accepted and ignored, so its ~50 call sites stay untouched. Removing it was measured: 16 call sites drop the argument cleanly, but that leaves the parameter unused in three further functions and cascades into their signatures too. That belongs with the sweep that takes the network off the Loader's remaining consumers, not here.

Tests

resolve-module-href-test.ts covers what the resolution rules now are: a scoped reference passed through, a scoped reference with no registered prefix also passed through (the deliberate change), a relative reference joined both down and up a level, an absolute URL untouched, a bare specifier rejected, and an absolute reference resolving with no relativeTo at all.

`loadCardDef` pulled the network off the loader and refused to run without one,
but used it for a single call: `resolveModuleHref`, which wanted only
`isRegisteredPrefix` and `resolveURL`. A code ref's module is canonical RRI, so
resolving it is path math — `@scope/name/...` and anything carrying a URL scheme
are already absolute, and a relative reference joins against `relativeTo`. That
is what `resolveRRIReference` does, with no mappings involved, and
`codeRefWithAbsoluteIdentifier` already took that path when handed no network.

No call site changes. All 67 `loadCardDef` callers pass a loader rather than a
network, so this is confined to code-ref.ts.

The bare-specifier rejection survives without a registry, because a bare
specifier is recognisable by shape: neither URL-like — relative, rooted, or
schemed — nor scoped. What it stops rejecting is a scoped reference whose prefix
this process has not registered, deliberately: such a reference is absolute and
cross-realm by construction, which is the rule the read path already applies,
and an unresolvable one now fails at fetch naming the module the caller wrote
rather than at a prefix check.

`codeRefWithAbsoluteIdentifier` keeps its optional network parameter, now
accepted and ignored, so its ~50 call sites stay untouched. Removing it cascades
into three more signatures and belongs with the sweep that takes the network off
the Loader's remaining consumers.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T14:43:11.229035Z 7f4fcf6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f4fcf6135

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/runtime-common/code-ref.ts
Comment thread packages/runtime-common/code-ref.ts Outdated
The bare-specifier rejection this added was stricter than the contract around
it and rejected identifiers the resolver would have handled.

`isRelativePath` draws the line: `@scope/name/...` is absolute, anything a URL
parser accepts is absolute, and everything else is a relative reference.
`resolveRRIReference` draws it identically. So `data:` and `blob:` modules were
being rejected before the resolver could preserve them, and a same-realm module
written without `./` — `garden-design`, as the checked-in Garden tab refs have
it — was rejected too, after which `codeRefWithAbsoluteIdentifier`'s catch
returned the ref unresolved and callers went on to look up the wrong
identifier. Silent, and in a path that had no guard before this branch.

The premise behind that guard was wrong. A bare specifier cannot be recognised
by shape: `garden-design` naming a module in this realm and `date-fns` naming
one of the ~30 packages the loader shims are indistinguishable, and only the
prefix registry ever told them apart. Losing the registry loses that, so the
resolution rule is now simply the contract's.

Relative wins for a bare name. A code ref names a card definition, so the
checked-in refs of that shape are same-realm modules while the shimmed packages
are imported by module source rather than referenced as code refs. A scoped
specifier that matches no realm prefix — `@cardstack/boxel-host/commands/foo`,
the case the removed comment named — still passes through unchanged, which is
what the loader's import map needs. What it costs is a bare shimmed specifier
used as a code ref, which would resolve into the realm and fail at fetch.

Tests follow the contract rather than the guard: a bare name joins against the
consumer, and non-http schemes pass through.
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Host Test Results

    1 files      1 suites   2h 37m 59s ⏱️
4 733 tests 4 718 ✅ 14 💤 0 ❌ 1 🔥
4 748 runs  4 732 ✅ 14 💤 1 ❌ 1 🔥

Results for commit 0a86a2e.

For more details on these errors, see this check.

Realm Server Test Results

    1 files    209 suites   1h 19m 34s ⏱️
2 728 tests 2 728 ✅ 0 💤 0 ❌
2 767 runs  2 767 ✅ 0 💤 0 ❌

Results for commit 0a86a2e.

@backspace
backspace marked this pull request as draft September 10, 2026 20:39
@backspace

Copy link
Copy Markdown
Contributor Author

[Claude Code 🤖]

Parked as a draft. Both review findings are fixed and resolved; what remains is the issue's scope being wrong rather than the code.

Integration | Store: can use registered prefix ids across store APIs fails here, and it is not a test to patch. resolveModuleHref used to end with virtualNetwork.resolveURL(module, relativeTo).href — always URL form. resolveRRIReference passes @scope/name/... through unchanged, so this branch changes the function's output spelling from URL to RRI. That test registers @test-prefix/, adds a reference by prefix id, and expects the card to load and fold onto the same instance as the URL form.

The issue's premise — that loadCardDef "needs far less than it asks for" — is only half right. The consuming half holds: no network needed, and none of the 67 call sites change. The producing half does not, because callers depend on the form of what comes back.

That is exactly what CS-12758 predicts for its real-URL-keying endpoint, quoting it: "Changes the output spelling of getConsumedModules / identify(), so the callers above fold for themselves." This branch adopted those semantics for one function without the decision having been made, and a consumer caught it. Patching the store would commit us to that endpoint by stealth, which is the thing that issue says to settle first.

So this waits on the endpoint decision. Under narrow injected functions it becomes small, but needs a port loadCardDef can reach — CS-12891's subject. Under real-URL keying it becomes part of CS-12892's sweep.

Kept from the attempt, and recorded on the issue: the bare-specifier rejection cannot be rebuilt without the registry. isRelativePath treats any non-scoped, non-URL identifier as relative, so garden-design naming a module in this realm and date-fns naming one of the ~30 packages shimExternals registers are the same shape. Only isRegisteredPrefix distinguished them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant