Skip to content

Gate the metadata-lifecycle overlay-whitelist table against DEFAULT_METADATA_TYPE_REGISTRY — both directions, and by AST not regex #11752

Description

@os-steve

Follow-up to #11664 / #11750. Filed by the devx PM seat; the #11750 dev raised it and was instructed not to build it in a docs-only PR.

What

Add a gate that asserts the "Overlay whitelist (shared-DB tenancy invariant)" table in content/docs/concepts/metadata-lifecycle.mdx against DEFAULT_METADATA_TYPE_REGISTRY (packages/spec/src/kernel/metadata-plugin.zod.ts).

Why this one is worth a gate

The section declares the registry to be the single machine-readable source — "The whitelist lives in one place: MetadataTypeRegistryEntry.allowOrgOverride" — and the table drifted from it anyway, on four types, undetected long enough to be caught by a human fact-checking a promo video (#11664, video-studio #4):

TypeTable saidRegistry says
flow❌ (rolled back #6283)
permission❌ (rolled back #6483)
position❌ (same rollback)
translationabsent

Nothing mechanical was watching a comparison that is entirely mechanical. #11750 corrected the table; it did not stop the next drift.

Two constraints, both established by measurement on #11750 — treat as binding

1. The gate MUST check both directions.translation was a false negative by omission — a type the registry marks allowOrgOverride: true that the table simply did not list. A table→registry gate ("every row I see agrees with the registry") passes on a table missing a whole row. Three of the four defects were findable from the table side; the fourth was findable only from the registry side. A one-directional gate would have shipped 3/4 and reported clean.

2. The gate MUST read the registry by AST, not regex. This is not a style preference — a same-line regex silently under-reads this exact file. Measured on origin/main @ 2a6122bd9:

  • grep -cE "^ \{ type: '" over the registry literal → 26
  • AST parse → 27

The missed entry is datasource, whose object literal opens { on its own line so type: and allowOrgOverride: land on separate lines. A regex-built gate would have been born with one type invisible to it — and invisible in the direction that matters, since the missing entry would never be reported as an omission from the table.

Two more shapes a regex gets wrong here, both present in the file today:

  • grep -c 'allowOrgOverride: true' = 6, but only 5 are entries; the sixth is prose inside a code comment at :789.
  • 33 lines match allowOrgOverride: false inside the registry region; only 22 are entries. The other 11 are comments discussing the flags.

The #11750 dev already wrote a throwaway AST comparator end-to-end and demonstrated it firing in both directions (exit 1 / VERDICT: 4 DIVERGENCE(S) before, exit 0 / VERDICT: CONSISTENT after), so the hard part is done and is known to work — this card is about making it a permanent, wired gate rather than re-deriving it.

Non-vacuity requirement

Standard for this repo, and there is a ready-made positive control: the pre-#11750 revision of metadata-lifecycle.mdx is a file the gate must reject with 4 divergences. Prove the gate red on it and green on main after #11750 lands. Do not accept a green reading on a gate that has not been shown able to go red.

Scope notes

  • The table is hand-maintained — verified on docs: correct the overlay-whitelist table against the registry (flow, permission, position, translation) #11750: no generator writes the page, no DO NOT EDIT marker, no existing gate compares it to the registry. So the gate is the new thing, not a fix to a generator.
  • Do not flip any registry flag to make things agree. scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json states that a flag flip here is an ADR-0005 whitelist change requiring the ADR revised, not a file edit. The registry is the authority; the table is what gets corrected.
  • Sibling pages already carry the same facts correctly (content/docs/permissions/authorization.mdx:236, content/docs/automation/jobs.mdx:42). Whether the gate should also cover those is an open question, not a requirement — decide it on evidence, and say which way you went and why.
  • Wire it into the derived-gate family the way its neighbours are wired, and confirm the wiring by a livenode scripts/pm/dispatch-gates.mjs derivation rather than by assuming the glob covers it.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions