Browse Source

fix(content): repair the four dead references and retire their stale docs

The three path-style refs in add-context.md become the bare slugs their registry
entry always carried (context:mvi, context:frontmatter, context:project-intelligence),
and the advanced profile's context:context-system/* gains its missing core/ prefix,
so the wildcard now installs the context-system subtree instead of silently nothing.

The dead refs were pinned as a known bug; per that pin's own contract, fixing them
is a deliberate edit to the pins: reference-resolution now gates the tree at zero
dead refs (with the four repairs asserted individually), profile-completeness drops
its known-dead exemption, and RegistryEmitter's carry-through test probes the
repaired spelling. Also retires stale prose the 9b4e807 refactor left behind: the
build.ts staging docblock gains the third (exhaustive-lists) divergence, the
ClaudeAdapter capability note stops claiming the build path degrades fail-closed,
and 03-adapter-specs §2.3/§2.5 are marked superseded.
darrenhinde 3 weeks ago
parent
commit
d336d7c9f3

+ 1 - 1
.oac/build-manifest.json

@@ -202,7 +202,7 @@
       "root": ".tmp/oac-build/plugins/claude-code/agents"
       "root": ".tmp/oac-build/plugins/claude-code/agents"
     },
     },
     "registry.json": {
     "registry.json": {
-      "sha256": "0c5b7641e7d4cdfb76e65e1da266de1980fa935309cf71c17cc3b349689b8cbd",
+      "sha256": "d149d8a5d30641136919c88775935fff68bb2cadbb02e2ea8dcc1d03b94b1346",
       "target": "registry",
       "target": "registry",
       "root": "registry.json"
       "root": "registry.json"
     }
     }

+ 3 - 3
.opencode/command/add-context.md

@@ -3,9 +3,9 @@ description: Interactive wizard to add project patterns using Project Intelligen
 tags: [context, onboarding, project-intelligence, wizard]
 tags: [context, onboarding, project-intelligence, wizard]
 dependencies:
 dependencies:
   - subagent:context-organizer
   - subagent:context-organizer
-  - context:core/context-system/standards/mvi.md
-  - context:core/context-system/standards/frontmatter.md
-  - context:core/standards/project-intelligence.md
+  - context:mvi
+  - context:frontmatter
+  - context:project-intelligence
 ---
 ---
 
 
 <context>
 <context>

+ 8 - 0
docs/architecture/canonical-refactor/03-adapter-specs.md

@@ -353,6 +353,14 @@ edit→Edit, bash→Bash, glob→Glob, grep→Grep, delegate→Task`.
 
 
 ### 2.3 The collapse rule — ordered rule list → coarse allowlist
 ### 2.3 The collapse rule — ordered rule list → coarse allowlist
 
 
+> **SUPERSEDED (2026-07-17).** §2.3 and §2.5's permission rows describe the derive-and-degrade
+> behaviour as specified before implementation. The shipped adapter replaced it twice over:
+> first refuse-on-loss (`d643b4a`), then no derivation at all (`9b4e807`) — on the build path
+> every agent targeting claude-code must author `oac.overrides.claude-code.tools`, taken
+> verbatim, and its absence is a build error. The collapse rule below survives only on the
+> `fromOAC` import path, where there is no source file to author an override in. Kept as
+> history; do not implement from it.
+
 **Verified constraint (CC docs, `sub-agents.md`):** `tools:` and `disallowedTools:` are
 **Verified constraint (CC docs, `sub-agents.md`):** `tools:` and `disallowedTools:` are
 allowlists/denylists of **tool NAMES only**. A pattern like `Bash(foo:*)` is **not valid** in
 allowlists/denylists of **tool NAMES only**. A pattern like `Bash(foo:*)` is **not valid** in
 agent frontmatter — argument scoping exists *only* in `settings.json`. So **every scope ≠ `*`
 agent frontmatter — argument scoping exists *only* in `settings.json`. So **every scope ≠ `*`

+ 13 - 5
packages/cli/src/commands/build.ts

@@ -41,6 +41,14 @@
  * `oac.overrides` to the prompt — but it is a real design decision, not a default a build
  * `oac.overrides` to the prompt — but it is a real design decision, not a default a build
  * command should pick. So this target keeps staging to {@link CLAUDE_STAGING_ROOT} and
  * command should pick. So this target keeps staging to {@link CLAUDE_STAGING_ROOT} and
  * REPORTING the comparison, and there is deliberately no flag to emit it in place.
  * REPORTING the comparison, and there is deliberately no flag to emit it in place.
+ *
+ * A third difference is mechanical, added 2026-07-17 (9b4e807): the adapter now emits
+ * EXHAUSTIVE tools/disallowedTools pairs — every bound tool lands in one list or the other —
+ * while the shipped files list only some denials (four list none, and none mention WebFetch).
+ * So the staged comparison reports all 7 files as "would change" even where the `tools:` line
+ * matches byte-for-byte. Converging the trees means accepting the exhaustive lists too, not
+ * just resolving the prompt variance. No security impact either way: the shipped `tools:`
+ * whitelist already confines each agent; the exhaustive deny list only makes it explicit.
  */
  */
 
 
 import { type Command } from 'commander'
 import { type Command } from 'commander'
@@ -190,11 +198,11 @@ const reportWarnings = (built: BuildPlan): void => {
 /**
 /**
  * Dead `type:id` references in the tree, reported with their source.
  * Dead `type:id` references in the tree, reported with their source.
  *
  *
- * Read against the registry ON DISK, so this describes the tree as it stands. Reported rather
- * than fatal by default: 4 dead references are known and pinned in
- * `tests/unit/build/reference-resolution.test.ts`, and one of them (`context:context-system/*`)
- * is deliberately carried through until whoever owns profiles decides what it should say.
- * `--strict` makes them fatal.
+ * Read against the registry ON DISK, so this describes the tree as it stands. The tree
+ * carries zero dead references (the four found 2026-07-15 were repaired 2026-07-17, gated in
+ * `tests/unit/build/reference-resolution.test.ts`), so any reported here is new rot. Reported
+ * rather than fatal by default so a broken ref cannot block an unrelated build; `--strict`
+ * makes them fatal, and CI's test suite catches them regardless.
  */
  */
 const reportDeadReferences = async (root: string): Promise<number> => {
 const reportDeadReferences = async (root: string): Promise<number> => {
   const dead = await new ReferenceResolver(root).findDeadReferences()
   const dead = await new ReferenceResolver(root).findDeadReferences()

+ 2 - 1
packages/compatibility-layer/src/adapters/ClaudeAdapter.ts

@@ -232,7 +232,8 @@ export class ClaudeAdapter extends BaseAdapter {
       notes: [
       notes: [
         "Agents emit to plugins/claude-code/agents/<id>.md — one flat markdown file each",
         "Agents emit to plugins/claude-code/agents/<id>.md — one flat markdown file each",
         "Permissions are two flat lists (tools/disallowedTools) — ordered rules, path globs " +
         "Permissions are two flat lists (tools/disallowedTools) — ordered rules, path globs " +
-          "and 'ask' have no equivalent and degrade fail-closed to disallowedTools",
+          "and 'ask' have no equivalent. The build path requires an authored " +
+          "oac.overrides.claude-code.tools list; only imports (fromOAC) degrade fail-closed",
         "Temperature and maxSteps are not expressible in agent frontmatter",
         "Temperature and maxSteps are not expressible in agent frontmatter",
         "Skills provide context injection similar to OAC contexts",
         "Skills provide context injection similar to OAC contexts",
       ],
       ],

+ 7 - 8
packages/compatibility-layer/src/core/RegistryEmitter.ts

@@ -38,15 +38,14 @@
  * five profiles install. See {@link ProfileLoader.drift} for the disagreement itself; the
  * five profiles install. See {@link ProfileLoader.drift} for the disagreement itself; the
  * reconciliation is a content decision, not an emitter one.
  * reconciliation is a content decision, not an emitter one.
  *
  *
- * ─── The dead wildcard stays dead, on purpose ───────────────────────────────────────────
+ * ─── Dead refs are repaired in the SOURCE, never here ───────────────────────────────────
  *
  *
- * `.profiles.advanced.components` contains `context:context-system/*`, which expands to zero
- * registry context paths (`.opencode/context/context-system` does not exist; the real subtree
- * is `.opencode/context/core/context-system/`). It is NOT repaired here. "Carry non-agent data
- * through verbatim" is the phase-1 contract, and rewriting the ref to
- * `context:core/context-system/*` would change what `--profile advanced` installs — a content
- * decision owned by whoever owns profiles. It is instead reported as a `dead-wildcard` by
- * {@link ReferenceResolver.resolve} and pinned as a known dead ref in
+ * "Carry non-agent data through verbatim" is the phase-1 contract: this emitter never edits
+ * profile lists, even broken ones. The live example was `.profiles.advanced.components`
+ * carrying `context:context-system/*`, which expanded to zero matches (the real subtree is
+ * `.opencode/context/core/context-system/`). It was repaired 2026-07-17 as a content edit to
+ * the committed `registry.json` — the authoritative profile source — not by rewriting it
+ * here. Dead refs are reported by {@link ReferenceResolver.resolve} and gated at zero in
  * `tests/unit/build/reference-resolution.test.ts`.
  * `tests/unit/build/reference-resolution.test.ts`.
  *
  *
  * ─── Withdrawing an entry, and why it is manifest-gated ─────────────────────────────────
  * ─── Withdrawing an entry, and why it is manifest-gated ─────────────────────────────────

+ 11 - 24
packages/compatibility-layer/tests/unit/build/profile-completeness.test.ts

@@ -8,8 +8,10 @@
  * fails at the same point, one level deeper.
  * fails at the same point, one level deeper.
  *
  *
  * `validate-registry.sh` checks NEITHER half of this: it never reads `.profiles.*` at all
  * `validate-registry.sh` checks NEITHER half of this: it never reads `.profiles.*` at all
- * (see reference-resolution.test.ts), which is how `context:context-system/*` has sat in the
- * `advanced` profile expanding to zero components while the validator prints 244/244 green.
+ * (see reference-resolution.test.ts), which is how a dead `context:context-system/*` sat in
+ * the `advanced` profile expanding to zero components — until repaired 2026-07-17 to
+ * `context:core/context-system/*` — while the validator printed 244/244 green. These tests
+ * are the only thing that would catch the next one.
  */
  */
 
 
 import { describe, it, expect } from "vitest";
 import { describe, it, expect } from "vitest";
@@ -25,9 +27,6 @@ import { importPendingSymbols, repoPath } from "../../support/pending.js";
 
 
 const PROFILES = ["advanced", "business", "developer", "essential", "full"] as const;
 const PROFILES = ["advanced", "business", "developer", "essential", "full"] as const;
 
 
-/** The one profile ref that does not resolve today. See reference-resolution.test.ts. */
-const KNOWN_DEAD_PROFILE_REF = "context:context-system/*";
-
 function refsFor(profile: string, registry: Registry): Reference[] {
 function refsFor(profile: string, registry: Registry): Reference[] {
   return profileReferences(registry).filter((reference) =>
   return profileReferences(registry).filter((reference) =>
     reference.source.endsWith(`profiles.${profile}.components`)
     reference.source.endsWith(`profiles.${profile}.components`)
@@ -80,21 +79,19 @@ describe("profiles", () => {
     expect(refsFor(profile, loadRegistry()).length).toBeGreaterThan(0);
     expect(refsFor(profile, loadRegistry()).length).toBeGreaterThan(0);
   });
   });
 
 
-  it.each(PROFILES)("%s resolves every component it names, except the known dead ref", (profile) => {
+  it.each(PROFILES)("%s resolves every component it names", (profile) => {
     const registry = loadRegistry();
     const registry = loadRegistry();
-    const dead = resolveAll(refsFor(profile, registry), registry)
-      .filter((resolution) => resolution.status !== "ok")
-      .filter((resolution) => resolution.ref !== KNOWN_DEAD_PROFILE_REF);
+    const dead = resolveAll(refsFor(profile, registry), registry).filter(
+      (resolution) => resolution.status !== "ok"
+    );
 
 
     expect(dead, `${profile} names components that do not exist:\n${format(dead)}`).toEqual([]);
     expect(dead, `${profile} names components that do not exist:\n${format(dead)}`).toEqual([]);
   });
   });
 
 
-  it.each(PROFILES)("%s has an installable transitive closure, except the known dead ref", (profile) => {
+  it.each(PROFILES)("%s has an installable transitive closure", (profile) => {
     const registry = loadRegistry();
     const registry = loadRegistry();
     const { refs } = closure(profile, registry);
     const { refs } = closure(profile, registry);
-    const dead = resolveAll(refs, registry)
-      .filter((resolution) => resolution.status !== "ok")
-      .filter((resolution) => resolution.ref !== KNOWN_DEAD_PROFILE_REF);
+    const dead = resolveAll(refs, registry).filter((resolution) => resolution.status !== "ok");
 
 
     expect(
     expect(
       dead,
       dead,
@@ -102,15 +99,6 @@ describe("profiles", () => {
     ).toEqual([]);
     ).toEqual([]);
   });
   });
 
 
-  it("advanced is the profile carrying the known dead wildcard", () => {
-    const registry = loadRegistry();
-    const carriers = PROFILES.filter((profile) =>
-      refsFor(profile, registry).some((r) => r.ref === KNOWN_DEAD_PROFILE_REF)
-    );
-
-    expect(carriers).toEqual(["advanced"]);
-  });
-
   it("the closure is strictly larger than the named set for at least one profile", () => {
   it("the closure is strictly larger than the named set for at least one profile", () => {
     // Guards the test itself: if dependency-following silently did nothing, every closure
     // Guards the test itself: if dependency-following silently did nothing, every closure
     // would equal its profile's own list and these tests would prove much less than they read.
     // would equal its profile's own list and these tests would prove much less than they read.
@@ -156,8 +144,7 @@ describe("ProfileLoader (shipped)", () => {
     );
     );
 
 
     const { missing } = await new ProfileLoader(repoPath()).resolveClosure(profile);
     const { missing } = await new ProfileLoader(repoPath()).resolveClosure(profile);
-    const unexpected = missing.filter((entry) => entry.ref !== KNOWN_DEAD_PROFILE_REF);
 
 
-    expect(unexpected).toEqual([]);
+    expect(missing).toEqual([]);
   });
   });
 });
 });

+ 75 - 105
packages/compatibility-layer/tests/unit/build/reference-resolution.test.ts

@@ -3,36 +3,35 @@
  *
  *
  * ─── Why this suite is the important one ────────────────────────────────────────────────
  * ─── Why this suite is the important one ────────────────────────────────────────────────
  *
  *
- * `scripts/registry/validate-registry.sh` currently prints:
- *
- *     Total paths checked:    244
- *     Valid paths:            244
- *     Missing paths:          0
- *     Missing dependencies:   0
- *     ✓ All component dependencies are valid!
- *
- * and exits 0. That is a FALSE GREEN, reproduced on disk 2026-07-15. Four references are
- * broken right now and the validator reports none of them, because it is blind in two ways
- * that the tests below pin down structurally:
+ * `scripts/registry/validate-registry.sh` prints "All component dependencies are valid!" and
+ * exits 0 — and did so for months while four references were broken. That was a FALSE GREEN,
+ * reproduced on disk 2026-07-15, because the validator is blind in two ways the tests below
+ * pin down structurally:
  *
  *
  *   1. It reads `registry.json` and nothing else. It never parses the `dependencies:`
  *   1. It reads `registry.json` and nothing else. It never parses the `dependencies:`
  *      frontmatter that actually ships in the component files, so drift between a registry
  *      frontmatter that actually ships in the component files, so drift between a registry
- *      entry and its own file is undetectable. `.opencode/command/add-context.md` is exactly
- *      that: its registry entry lists the four correct bare ids while its frontmatter lists
- *      three path-style ids that resolve to nothing.
+ *      entry and its own file is undetectable. `.opencode/command/add-context.md` was exactly
+ *      that: its registry entry listed the four correct bare ids while its frontmatter listed
+ *      three path-style ids that resolved to nothing.
  *   2. It iterates `.components.*[].dependencies` only. `.profiles.*.components` — 209 refs
  *   2. It iterates `.components.*[].dependencies` only. `.profiles.*.components` — 209 refs
  *      across 5 profiles — is never validated, which is how a wildcard expanding to zero
  *      across 5 profiles — is never validated, which is how a wildcard expanding to zero
- *      matches sits in the `advanced` profile unnoticed.
+ *      matches sat in the `advanced` profile unnoticed.
+ *
+ * ─── The four dead refs, and their repair ───────────────────────────────────────────────
  *
  *
- * ─── The dead-ref count is 4, not 9 ─────────────────────────────────────────────────────
+ * Four dead references were verified on disk 2026-07-15 and repaired 2026-07-17: the three
+ * path-style frontmatter refs in `add-context.md` became the bare slugs its registry entry
+ * always carried, and the `advanced` profile's `context:context-system/*` gained its missing
+ * `core/` prefix. {@link REPAIRED} records them; the tree now carries ZERO dead references
+ * and this suite fails on the first new one.
+ *
+ * ─── The dead-ref count was 4, not 9 ────────────────────────────────────────────────────
  *
  *
  * A "9 known dead context import paths" figure circulated in earlier planning notes. It was
  * A "9 known dead context import paths" figure circulated in earlier planning notes. It was
- * never substantiated: no doc, commit or script produces it, and no scan of this tree
- * reproduces it. The repo's own docs say three
- * (`01-feature-inventory.md:955`, `06-REVIEW.md:309`); a fourth — the profile wildcard —
- * was found while writing this suite and is recorded in `task.json` notes. The oracle in
- * `tests/support/references.ts` finds exactly these 4 and no others. `9` is treated here as
- * folklore and is asserted against, so it cannot quietly return.
+ * never substantiated: no doc, commit or script produces it, and no scan of this tree ever
+ * reproduced it. The repo's own docs said three (`01-feature-inventory.md:955`,
+ * `06-REVIEW.md:309`); the fourth — the profile wildcard — was found while writing this
+ * suite. `9` is folklore.
  */
  */
 
 
 import { describe, it, expect } from "vitest";
 import { describe, it, expect } from "vitest";
@@ -51,120 +50,85 @@ import {
 import { importPendingSymbols, repoPath } from "../../support/pending.js";
 import { importPendingSymbols, repoPath } from "../../support/pending.js";
 
 
 /**
 /**
- * The four dead references in this tree, verified on disk 2026-07-15.
+ * The four references that were dead from 2026-07-15 to 2026-07-17, in their repaired form.
  *
  *
- * This is a snapshot of a KNOWN BUG, not an invariant to preserve. When a subtask repairs
- * one of these refs, the "is still dead" test below turns red — that is correct and
- * intended: fixing a dead ref must be a deliberate edit here, never a silent drift.
+ * Each must now resolve. If one of these goes red, the repair has been reverted or the
+ * component it points at has been removed — either way that is a deliberate content decision
+ * and this list must be edited with it, never weakened around.
  */
  */
-const KNOWN_DEAD: readonly { ref: string; source: string; why: string }[] = [
+const REPAIRED: readonly { ref: string; source: string; was: string }[] = [
   {
   {
-    ref: "context:core/context-system/standards/mvi.md",
+    ref: "context:mvi",
     source: ".opencode/command/add-context.md",
     source: ".opencode/command/add-context.md",
-    why: "path-style-with-.md, but the registry id is the bare slug `mvi`",
+    was: "context:core/context-system/standards/mvi.md (path-style, a dead id)",
   },
   },
   {
   {
-    ref: "context:core/context-system/standards/frontmatter.md",
+    ref: "context:frontmatter",
     source: ".opencode/command/add-context.md",
     source: ".opencode/command/add-context.md",
-    why: "path-style-with-.md, but the registry id is the bare slug `frontmatter`",
+    was: "context:core/context-system/standards/frontmatter.md (path-style, a dead id)",
   },
   },
   {
   {
-    ref: "context:core/standards/project-intelligence.md",
+    ref: "context:project-intelligence",
     source: ".opencode/command/add-context.md",
     source: ".opencode/command/add-context.md",
-    why: "path-style-with-.md, but the registry id is the bare slug `project-intelligence`",
+    was: "context:core/standards/project-intelligence.md (path-style, a dead id)",
   },
   },
   {
   {
-    ref: "context:context-system/*",
+    ref: "context:core/context-system/*",
     source: "registry.json profiles.advanced.components",
     source: "registry.json profiles.advanced.components",
-    why: "expands to 0 matches — the real directory is .opencode/context/core/context-system/",
+    was: "context:context-system/* (missing the core/ prefix, a dead wildcard)",
   },
   },
 ];
 ];
 
 
-/** The count that folklore claims. Asserted against so it cannot creep back in. */
-const UNSUBSTANTIATED_COUNT = 9;
-
 function describeAll(resolutions: readonly Resolution[]): string {
 function describeAll(resolutions: readonly Resolution[]): string {
   return `\n${format(resolutions)}\n`;
   return `\n${format(resolutions)}\n`;
 }
 }
 
 
 // ============================================================================
 // ============================================================================
-// The facts — green today, and they lock the ground truth
+// The facts — the tree is clean, and stays clean
 // ============================================================================
 // ============================================================================
 
 
 describe("dead references in this tree", () => {
 describe("dead references in this tree", () => {
-  it("finds exactly 4 dead references — not the unsubstantiated 9", () => {
+  it("finds none — the four known dead refs were repaired 2026-07-17", () => {
     const dead = deadReferences();
     const dead = deadReferences();
 
 
-    expect(dead.length, `dead references found:${describeAll(dead)}`).toBe(KNOWN_DEAD.length);
-    expect(dead.length).not.toBe(UNSUBSTANTIATED_COUNT);
+    expect(dead, `reference rot has appeared:${describeAll(dead)}`).toEqual([]);
   });
   });
 
 
-  it.each(KNOWN_DEAD)("still reports $ref as dead ($why)", ({ ref, source }) => {
-    const dead = deadReferences();
-    const match = dead.find((d) => d.ref === ref && d.source.includes(source));
+  it.each(REPAIRED)("still resolves $ref (was $was)", ({ ref, source }) => {
+    const [resolution] = resolveAll([{ ref, source }]);
 
 
     expect(
     expect(
-      match,
-      `expected ${ref} (authored in ${source}) to resolve to nothing.\n` +
-        `If a subtask has repaired it, delete its entry from KNOWN_DEAD in this file — ` +
-        `do not weaken the resolver.\nCurrently dead:${describeAll(dead)}`
-    ).toBeDefined();
-  });
-
-  it("reports no dead references beyond the 4 known ones", () => {
-    const unexpected = deadReferences().filter(
-      (dead) => !KNOWN_DEAD.some((known) => known.ref === dead.ref)
-    );
-
-    expect(
-      unexpected,
-      `new reference rot has appeared since 2026-07-15:${describeAll(unexpected)}`
-    ).toEqual([]);
-  });
-
-  it("classifies the three add-context refs as dead ids and the profile ref as a dead wildcard", () => {
-    const dead = deadReferences();
-
-    expect(dead.filter((d) => d.status === "dead-id").length).toBe(3);
-    expect(dead.filter((d) => d.status === "dead-wildcard").length).toBe(1);
+      resolution?.status,
+      `${ref} (authored in ${source}) no longer resolves. If that is deliberate, ` +
+        `update REPAIRED in this file alongside the content change.`
+    ).toBe("ok");
   });
   });
 });
 });
 
 
 // ============================================================================
 // ============================================================================
-// Why the shell validator cannot see them — the mechanism, asserted structurally
+// Why the shell validator could not see them — the mechanism, asserted structurally
 // ============================================================================
 // ============================================================================
 
 
 describe("validate-registry.sh blind spots", () => {
 describe("validate-registry.sh blind spots", () => {
-  it("cannot see frontmatter drift: add-context.md's file and registry entry disagree", () => {
+  it("cannot see frontmatter drift: add-context.md's file and registry entry must agree by hand", () => {
     const registry = loadRegistry();
     const registry = loadRegistry();
     const entry = registry.components.commands?.find((c) => c.id === "add-context");
     const entry = registry.components.commands?.find((c) => c.id === "add-context");
     const onDisk = frontmatterReferences().filter((r) =>
     const onDisk = frontmatterReferences().filter((r) =>
       r.source.endsWith("command/add-context.md")
       r.source.endsWith("command/add-context.md")
     );
     );
 
 
-    // The registry entry is correct — which is precisely why a registry-only validator is happy.
+    // Nothing enforces this agreement but this test: the validator reads only the registry
+    // side, so if the shipped frontmatter drifts again it is the ONLY thing that goes red.
     expect(entry?.dependencies).toEqual([
     expect(entry?.dependencies).toEqual([
       "subagent:context-organizer",
       "subagent:context-organizer",
       "context:mvi",
       "context:mvi",
       "context:frontmatter",
       "context:frontmatter",
       "context:project-intelligence",
       "context:project-intelligence",
     ]);
     ]);
-
-    // The file that actually ships says something else, and three of its refs resolve to nothing.
-    expect(onDisk.map((r) => r.ref)).toEqual([
-      "subagent:context-organizer",
-      "context:core/context-system/standards/mvi.md",
-      "context:core/context-system/standards/frontmatter.md",
-      "context:core/standards/project-intelligence.md",
-    ]);
-
-    expect(
-      resolveAll(onDisk).filter((r) => r.status !== "ok").length,
-      "the shipped frontmatter should contain 3 dead refs the registry entry hides"
-    ).toBe(3);
+    expect(onDisk.map((r) => r.ref)).toEqual([...(entry?.dependencies ?? [])]);
   });
   });
 
 
-  it("never validates profile component lists, where the dead wildcard lives", () => {
+  it("never validates profile component lists, where the repaired wildcard lives", () => {
     const registry = loadRegistry();
     const registry = loadRegistry();
     const profileRefs = profileReferences(registry);
     const profileRefs = profileReferences(registry);
     const componentRefs = registryComponentReferences(registry);
     const componentRefs = registryComponentReferences(registry);
@@ -178,27 +142,32 @@ describe("validate-registry.sh blind spots", () => {
     ]);
     ]);
     expect(profileRefs.length).toBeGreaterThan(200);
     expect(profileRefs.length).toBeGreaterThan(200);
 
 
-    // The dead wildcard is authored ONLY in a profile — no component depends on it — so a
-    // validator that walks components alone cannot reach it by any path.
-    expect(profileRefs.map((r) => r.ref)).toContain("context:context-system/*");
+    // The dead spelling was authored ONLY in a profile — no component depended on it — so a
+    // validator that walks components alone could not reach it by any path. That is how it
+    // survived unnoticed, and why this suite must keep watching profiles. (The repaired
+    // spelling also appears as a component dependency — context-organizer's — which is
+    // precisely why only the profile copy could rot invisibly.)
+    expect(profileRefs.map((r) => r.ref)).toContain("context:core/context-system/*");
+    expect(profileRefs.map((r) => r.ref)).not.toContain("context:context-system/*");
     expect(componentRefs.map((r) => r.ref)).not.toContain("context:context-system/*");
     expect(componentRefs.map((r) => r.ref)).not.toContain("context:context-system/*");
   });
   });
 
 
-  it("wildcard misses are silent: the sibling context:openagents-repo/* does resolve", () => {
+  it("wildcard misses are silent: a wrong prefix reports dead-wildcard, not an error", () => {
     const registry = loadRegistry();
     const registry = loadRegistry();
-    const [openagentsRepo] = resolveAll(
-      [{ ref: "context:openagents-repo/*", source: "control" }],
+    const [repaired] = resolveAll(
+      [{ ref: "context:core/context-system/*", source: "control" }],
       registry
       registry
     );
     );
-    const [contextSystem] = resolveAll(
+    const [misspelt] = resolveAll(
       [{ ref: "context:context-system/*", source: "control" }],
       [{ ref: "context:context-system/*", source: "control" }],
       registry
       registry
     );
     );
 
 
-    // Both are authored in the same profile list, one line apart. Only one expands. That is
-    // what makes the miss invisible to eyeballing as well as to the validator.
-    expect(openagentsRepo?.status).toBe("ok");
-    expect(contextSystem?.status).toBe("dead-wildcard");
+    // The two spellings differ by one path segment. One expands, one silently matches
+    // nothing — which is what made the miss invisible to eyeballing as well as to the
+    // validator, and why the resolver must classify it rather than ignore it.
+    expect(repaired?.status).toBe("ok");
+    expect(misspelt?.status).toBe("dead-wildcard");
   });
   });
 
 
   it("registry context ids are bare slugs, so a path-style ref is genuinely a different namespace", () => {
   it("registry context ids are bare slugs, so a path-style ref is genuinely a different namespace", () => {
@@ -214,7 +183,8 @@ describe("validate-registry.sh blind spots", () => {
       "no registry context id may contain '/' or end in '.md'"
       "no registry context id may contain '/' or end in '.md'"
     ).toEqual([]);
     ).toEqual([]);
 
 
-    // The three add-context targets exist on disk — the files are fine, the refs are not.
+    // The three former add-context targets exist on disk under their bare ids — the files
+    // were always fine; only the refs were wrong.
     for (const id of ["mvi", "frontmatter", "project-intelligence"]) {
     for (const id of ["mvi", "frontmatter", "project-intelligence"]) {
       const component = (registry.components.contexts ?? []).find((c) => c.id === id);
       const component = (registry.components.contexts ?? []).find((c) => c.id === id);
       expect(component, `registry should carry the bare context id "${id}"`).toBeDefined();
       expect(component, `registry should carry the bare context id "${id}"`).toBeDefined();
@@ -236,17 +206,17 @@ describe("reference corpus", () => {
     expect(frontmatterReferences().length).toBeGreaterThan(0);
     expect(frontmatterReferences().length).toBeGreaterThan(0);
   });
   });
 
 
-  it("resolves the overwhelming majority of references, so the 4 stand out", () => {
+  it("resolves every reference in the corpus", () => {
     const resolutions = resolveAll(allReferences());
     const resolutions = resolveAll(allReferences());
-    const ok = resolutions.filter((r) => r.status === "ok");
+    const notOk = resolutions.filter((r) => r.status !== "ok");
 
 
     expect(resolutions.length).toBeGreaterThan(200);
     expect(resolutions.length).toBeGreaterThan(200);
-    expect(ok.length).toBe(resolutions.length - KNOWN_DEAD.length);
+    expect(notOk, `unresolved references:${describeAll(notOk)}`).toEqual([]);
   });
   });
 });
 });
 
 
 // ============================================================================
 // ============================================================================
-// RED — the shipped resolver (subtask 05) must agree with the oracle
+// The shipped resolver (subtask 05) must agree with the oracle
 // ============================================================================
 // ============================================================================
 
 
 const OWED_BY = "subtask 05 (src/core/ReferenceResolver.ts)";
 const OWED_BY = "subtask 05 (src/core/ReferenceResolver.ts)";
@@ -261,7 +231,7 @@ describe("ReferenceResolver (shipped)", () => {
     );
     );
   });
   });
 
 
-  it("finds the same 4 dead references the oracle finds", async () => {
+  it("finds the same zero dead references the oracle finds", async () => {
     const { ReferenceResolver } = await importPendingSymbols<{
     const { ReferenceResolver } = await importPendingSymbols<{
       ReferenceResolver: new (root: string) => {
       ReferenceResolver: new (root: string) => {
         findDeadReferences(): Promise<{ ref: string; source: string }[]>;
         findDeadReferences(): Promise<{ ref: string; source: string }[]>;
@@ -270,13 +240,13 @@ describe("ReferenceResolver (shipped)", () => {
       "src/core/ReferenceResolver.ts",
       "src/core/ReferenceResolver.ts",
       ["ReferenceResolver"],
       ["ReferenceResolver"],
       OWED_BY,
       OWED_BY,
-      "the shipped resolver finds exactly the 4 dead refs the oracle finds — and therefore " +
-        "catches what validate-registry.sh reports as 244/244 green"
+      "the shipped resolver agrees with the oracle that the tree is clean — and therefore " +
+        "catches what validate-registry.sh cannot"
     );
     );
 
 
     const found = await new ReferenceResolver(repoPath()).findDeadReferences();
     const found = await new ReferenceResolver(repoPath()).findDeadReferences();
 
 
-    expect(found.map((f) => f.ref).sort()).toEqual(KNOWN_DEAD.map((k) => k.ref).sort());
+    expect(found).toEqual([]);
   });
   });
 
 
   it("resolves a live reference to the component's real path on disk", async () => {
   it("resolves a live reference to the component's real path on disk", async () => {

+ 7 - 5
packages/compatibility-layer/tests/unit/core/RegistryEmitter.test.ts

@@ -269,14 +269,16 @@ describe("carry-through of everything the canonical tree does not own", () => {
     expect(document.profiles).toEqual(COMMITTED.profiles);
     expect(document.profiles).toEqual(COMMITTED.profiles);
   });
   });
 
 
-  it("leaves the dead advanced-profile wildcard exactly as authored", async () => {
-    // `context:context-system/*` expands to zero registry paths. Repairing it to
-    // `context:core/context-system/*` would change what `--profile advanced` installs, so it
-    // stays a known-dead ref that ReferenceResolver reports rather than an emitter rewrite.
+  it("leaves the advanced-profile wildcard exactly as authored", async () => {
+    // This ref spent 2026-07-15..17 authored as the dead `context:context-system/*`, and the
+    // emitter carried THAT verbatim too — the repair to `core/context-system/*` was a content
+    // edit to the committed registry.json, not an emitter rewrite. Profile lists pass through
+    // untouched either way; that is the contract this test pins.
     const document = await new RegistryEmitter(repoPath()).emit();
     const document = await new RegistryEmitter(repoPath()).emit();
     const advanced = document.profiles as Record<string, { components: string[] }>;
     const advanced = document.profiles as Record<string, { components: string[] }>;
 
 
-    expect(advanced.advanced?.components).toContain("context:context-system/*");
+    expect(advanced.advanced?.components).toContain("context:core/context-system/*");
+    expect(advanced.advanced?.components).not.toContain("context:context-system/*");
   });
   });
 
 
   it("carries agents that have no canonical counterpart", async () => {
   it("carries agents that have no canonical counterpart", async () => {

+ 1 - 1
registry.json

@@ -3565,7 +3565,7 @@
         "context:subagent-template",
         "context:subagent-template",
         "context:openagents-repo/*",
         "context:openagents-repo/*",
         "context:context-system",
         "context:context-system",
-        "context:context-system/*",
+        "context:core/context-system/*",
         "context:registry-dependencies",
         "context:registry-dependencies",
         "context:templates",
         "context:templates",
         "context:frontmatter",
         "context:frontmatter",