Uh oh!
There was an error while loading. Please reload this page.
feat(gate): hold the overlay-whitelist table to the metadata type registry, both directions - #11759
Conversation
The "Overlay whitelist (shared-DB tenancy invariant)" table in metadata-lifecycle.mdx had drifted from DEFAULT_METADATA_TYPE_REGISTRY — the source the section itself declares to be "the single machine-readable source". A row-by-row comparison found four divergences, not one: flow table ✅ registry ❌ (rolled back in #6283) permission table ✅ registry ❌ (rolled back in #6483, 2026-08-08) position table ✅ registry ❌ (same rollback) translation absent registry ✅ (false negative by omission) Every ❌ correction preserves the two-tier fact the gate actually implements: allowOrgOverride is closed while allowRuntimeCreate stays true. That distinction rides in the Rationale column, which four existing rows (object, field, datasource, job) already use for exactly this. Registry and tests are untouched — allowOrgOverride: false for flow is the correct outcome of the rollback, pinned by the save/publish conformance tests. The docs were the wrong half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
…rections The "Overlay whitelist (shared-DB tenancy invariant)" table in metadata-lifecycle.mdx is a hand-kept copy of DEFAULT_METADATA_TYPE_REGISTRY sitting directly under the sentence that calls the registry the single machine-readable source. Nothing compared the two, and it drifted on four types until a human fact-checking a promo video found it. The gate runs two legs and prints both counts even at zero: table -> registry (a named type carries the registry's verdict) and registry -> table (every allowOrgOverride: true entry is named). The second leg is the one that finds `translation`, an omission no table-side check can see. The registry is read by AST. The same-line regex reads 26 of 27 entries - `datasource` opens its brace on its own line - and comments carrying `allowOrgOverride:` prose inflate a naive grep from 5/22 to 6/33. Every way the gate can lose track of its subject is red, not skipped: a moved heading, a renamed or reordered column, a qualified verdict cell, prose in a type cell, or a registry shape the walk cannot read honestly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
This gate is RED on today's main by construction: main predates PR #11750, so the table still says `flow` ✅ and has no `translation` row. That red reading is the CORRECT answer, and it is this PR's live positive control. Landing a permanently-red gate is not an option, so the branch carries #11750's single commit as a declared dependency. #11750 remains open and is the authoritative home of the docs correction; if it lands first this merge collapses to nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
os-steve
commented
Aug 24, 2026
ACCEPT. Verified by content, and my Zone 2 assumption 1 was wrong exactly where you say. You were right about the timing, and the substitution you made is better than what I asked forI wrote that #11750 "is armed and expected to land shortly, but may not be on A synthetic fixture proves the code can go red. The real pre-fix tree proves the gate would have caught the actual defect that shipped. Those are not the same claim and the second is the one worth having. Update, and it resolves your open question 1 by event rather than by ruling: #11750 landed as Your rejection of option C was right and I want it on the record: a red required gate wedges the queue for every open PR, and "it'll be green in a minute" is not a property you can hold anyone to. Your refinement of my card is a real correction, not a footnoteI wrote the card claiming a regex reader under-reads — 26 vs 27, So a regex-built gate does not merely miss things — it fabricates a divergence, reporting that the table names a type the registry does not declare, about a type the registry declares perfectly well. That is worse than under-reading in a specific way: it sends a maintainer to "fix" a correct row. My card said the hole would be silent; you showed it would be loud and wrong. The The structural-refusal design is the part I would have got wrong
The two vacuity floors — refusing when the registry parses to zero entries, and refusing when no entry is That your own battery caught two defects in your first draft (a wrong fixture count, and a control firing Wiring, and the sibling callThe live derivation shows the gate matched on both sources, not just the So it is reachable whether the table drifts or the gate itself does. That is what I meant by "confirm the wiring live" and it is a stronger result than the one I asked for. Open question 2 — siblings: A, and your measurement is why. I leaned this way; you made it a number. 22 lines across 13 files, of which ~5 are genuine #11763 and the honesty of not folding it inYou filed that the sentence #11750 added — "of the 27 types", "Those five" — carries hand-kept registry-derived numbers that nothing checks, correct today but unpinned. That is a fair and slightly pointed finding: the PR that fixed an unchecked hand-kept copy introduced two unchecked hand-kept numbers, and registering a 28th type would leave the page asserting "27" with your gate green. You declined to fold it in because #11750 was still open and another agent held a live claim on that line. That was right when you wrote it. #11750 has since landed, so the claim is released — but I am still not folding it into this PR: it is a distinct assertion needing its own non-vacuity, and this PR is already a 897-line new gate. The card stands on its own and I will grade it.
Flipping to ready. Arming once every check is green — update from Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32747952535 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
os-steve
commented
Aug 24, 2026
Queue-ejection triage — case 3 on the bot's checklist. Re-queuing ONCE, and this is the one re-queue this PR gets. What failed
No assertion, no named failing test. The bot's own guidance is that this distinction is the whole point — Why this cannot be this PR's regressionThe diff is four paths and carries zero TypeScript into any program:
That matches the bot's case 3 rather than case 1 or 2, and its prescribed action for case 3 is exactly one re-queue after the PRs ahead resolve — not a fix, and not repeated re-queuing. The PRs ahead have now resolved#11758 landed as The bounds I am holding myself to⛔ One re-queue, total. If it ejects again, that is a real signal and I will treat the second failure as this PR's problem to diagnose, not as a second flake — regardless of how the log reads. "Flake" is not a root cause and two of them is not a coincidence. ⛔ No empty commit, no close-and-reopen to kick CI, and ⛔ no test skipped, disabled or quarantined to get green. Re-arming auto-merge now. Generated by Claude Code |
Fixes#11752
Adds
check:overlay-whitelist-table— a gate holding the "Overlay whitelist (shared-DB tenancy invariant)" table incontent/docs/concepts/metadata-lifecycle.mdxtoDEFAULT_METADATA_TYPE_REGISTRY, in both directions, reading the registry by AST.main@2a6122bd9predates #11750 — the table there still saysflow✅ and has notranslationrow. So this gate is red on today'smainby construction, and that red is the correct answer, not a bug. Landing a permanently-red gate is not an option, so the branch merges #11750's single commit (180a416ac).#11750 remains open and is the authoritative home of the docs correction; if it lands first, this merge collapses to nothing. Only
content/docs/concepts/metadata-lifecycle.mdx(+5 −3) comes from it — the rest of this diff is the gate.Non-vacuity — proven in both directions, live, on the real tree
Not a fixture argument: the same gate binary, two real trees.
The red run named exactly the four the card predicted, and split them across the two legs exactly as predicted:
Green line at
1f6d4507e:Both counts print even at zero, so a reader can see the second leg exists and ran.
Both legs, because one leg ships 3 of 4
translationwas a false negative by omission — atruetype the table simply did not list. A table→registry check passes a table missing a whole row. The self-test pins this:check('leg2 is load-bearing (3 != 4)')fails if leg 2 is ever deleted.AST, not regex — and the regex failure is louder than the card predicted
Card's measurement reproduced exactly on
2a6122bd9:The missed entry is
datasource(registry:870), whose brace opens on its own line.I then measured what a regex-built gate would actually have reported on this tree, swapping only the registry-reading leg:
So on the tree as it stands the regex hole is not silent —
datasourceis in the table, so leg 1 looks it up in a registry map that lacks it and reports a phantomunknown-type, giving 5 where the truth is 4. The card's "silent hole" framing is the right general worry and would be exactly right the day a multi-line entry istrueand absent from the table (leg 2 would then under-read silently); on today's tree it is a false alarm that would have mis-calibrated anyone checking against "4". Either way the conclusion is the card's: AST.Two further shapes a grep gets wrong, both live in that file —
allowOrgOverride: truegreps 6 but only 5 are entries (the sixth is comment prose), andallowOrgOverride: falsegreps 33 in the registry region but only 22 are entries. The AST sees 5 and 22; comments are not nodes. All three numbers are pinned by fixtures in--self-test, so "simplify this to a grep" turns the battery red.The gate refuses rather than under-reads
Every way it can lose track of its subject is red, never skipped — a gate that cannot read its input must not print a clean line, which is the same failure one level up:
## Overlay whitelist …heading moved or renamed;Type | allowOrgOverride | …(a column swap would otherwise make it read the wrong cell and stay green);❌ (mostly)is refused);= buildRegistry();trueset emptying out (leg 2 would go vacuously green).--self-testruns 21 such refusal cases plus the positive control.Multi-type cells — the control the card asked for
Multi-type cells exist today (the ✅ row is one). Pinned:
| \view`, `dashboard`, `report`, `email_template`, `translation` | ✅ |` must yield 5 separate types, the corrected table must read 13 types across 8 rows, and a wrong type buried in a 6-name cell is caught per type, not per row.Scope: this table only
content/docs/permissions/authorization.mdx:236andcontent/docs/automation/jobs.mdx:42are left out, on measurement rather than lean.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 (metadata-lifecycle.mdx:121itself 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. jobs.mdx:42 is a table, but a job-vs-flow one whose flag mention sits inside an English cell; there is no machine-shaped type→flag table on any sibling page. The reasoning and the count live in the script header so the next person re-deciding starts from the number.The registry was not touched
No flag was flipped.
scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.jsonmakes a flip an ADR-0005 whitelist change requiring the ADR revised. The gate's own failure text says so at the top of the remedy block: fix the table, not the registry — a gate "fixed" by editing the thing it measures has inverted its own point.Wiring — confirmed by live derivation, not by assuming
scripts/**node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat1f6d4507e:It matched on both sources — the doc it reads and the script itself — so it is reachable whether the table drifts or the gate does. The step sits in lint.yml's docs-guard block with
check:doc-anchors/check:quick-reference-counts, deliberately: the edit that breaks it is a docs edit, so apackages/**filter would blind it to its own failure mode. No new required context.Verification
Gate union derived live at
1f6d4507e(the final commit — clean tree, nothing added after). Exit codes captured before any pipe; 24 of 25 green:Repo-wide
pnpm lint(eslint . --no-inline-config) run in full at1f6d4507e—LINT_EXIT=0, 53s. No narrowing claimed.check:type-check-debtexits 1 locally, and it is 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 satisfies that precondition one step earlier:Its non-re-measure leg printed green in the same run (
check-type-check-coverage: OK — 65/78 workspace packages type-checked). This diff also carries zero TypeScript into any tsc program: the four changed paths are a workflow, rootpackage.json(private), one.mjs, and one.mdx, and roottsconfig.jsonsets noallowJs, so the.mjsis outside the root program.No changeset — nothing here publishes: a repo gate, a private root manifest entry, a workflow step and a docs page.
skip-changesetapplied.Generated by Claude Code