Repository navigation
Gate the metadata-lifecycle overlay-whitelist table against DEFAULT_METADATA_TYPE_REGISTRY — both directions, and by AST not regex #11752
Copy link
Copy link
Closed
Description
Activity
Claim: devx lane PM seat, session
e2eac1a7-8000-5c95-9749-38aec2ace6fc, branchclaude/issue-11752-overlay-whitelist-gate.Dispatching now. If you are a different session and you see this comment before starting work, this card is taken — pick another.
Generated by Claude Code
{ "issue": 11752, "status": "done", "branch": "claude/issue-11752-overlay-whitelist-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/11759", "premise_still_valid": true, "summary": "Added scripts/check-overlay-whitelist-table.mjs (+ package.json check:overlay-whitelist-table, + a lint.yml step in the docs-guard block): the overlay-whitelist table in content/docs/concepts/metadata-lifecycle.mdx is held to DEFAULT_METADATA_TYPE_REGISTRY on two legs, both counts printed even at zero, registry read by TypeScript AST via scripts/ts-parse.mjs. ORDERING: your assumption 1 is right about order and wrong about timing — PR #11750 is OPEN, not merged; main @ 2a6122bd9 predates it, so the gate is red on today's main BY CONSTRUCTION. That red is the correct answer and I used it as the live positive control instead of a synthetic one. To avoid landing a permanently-red gate the branch merges #11750's single commit (180a416ac) as a declared dependency; #11750 remains open and authoritative, and the merge collapses to nothing if it lands first. Assumptions 2 and 3 held: the registry region has no spread/computed/helper shape (clean AST walk), and multi-type cells parse correctly (controlled). Assumption 4 (siblings) I decided your way, on measurement rather than lean — see open_questions for the one call that is yours, not mine.", "tests": "All runs at final commit 1f6d4507e unless stated; exit codes captured before any pipe.\n\nNON-VACUITY, LIVE, BOTH DIRECTIONS (same gate binary, two real trees):\n RED @ 2a6122bd9 (main, pre-#11750): exit 1, 'VERDICT: 4 DIVERGENCE(S)'\n LEG 1 table->registry: 3 — [mismatch] flow (mdx:111 vs zod.ts:827), permission (mdx:113 vs zod.ts:1027), position (mdx:113 vs zod.ts:1028)\n LEG 2 registry->table: 1 — [missing-row] translation (zod.ts:981, allowOrgOverride: true, named nowhere in the table)\n GREEN @ 1f6d4507e: exit 0 — 'leg 1 (table -> registry) 0 divergence(s) over 13 type(s) named in 8 row(s); leg 2 (registry -> table) 0 divergence(s) over 5 allowOrgOverride: true type(s) [view, dashboard, report, translation, email_template] out of 27 declared.'\n Every zero above is stated alongside that red run, which is the same probe on the same code.\n\nSELF-TEST (embeds the positive control permanently): exit 0 — 'positive control reproduces the 4 known divergences (leg 1: flow, permission, position; leg 2: translation), the corrected table is green on both legs, and 21 structural/parser cases are refused.' The battery caught two real defects in my own first draft (a wrong fixture count; a control that fired duplicate+mismatch instead of mismatch) — both fixed, both anchors confirmed on disk by grep before re-running.\n\nAST vs REGEX, card's numbers reproduced on 2a6122bd9: grep -cE \"^ \\{ type: '\" = 26, AST (--list) = 27; missed entry is datasource @ registry:870. MEASURED FURTHER (scratch ablation, swapping only the registry-reading leg, no tree mutation so no restore leg): regex reader leg1=4 leg2=1 total=5 including a SPURIOUS 'leg1 [unknown-type] datasource'; AST reader leg1=3 leg2=1 total=4. See open_questions/summary — this refines the card.\n\nWIRING, live derivation (Zone 3 item 4), 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' at 1f6d4507e, stderr confirms 'objectstack-ai/objectstack at commit 1f6d4507e': gate matched on BOTH sources, not merely the scripts/** glob —\n 'pnpm check:overlay-whitelist-table [lint.yml] matched via content/docs/concepts/metadata-lifecycle.mdx => gate source 'content/docs/concepts/metadata-lifecycle.mdx'; scripts/check-overlay-whitelist-table.mjs => gate script 'scripts/check-overlay-whitelist-table.mjs''\n So it is reachable whether the table drifts or the gate does. 33 families matched for this diff.\n\nGATE UNION at 1f6d4507e (the final commit; clean tree, nothing committed after) — 24/25 green:\n overlay-whitelist-table 0, entry-guard 0, parse-guard 0, nul-bytes 0, pnpm-filter-targets 0,\n required-contexts 0, aggregator-roster 0, step-collectors 0, workflow-status-fns 0,\n type-check-coverage 0, shard-attestation 0, node-version 0, agent-test-spelling 0,\n cross-package-inputs 0, doc-anchors 0, doc-authoring 0, doc-frontmatter 0, docs-audit-scope 0,\n docs-redirects 0, role-word 0, section-landing-index 0, published-readme-links 0,\n react-page-adapter 0, ci-failure-selftest 0\n type-check-debt = 1, NOT this change: it refuses to measure without a built dependency closure ('56 workspace dependenc(ies) ... have no built type entry point on disk'), the fresh-worktree trap. lint.yml:3418-3422 satisfies that precondition one step earlier (turbo run build --filter './packages/*' ... immediately before 'pnpm check:type-check-debt'). Its non-re-measure leg printed green in the same run ('check-type-check-coverage: OK — 65/78 workspace packages type-checked'). This diff carries zero TypeScript into any tsc program: the four changed paths are a workflow, the private root package.json, one .mjs and one .mdx, and root tsconfig.json sets no allowJs.\n\nREPO-WIDE LINT: 'pnpm lint' (eslint . --no-inline-config) run IN FULL at 1f6d4507e, LINT_EXIT=0, 53s under the shared verify lock. No narrowing claimed, so no narrowing evidence owed.\n\nAll heavy runs went through scripts/pm/os-verify-lock.sh (one wait of 232s behind two other agents' builds; verdicts read from its VERDICT lines).", "open_questions": [ { "question": "The branch currently carries #11750's commit so the gate can be green. That is a sequencing call you own, not a blocker: #11750 is open, non-draft, already skip-changeset labelled, and will very likely land first.", "options": [ "A — leave the carry as is. If #11750 lands first the merge collapses to nothing and my PR is a pure gate diff. If mine lands first, #11750 becomes an empty PR someone must close.", "B — drop the merge commit and hold #11759 until #11750 lands, then merge main. Keeps the two PRs disjoint; costs a rebase round and leaves #11759 red until then.", "C — land #11759 red and rely on #11750 landing shortly after. Rejected: a red required gate wedges every open PR and the merge queue." ], "recommendation": "A. It is the only option that makes #11759 self-consistently green at every moment, the merge preserves #11750's authorship, and the collapse case is the likely one. C is unsafe and I did not consider it further." }, { "question": "Sibling-page coverage (your assumption 4) — I went your way, but on measurement, and I want the number on the record rather than the lean.", "options": [ "A — leave siblings out (what I did).", "B — extend the gate to prose assertions on authorization.mdx / jobs.mdx / agents.mdx / capabilities.mdx / email-template.mdx." ], "recommendation": "A. 'grep -rn allowOrgOverride content/docs/' hits 22 lines across 13 files; only ~5 are genuine '<type> is <bool>' assertions. The rest are the schema-field reference row, a code sample using an invented type, and sentences ABOUT the flag rather than about any type — metadata-lifecycle.mdx:121 itself is one. Covering them mechanically means a co-occurrence regex over English plus a per-site allowlist of ~17 exceptions to catch ~5 assertions: a worse gate than none, and the kind whose baseline gets bulk-updated to green. jobs.mdx:42 IS a table but a job-vs-flow one whose flag mention sits inside an English cell; no sibling page has a machine-shaped type->flag table. The count and the reasoning are in the script header so the next person re-deciding starts from the number." } ], "out_of_scope_findings": [ "filed as #11763: metadata-lifecycle.mdx:119 (the sentence #11750 added under the table) carries two hand-kept registry-derived numbers — 'of the 27 types' and 'Those five' — that nothing checks. Both correct today (--list confirms 27 types, 5 overridable), so this is a finding, not a defect: label 'finding', no pm:queue, unassigned. Not folded into #11759 because the in-place-fix exemption fails condition 3 — that sentence was authored by PR #11750, still open, so another agent's in-flight work holds a live claim on the line, and asserting the number would couple my gate to that PR's final wording. Registering a 28th type would leave the page asserting '27' with every gate green." ] }
Generated by Claude Code
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.mdxagainstDEFAULT_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):flowpermissionpositiontranslationNothing 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.
translationwas a false negative by omission — a type the registry marksallowOrgOverride: truethat 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 → 26The missed entry is
datasource, whose object literal opens{on its own line sotype:andallowOrgOverride: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.allowOrgOverride: falseinside 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: CONSISTENTafter), 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.mdxis a file the gate must reject with 4 divergences. Prove the gate red on it and green onmainafter #11750 lands. Do not accept a green reading on a gate that has not been shown able to go red.Scope notes
DO NOT EDITmarker, no existing gate compares it to the registry. So the gate is the new thing, not a fix to a generator.scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.jsonstates 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.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.node scripts/pm/dispatch-gates.mjsderivation rather than by assuming the glob covers it.