Uh oh!
There was an error while loading. Please reload this page.
feat(gates): name the runtime-interface-only ADR-0087 disposition and check it (#8299) - #8456
feat(gates): name the runtime-interface-only ADR-0087 disposition and check it (#8299)#8456os-zhuang wants to merge 2 commits into
Conversation
… check it (#8299) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e
…ow reads (#8299) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
commented
Aug 13, 2026
PM note on the red That gate's own header states the rule: a PR whose diff touches ⛔ This seat will not clear it. The standing guardrail for Awaiting a human merge. Everything else on the branch is green and the substance is in the review comment on #8299. Generated by Claude Code |
os-zhuang
commented
Aug 13, 2026
Second CI note — the ESLint job's red is a different thing from the ADR gate's, and it is not an eslint finding. The job dies inside an unrelated gate's fixture self-test: Attribution, measured rather than assumed — this branch changes exactly four files: Neither Next step is a re-run of the failed job to separate flake from real: Generated by Claude Code |
os-zhuang
commented
Aug 13, 2026
ESLint resolved: flake, not a defect — and stated with both readings rather than as a verdict. Same commit
A red that does not reproduce on the identical tree is an environment/state failure in that gate's temp-repo fixture, not a finding about this branch — which is also what the attribution said before the re-run: the diff touches four files, none of them Current state of this PR: everything green except Generated by Claude Code |
Fixes#8299
⛔ ADR-class: this PR is for the maintainer to merge by hand. It touches
docs/adr/**, so it is pushed, opened as a draft and left visibly waiting — never queued, never auto-merged.check-adr-merge-approvalwill be RED until an approving review exists; that is the gate working, not a defect in this branch.The gap, and what closes it
ADR-0087's changeset disposition had a closed, three-valued vocabulary —
unpublished,already-registered,no-migration-prescription— and every member reasons about metadata. PR #8277 (out of scope: not addressed here, already merged) hit a shape outside that taxonomy: a published runtime TypeScript interface with no metadata surface at all. Its exemption argument was correct and was accepted, but it was prose with no category to cite and nothing mechanical behind it.This PR carries both halves in one commit series so the text and the predicate cannot drift apart at birth:
docs/adr/0087-metadata-protocol-upgrade-contract.md— a new addendum recording the disposition vocabulary itself (it had never been written down anywhere but the gate) and ratifying D7,runtime-interface-only.scripts/check-adr-0087-registration.mjs— the category, its predicate, and a two-way pin between the two files.scripts/adr-anchors/scripts__check-adr-0087-registration.mjs.json— PD [WIP] Add Chinese version of the documentation #13 anchor, so the next author of this gate is told which decision they are standing on.Three measurements, one of which changed the design
1. The proposed predicate, run literally, REFUSES its own worked example. The card proposed "the touched exported symbol appears in no
*.zod.ts, no speccontracts/**entry, and no object definition". Measured onorigin/main:Both hits are a homonym:
metadata-persistence.zod.ts:166declares its own unrelatedPackagePublishResult, and the contracts file merely imports that one. Neither is the service interface #8277 changed (packages/services/service-package/src/index.ts:76). A bare name is not a symbol identity in this repo — so a claim namespath#Symbol, the notationpackages/spec/export-origins/*.jsonalready uses, and the reference scan clears a hit file that declares the name itself or imports it from another module.That collision is now the predicate's own accept/refuse fixture, and it is real rather than synthetic: the same name under two paths must come out two different ways.
2. #8277's exemption was held by a detector MISS, not by a finding. Running the shipped detector on its real changeset (
.changeset/lucky-schools-smash.md, merged asfc71b84):findMigrationPrescriptionreturnsnull, even though the body does tell consumers "readresult.driverFault?.messagewhere you readresult.error" — no arrow, no framing, so branches 1–4 all miss it. Two consequences, both encoded: the category was worth naming, and it can inherit the prescription refusal at zero cost to the worked example.3. The disposition vocabulary was not in the ADR at all.
grep -i 'disposition\|changeset' docs/adr/0087-*.mdreturned nothing before this PR. The card's "the disposition text lives with ADR-0087" was aspiration, not fact — so the addendum writes the whole vocabulary down, which is what makes a category citable.What the category checks
For each named
path#Symbol, at HEAD:interface/type/class/enumreally exists there;*.zod.ts, not underpackages/spec/src/contracts/, not an object definition;z.input< typeof X >and family (spaced here only so GitHub's sanitizer does not eat it), which live in ordinary.tsfiles too;And it inherits the
no-migration-prescriptionrefusal, which is what makes it a narrowing of that catch-all rather than a fifth way around it. Nothing refused today becomes claimable by renaming the category (pinned:RIO-R9).Deliberately not checked: that the author named every symbol their PR touched. Like
registered, the gate judges the claim that was made — inferring the touched surface is the cross-package retirement detector the 2026-08-07 ruling routes around.The two halves cannot drift
assertInputsgains a fifth rot assertion: the categoriesCATEGORIESaccepts and the categories ADR-0087 documents must be the same set, both directions. A category the gate accepts and the ADR never describes is an exemption an author cannot look up; a category the ADR describes and the gate rejects is one nobody can claim. Both are red, and the assertion was observed failing before the ADR half was committed.Evidence
Accept/refuse proved on the real repository, planting a changeset and running the shipping gate (
--base origin/main), then reset away:Self-test: 153 → 202 assertions, all over real temp git repos through the real
scan()/assertInputs()path. New casesRIO-G1,RIO-R1…RIO-R9,V1–V3,U1–U12b.Gates run locally, all green except the one that is supposed to be red:
check:adr-0087-registration(+--self-test),check:changeset-gate-self-tests,check:adr-anchors,check:objectui-changeset,check-adr-links(534 links resolve),check:nul-bytes(7621 files),check-adr-merge-approval --self-test. Gate families re-derived against the actual changed paths withscripts/pm/dispatch-gates.mjs: identical to the dispatch list, nothing added.Why
check:objectui-changesetis in the diffobjectui-changeset-digest.mjsbuilds a throwaway repo and runs this gate as a subprocess (#6494 round trip). The new input assertion made that fixture red, correctly — it had no ADR. It now copies the real record next to the gate copy it already makes, the one spelling that cannot drift when the vocabulary next changes.No changeset
scripts/+docs/adr/touch no published package's shipped files, so nothing releases;skip-changesetapplied.Generated by Claude Code