docs(adr),chore(devx): tombstone ADR-0001 — retire the number, empty the citation allowlist (#7866) - #7960
docs(adr),chore(devx): tombstone ADR-0001 — retire the number, empty the citation allowlist (#7866)#7960hotlong wants to merge 1 commit into
Conversation
…the citation allowlist (#7866) ADR-0001 sat on the weaker of two remedies: an `UNRESOLVED_ADR_CITATIONS` entry in `check-adr-anchors.mjs`, which says "this citation resolves to nothing, and that is knowingly tolerated". The gate's own prose prefers a tombstone record, and since #6676 there is a working template for one. This lands that upgrade, and with it the allowlist becomes empty. Both halves are in one commit because the gate refuses either order: * tombstone first, entry kept -> red, "the UNRESOLVED_ADR_CITATIONS entry for 0001 is stale — docs/adr/ now HAS a record" * entry removed, no tombstone -> red, "ADR-0001 is cited by 3 file(s) but names no record under docs/adr/" Both measured on this branch before writing the final state. The record that held the number was `0001-metadata-service-architecture.md`, merged 2026-02-10 (`908d95c82`) and deleted 2026-02-11 (`9da8e3e72`). The deletion was NOT a withdrawal on the merits, which is the substantive difference from ADR-0107: it was collateral in a 37-path documentation sweep that removed the whole `docs/adr/` registry, README and all, under a subject about permission-protocol analysis, with an empty body and no mention of any ADR. The tombstone reconstructs that from the tree rather than quoting a withdrawal that was never written. The record must not be resurrected either way: its selected option was a hybrid dual-provider architecture in which ObjectQL registers `metadata` as a fallback, and the code has since gone single-provider — the one `registerService('metadata', ...)` in the tree is in `packages/metadata/src/plugin.ts`, while `packages/objectql/src/plugin.ts` registers `objectql`, `data`, `manifest` and `lifecycle` only. Corrects a claim the removed allowlist entry carried. It said the number was "cited as history by ADR-0002". ADR-0002 does cite `ADR-0001`, but for "one global DB + tenant column ... already discarded in v3.4's ADR-0001" — a tenancy decision, not the metadata-service one this number held, and not a document that ever existed in this repository. Verified against full history with the clone unshallowed: nothing matching `*0001-*` was ever added under any path, on any branch, other than the record above. The tombstone dissects the discrepancy; ADR-0002's own wording is left alone, being an accepted record. `--self-test` gains the live-tree pin the card asks for, mirroring #7838's 0107 pin: 0001 must land in both `records` (citations resolve) and `nonDecisions` (anchors refused). Pinned by name for 0001 and 0107, plus a structural sweep so a future `NNNN-withdrawn-*.md` inherits the rule without editing the gate, and a non-empty guard so that sweep cannot pass by reading nothing. 70 -> 74 assertions.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
What happened, in order
Why arming auto-merge here is the forbidden actionThe #6741 ruling is "adr 只能由维护者自己确认,人工合并,ai 不得擅自合并", operationalised as: no AI seat may merge, enqueue, or arm auto-merge on a The Nothing was lost — the PR is not merged, not enqueued, and What I did NOT do, and why
The green path is unchangedAn APPROVED review from To the Generated by Claude Code |
🛑 To the seat operating as
|
|
| fact | value |
|---|---|
| PR author | hotlong (id 50353452) |
MAINTAINER_APPROVERS (check-adr-merge-approval.mjs:101) |
[{ login: 'hotlong', id: 50353452 }] — the same account |
| GitHub rule | a user cannot approve their own pull request |
So the only account the gate accepts is the one account GitHub forbids from approving here. There is no sequence of clicks that turns this check green. The maintainer reported exactly that symptom — "提示自己提交的不能批准" — which is the deadlock, not an operator error.
Cause, and it is mine. This PR was opened by a cloud dev session I dispatched, running on this seat's credential — hotlong. Historically docs/adr/** PRs were authored by other accounts (os-project-manager on PR #7316, os-help on #7343, os-zhuang on #6942/#6962/#7087), which is why the gate has worked until now: the author was never the approver. My dispatch changed that without anyone noticing.
What is NOT the fix
⛔ Do not add "author is in MAINTAINER_APPROVERS ⇒ pass" to the gate. AI seats author PRs as hotlong — that carve-out would weld the gate permanently open, which is the opposite of #6741.
Routes, for the maintainer to choose (⛔ this seat takes none of them unilaterally)
- Admin merge with the check red. The governance condition is met — the maintainer reviewed this PR in person and merges it in person; only the gate's chosen proxy for that (an approving review) is physically unavailable. Fastest; leaves a bypass record.
- Re-open from a non-
hotlongaccount on the same branchclaude/issue-7866-adr-0001-tombstone(no code changes), then approve fromhotlongand merge. Walks the designed path; a few more steps. - Root fix — give the agent fleet its own account, so seats stop authoring as the maintainer. Fixes this deadlock for every future ADR PR and closes the identity collision recorded in [governance] The ADR gate blocks the merge but not the ARMING of auto-merge — an AI seat armed it on a live
docs/adr/**PR today, which would have made the maintainer's approval the merge trigger #8012, where the gate's sole trusted account is one the agents hold.
Recorded on #8012 alongside the auto-merge-arming gap. This PR stays as it is — not merged, not enqueued, no auto-merge — until the maintainer says which route.
Generated by Claude Code
Fixes #7866
⛔ This PR is maintainer-merge, and the red check is the system working
docs/adr/is matched bycheck-adr-merge-approval.mjson the path prefix with no size exemption, soADR maintainer approvalwill be RED on this PR and is expected to be. PR #7838 was green on that check only because it deliberately touched no ADR file; this card cannot be landed that way (see below). No AI seat may merge, enqueue, or arm auto-merge here.Confirmed locally with the tombstone committed:
Any other red is mine.
Why both halves are in one commit
The card says the two halves cannot be split and calls a tombstone-first landing "a redundant grandfather clause". It is stronger than that — both orders go red. Measured on this branch, one direction at a time:
the UNRESOLVED_ADR_CITATIONS entry for 0001 is stale — docs/adr/ now HAS a record for that numberADR-0001 is cited by 3 file(s) but names no record under docs/adr/120 decision number(s) … 22724 citation(s) across 3779 file(s) resolveOne commit is the only green path. Worth noting the dangling-citation message names 3 citing files, not the 1 the card assumed:
ARCHITECTURE.md,docs/adr/0002-environment-database-isolation.md, and the gate script itself.The card's open question, answered
Recoverable in full, and the honest tombstone summarises what it decided while refusing to reprint it — the same posture as the 0107 precedent, reached for a different reason.
The archaeology required unshallowing: agent containers here clone 50 commits deep, and a shallow clone silently answers "never existed" (the 0107 tombstone warns about exactly this). Against full history:
docs/adr/0001-metadata-service-architecture.md, Status Accepted, merged 2026-02-10 (908d95c82) and deleted 2026-02-11 (9da8e3e72) — about thirty hours.git show 908d95c82:docs/adr/0001-metadata-service-architecture.md.*0001-*was ever added under any path, on any branch, other than that record. There is no second era of the number as a file.Two findings decide the content, and both cut against a plain "deleted, see commit":
1. It was not withdrawn on the merits — it was swept.
9da8e3e72is a 37-path documentation cleanup that removed the wholedocs/adr/registry (both records of that era plusREADME.md), the entiredocs/METADATA_*family and theexamples/metadata-objectqlpackage, under the subject "feat(docs): add comprehensive analysis of Permission Protocol with AI-enhanced security controls and RLS implementation", with an empty body and no mention of any ADR. This is the substantive difference from ADR-0107, whose withdrawing commit states its reasoning in full. "Deleted, see9da8e3e72" would send a reader to a commit that does not explain, or even acknowledge, the deletion — so the tombstone has to reconstruct it from the tree. That is the case for summarising, not against.2. Its decision is contradicted by shipped code, so reprinting the text would plant a false statement in the decision log. Its selected option was a hybrid dual-provider architecture — both
@objectstack/objectqland@objectstack/metadatamay provide themetadataservice, with ObjectQL registering itself as the fallback provider. Verified against the tree today:registerService('metadata', ...)in the repo ispackages/metadata/src/plugin.ts;packages/objectql/src/plugin.tsregistersobjectql,data,manifestandlifecycle— nevermetadata;IMetadataServiceinpackages/spec/src/contracts/metadata-service.ts.So the tombstone states what the record decided and that the fallback half is reversed, points at
ARCHITECTURE.mdas the live source of truth, and leaves the full text in history — "a withdrawn record reprinted inside its own tombstone reads as a record", borrowing 0107's own line.It also records, without closing, that the single-provider architecture which replaced it has no ADR record of its own.
ARCHITECTURE.mdalready says re-homing that is a maintainer call; the tombstone does not attempt it.A correction the card did not have: what ADR-0002 actually cites
The removed allowlist entry justified itself with "cited as history by ADR-0002", and the card inherited that. It is wrong, and it matters because it is the one citation said to keep the entry earning its place.
ADR-0002 says, of a rejected alternative: "One global DB + tenant column. Was never on the table — already discarded in v3.4's ADR-0001." That is a tenancy decision. The record that held this number decided how the
metadataservice is registered and says nothing about database topology. Today's ADR-0002 is dated 2026-04-19 and supersedes the v3.4/v4.0 per-organization database model, so its "v3.4's ADR-0001" points at a pre-registry document this repository has never contained — confirmed by the archaeology above.The tombstone dissects this in a dedicated section, because it is the first thing a reader arriving from ADR-0002 will want. ADR-0002's own wording is deliberately left alone: it is an accepted record, and correcting its prose is a separate decision, not a rider on this one.
The gate changes
0001entry is dropped fromUNRESOLVED_ADR_CITATIONS, which leaves the list empty. The block comment now says that is the finished state and why: both numbers it ever held (0107 via docs/adr has a number hole at 0107 — either a withdrawn record with no tombstone, or a lost file #6676, 0001 here) left for a tombstone, remedy (a) of the dangling-citation message.--self-testThe pin the card asks for, mirroring what #7838 actually did for 0107 (I read the existing pin rather than the card's description of it — both directions over the same file, since a test of only the red half would pass on an implementation that broke the thing tombstones exist for):
live-tombstone-0001-is-flagged/live-tombstone-0107-is-flagged— innonDecisions;live-tombstone-NNNN-still-resolves-citations— inrecords;ablation-anchoring-the-live-tombstone-NNNN-is-red;every-live-tombstone-resolves-and-refuses-anchors— a structural sweep over whatevernonDecisionsturns out to hold, so a futureNNNN-withdrawn-*.mdinherits the rule without editing the gate. That is the property the filename marker was chosen for in the first place, and pinning only two numbers by name would have quietly given it up;live-tombstone-set-is-not-empty— the guard on that sweep, which would otherwise pass by reading nothing if the marker ever broke.Reverse verification, direction predicted before running. Renamed the tombstone to drop the
withdrawn-prefix; predicted red on the non-decision half only. Observed exactly that — 3 failures, all naming 0001, withnonDecisionscollapsing to{0107}:live-tombstone-0001-still-resolves-citationscorrectly stayed green — the file still existed, sorecordsstill had 0001. That split is the #7329 gap's exact shape, and seeing the two halves move independently is what says the pin is reading the live tree rather than a constant.Verification
pnpm check:adr-anchorsnode scripts/check-adr-links.mjsdocs/adr/resolvepnpm check:nul-bytesgrep -naPover both changed filesnode scripts/check-adr-merge-approval.mjsChangeset: route 2 (
skip-changeset)A gate script plus a decision record; no package behaviour changes and nothing releases. Same route PR #7838 took after a naive empty-frontmatter changeset was correctly refused — changeset deleted,
skip-changesetapplied — so no.changeset/*.mdis added here and the label is applied directly.Scope
Two files, nothing else.
#7329remains closed and#7838is merged; this PR is the remainder that #7838 recorded as out of scope for a script-only branch.ARCHITECTURE.mdanddocs/adr/0002-environment-database-isolation.mdare read but not edited.⛔ No accepted ADR's decision content is changed. The tombstone records a reversal that shipped code performed long ago; it does not perform one.
Generated by Claude Code