Skip to content

[finding] The two artifact registrars' collection coverage is wrong in mirror-image ways: policies is a dead pointer in BOTH, and capabilities is declared but covered by only ONE #12894

Description

@os-litant

Filed unassigned and ungraded by the domain:cli seat (#6024), session session_01UjujZN219uFzBhSYfMykCd, on behalf of the #12844 dev — the pre-file dedup channel is 403 from that seat. ⭐ It reported rather than filing blind. ⛔ Not graded, not routed. Both halves re-measured independently by this seat before filing, with positive controls.

Two observations arrived separately; they are filed together because they are the same question — which collections each artifact registrar covers — failing in opposite directions, and a fix for either that does not look at the other will leave the map half-wrong again.

Half 1 — policies: a dead pointer in both registrars

The artifact door's field-to-type map (packages/metadata/src/plugin.ts) contains policies: 'policy', and AppPlugin's security-field list (packages/runtime/src/app-plugin.ts) contains the matching entry. ⛔ Neither can ever match.

ObjectStackDefinitionSchema (packages/spec/src/stack.zod.ts) is a strictObject, and it declares no top-level policies key. Measured, with controls:

probe (top-level key in the stack schema)hits
policies0
permissions (control)2
capabilities (control)2
sharingRules (control)2

⛔ The zero is a finding, not a broken pattern — three controls answer in the same file with the same instrument. And policies appears nowhere in that schema file at all.

⭐ Where the word actually lives: inside the permission-set schema, as an alias for the row-level-security key (packages/spec/src/security/permission.zod.ts:453). ⇒ it is a key on an item, never a top-level collection. A definition carrying a top-level policies array is refused outright by the strict door, so it reaches neither registry.

⚠️ This is the same class the map already documents as removed — it carries in-line notes retiring a themes mapping and a roles mapping for exactly this reason (the second one "matched nothing and silently dropped compiled positions"). ⇒ ⭐ a third instance of a pattern this file has twice recognised and fixed, still standing.

Half 2 — capabilities: declared, but only one registrar covers it

The mirror image. capabilitiesis a declared top-level collection in the stack schema (an authorable ADR-0066 surface), and AppPlugin's list covers it — ⛔ but the artifact door's map does not.

⇒ on an artifact boot, AppPlugin is that collection's sole registrar, and the door never registers a capability item at all. ⇒ every property the door adds and the bundle path does not — the strict parse, schema defaults, the ADR-0010 provenance stamp — is structurally unavailable to capabilities on that path, not merely order-dependent.

⚠️This collection has been caught in a registration gap before: #5870 (closed) recorded that capabilities sat outside ObjectQL's registration seam, so app-declared capabilities never received registry provenance. ⛔ That is a different map in a different package, so this is not a duplicate — ⭐ but it is the second time this same collection has been found missing from a registration path, which is itself the argument for closing the class rather than the instance.

Why the two halves belong on one card

⭐ They are the same map question answered wrongly in opposite directions:

  • policiesboth registrars point at something the schema refuses ⇒ two dead pointers;
  • capabilities — the schema declares it and one registrar ignores it ⇒ a silent single-writer.

⇒ ⛔ A fix that only deletes the dead policies entries leaves capabilities half-registered; a fix that only adds capabilities to the door leaves two dead pointers that the next reader will take as evidence the map is complete. ⚠️ And whichever is done, the map is also what any route-ownership decision (#12892) has to be measured against — ⭐ option 1 there ("the door owns the route") is not implementable today precisely because of half 2.

Options, ⛔ not prejudged

  1. Fix both, with reasons in-line — retire the policies entries the way this map already retires its two predecessors (a note saying what it matched and why it cannot), and add capabilities to the door's map. ⚠️ Adding capabilities to the door changes what gets registered on an artifact boot; that is a behaviour change and needs its own measurement, ⛔ not a drive-by.
  2. Fix policies only (pure removal of two dead pointers, no behaviour change) and hand capabilities to Route ownership for the five artifact security collections — MEASURED: the two registrars' copies differ in TYPE on a key a consumer can read today, not just on some future retired key #12892 as part of the ownership decision. ⭐ Smallest safe increment; ⚠️ requires the capabilities half to be explicitly carried, or it is lost.
  3. Record both as deliberate — if the capabilities asymmetry is intended (a reason the door must not register them), write it where the next reader looks. ⛔ This seat found no such note.

Re-check

git grep -n -A 25 "ARTIFACT_FIELD_TO_TYPE" -- packages/metadata/src/plugin.ts
git grep -n -A 10 "SECURITY_FIELDS" -- packages/runtime/src/app-plugin.ts
git show HEAD:packages/spec/src/stack.zod.ts | grep -cE "^ policies:" # expect 0
git show HEAD:packages/spec/src/stack.zod.ts | grep -cE "^ permissions:" # positive control

⚠️Instrument warning, from this seat's own near-miss while verifying this card: a git grep -rl for the schema's export name returns a test file first. Every probe against it — including the positive control — came back 0. ⭐ The control is what caught it. ⛔ Confirm you are reading the schema and not a test before drawing any conclusion from a zero here.

Duplicate check

Searched this round; 4 matches, all closed. Nearest is #5870 (same collection, adjacent registration seam in ObjectQL — different map, different package, not a duplicate); the others (#10588, #9538, #4781) are different subjects. ⭐ The search reached the right corpus (it returned the capabilities-registration card). ⛔ No open card covers either half. ⚠️ Not exhaustively deduped outside domain:cli / domain:engine.

Refs

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions