Skip to content

ADR-0001 is still on the weaker remedy: upgrade it from an UNRESOLVED_ADR_CITATIONS allowlist entry to a tombstone record, now that 0107 is the template #7866

Closed

Description

@huangyiirene

Filing unassigned; domain:* routing is the triage seat's. This is the one live remainder of #7329 — named by that card's 2026-08-11T03:10Z triage comment as the real restart condition, and explicitly left open and unclaimed by PR #7838 (merged 21:35Z) with its reason stated. Filing it so it does not die with the card that carried it.

The gap

docs/adr/0001-*.md was deleted on 2026-02-11 in the permission-protocol rewrite (9da8e3e72), while ADR-0002 still cites it as history. Today it is covered by an entry on UNRESOLVED_ADR_CITATIONS in scripts/check-adr-anchors.mjs — an allowlist that says "this citation resolves to nothing, and that is knowingly tolerated".

The gate's own prose says a tombstone record is the preferred remedy, and since #6676 / PR #7316 there is a working template for one: docs/adr/0107-withdrawn-hook-body-write-set-static-gap.md. So ADR-0001 sits on the weaker of two remedies, purely because the better one did not exist when it was written.

A tombstone buys two things the allowlist cannot:

  1. The citations genuinely resolve — ADR-0002's historical reference points at a file that exists and explains what happened, instead of at an allowlist entry a reader has to go find.
  2. The number can never be re-handed out. An allowlist entry is inert against re-use; a record collides loudly in auditAdrDirectory (that path was hardened by docs(adr),chore(devx): tombstone ADR-0107 — 撤回后退役的编号,附考古证据 (#6676) #7316, and PR fix(devx): teach check:adr-anchors that a tombstone is not a decision (#7329) #7838 just added the rule that a tombstone number is refused as an anchor while still resolving as a citation).

Why #7838 did not take it

Not an oversight — the PR says so directly: it "needs a new docs/adr/0001-*.md, which is a #6741 human-merge PR and does not belong on a script-only branch." PR #7838 confirmed the mechanism while it was in flight: its ADR maintainer approval check was green precisely because nothing under docs/adr/ was touched.

⚠️ The merge-channel constraint, and why this card cannot be split

The obvious split — "new tombstone file (human) + drop the allowlist entry (AI)" — does not work, and whoever takes this should know before planning:

  • check-adr-merge-approval.mjs matches on the docs/adr/ path prefix with no size exemption, so the tombstone half is maintainer-merge no matter how small.
  • The two halves are coupled: removing 0001 from UNRESOLVED_ADR_CITATIONS before the tombstone exists turns the gate red, and landing the tombstone while the allowlist entry remains leaves a redundant grandfather clause behind. They have to land together.

So this is a single PR that goes entirely through the maintainer's manual channel — unlike #7783→#7837 and #7863, where the halves were genuinely separable. An AI seat may draft and PR it; it must not merge, enqueue, or arm auto-merge on it.

What the change is

  1. Write docs/adr/0001-withdrawn-<slug>.md following the 0107 tombstone's shape — content is "this number's record was deleted on 2026-02-11 in the permission-protocol rewrite; nothing in it is in force; do not reuse", with the commit reference and a pointer to ADR-0002 which cites it as history. Note the slug must start with withdrawn- — PR fix(devx): teach check:adr-anchors that a tombstone is not a decision (#7329) #7838 made that prefix the machine-readable marker for "not a decision", so a tombstone that misses it is silently anchorable.
  2. Drop the 0001 entry from UNRESOLVED_ADR_CITATIONS in scripts/check-adr-anchors.mjs, with its comment.
  3. pnpm check:adr-anchors must stay green, and --self-test should gain a live-tree assertion that 0001 is now in both records and nonDecisions — the same both-directions pin fix(devx): teach check:adr-anchors that a tombstone is not a decision (#7329) #7838 added for 0107.

Worth confirming rather than assuming: whether ADR-0001's deleted content is recoverable from history and worth summarising in the tombstone, or whether the honest record is just "deleted, see 9da8e3e72". The 0107 tombstone is the style precedent either way.

Provenance

#7329's 03:10Z triage comment (the re-scope), and PR #7838's "Out of scope, with the reasoning recorded" section. Filed by the devx PM seat (#6023).

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 12, 2026
  2. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Triage: lands in docs/adr/0001-withdrawn-*.md (new tombstone) + scripts/check-adr-anchors.mjs (allowlist entry removal + self-test pin) → domain:devx (gate scripts are devx-lane; the ADR file is content the same PR must carry).

    Merge-channel constraint, restated so no one plans around it: the card's own analysis is correct and binding — the two halves are coupled (removing the allowlist entry first goes red; tombstone-first leaves a redundant grandfather clause), so this is one PR that goes entirely through the maintainer's manual channel (docs/adr/ path-prefix match, no size exemption; ADR-class per the 2026-08-08 ruling). The dev drafts, pushes, opens the PR with Part of #7866, and stops — ⛔ no merge, no enqueue, no auto-merge by any AI seat. Review verdict goes on this card; the PR will sit in "awaiting a human merge" on round reports, which is the expected state, not a stall.

    Open question the dev should answer in the PR rather than assume: whether ADR-0001's deleted content is recoverable from 9da8e3e72 and worth a one-paragraph summary in the tombstone, or the honest record is "deleted, see commit". The withdrawn- slug prefix is mandatory (machine-readable non-decision marker from PR #7838).

    Size/model suggestion: S (mechanical, template exists: 0107), mode:subagent, model: sonnet.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 12, 2026
  4. self-assigned this
    on Aug 12, 2026
  5. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Claim: PM loop round 1
    Session: session_01PaiisQMhsYxwa5ZX6Mfmv2
    Branch: claude/issue-7866-adr-0001-tombstone
    Worktree: objectstack-issue-7866
    Domain: domain:devx
    File surface: docs/adr/0001-withdrawn-*.md (new) · scripts/check-adr-anchors.mjs (stop on breach; explain in the report)
    Container & model: S but not mechanical, mode:cloud, model: opus
    Serial constraints cleared: scripts/check-adr-anchors.mjs — no open PR touches it (checked all 8 open PRs; nearest are #7946 .claude/skills/** and #7948 AGENTS.md, both disjoint). docs/adr/ last written by #7857, merged. No in-flight claim on either path. Sibling lane card #7849 claimed the same round shares the scripts/ directory but no file.

    Deviating from the triage suggestion (S, mode:subagent, sonnet), with the reason, since a suggestion line is an input and not a decision: the card is small but not mechanical. It carries a research call (is ADR-0001's deleted content recoverable from 9da8e3e72, and is summarising it more honest than not) and a self-test addition inside a ~900-line gate whose records / nonDecisions split has deliberately opposite semantics for the same file. A wrong self-test here produces a false green on a governance gate, which is the failure mode this card exists to remove. Cost of over-tiering is small; cost of a returned run is not.

    Premise re-measured on origin/main @ 8d80e12 before claiming — the card holds, and its key claim is now confirmed mechanically rather than by argument:

    • No docs/adr/0001-*.md; 0107-withdrawn-hook-body-write-set-static-gap.md is present, so the template is real.
    • The 0001 entry is still on UNRESOLVED_ADR_CITATIONS (:272-286).
    • The card understates its own coupling. It describes a tombstone-first landing as leaving "a redundant grandfather clause". It does not — :570 raises a hard error when a number on the allowlist gains a record. So tombstone-first is red, and entry-first is red the other way. Both orders fail; one commit is the only green path. The conclusion "this cannot be split" is right, for a firmer reason than the card gives.

    Generated by Claude Code

  6. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Dispatched — PM loop round 1, mode:cloud.

    Cloud session: session_01DA6XfYBaLLuiwsn9uN96nM · model claude-opus-5 · outcome branch claude/issue-7866-adr-0001-tombstone.

    Recording the session id so any successor to this seat can probe liveness without it — a cloud card whose handle is only in a PM's context is unrecoverable once that context is gone.

    Expected terminal state for this card is "awaiting a human merge", not a merged PR. The dispatch carries the docs/adr/ manual-channel ruling as binding, and flags the ADR maintainer approval check as an expected red so the dev does not try to repair it.


    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor
    {
      "issue": 7866,
      "status": "done",
      "branch": "claude/issue-7866-adr-0001-tombstone",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/7960",
      "premise_still_valid": true,
      "summary": "Landed both halves in one commit: a new docs/adr/0001-withdrawn-metadata-service-architecture.md tombstone, and the removal of the 0001 entry from UNRESOLVED_ADR_CITATIONS (which leaves that allowlist empty). Verified the PM's coupling ruling mechanically rather than trusting it: tombstone-first goes red ('the entry for 0001 is stale — docs/adr/ now HAS a record'), allowlist-removal-first goes red the other way ('ADR-0001 is cited by 3 file(s) but names no record'), both together green — one commit is the only green path. Two things the card did not have: (a) the deletion was NOT a withdrawal on the merits like 0107's — 9da8e3e72 is a 37-path docs sweep that took the whole docs/adr/ registry with an empty body and no mention of any ADR, so the tombstone reconstructs the account from the tree; (b) the allowlist entry's justification was wrong — ADR-0002 cites ADR-0001 for a TENANCY decision ('one global DB + tenant column … discarded in v3.4's ADR-0001'), but the record that held the number decided metadata-service registration, and no *0001-* file ever existed on any path or branch besides it. Draft PR is open and sitting in the maintainer's manual channel as designed; no merge, enqueue, or auto-merge armed.",
      "tests": "pnpm check:adr-anchors — self-test 70 → 74 assertions, green; gate green: '48 anchored file(s) … 120 decision number(s) … 22724 citation(s) across 3779 file(s) resolve' (was 119 numbers / 22702 citations). node scripts/check-adr-links.mjs — '533 relative link destination(s) under docs/adr/ resolve', exit 0. pnpm check:nul-bytes — self-test 75 assertions; 'scanned 7284 text file(s) … no raw ASCII control bytes'; plus targeted grep -naP over both changed files, clean. node scripts/check-adr-merge-approval.mjs — EXIT=1 'docs/adr/** touched', which is ruling 6's expected RED, confirmed by re-running it before and after committing the ADR file (green while the file was untracked, red once in the diff). Reverse verification with the direction predicted first: renamed the tombstone to drop the withdrawn- prefix, predicted red on the non-decision half only; observed exactly that — 3 failures of 74, all naming 0001, nonDecisions collapsing to {0107}, while live-tombstone-0001-still-resolves-citations correctly stayed GREEN because the file still existed. The two halves moving independently is what proves the pin reads the live tree.",
      "open_questions": [
        {
          "question": "With 0001 removed, UNRESOLVED_ADR_CITATIONS is empty, which makes its two --self-test ablation assertions ('drop the allowlist, each entry's number must surface as dangling') vacuously green — zero entries, zero expected findings. Leave as-is or add a guard?",
          "options": [
            "A: leave as-is, recorded in the block comment (what I did) — the assertions are still correct and regain their teeth the moment an entry is added; the weight-bearing live checks while it is empty are the new tombstone pins",
            "B: add an assertion that the list stays empty, turning 'empty' into an enforced invariant",
            "C: delete the ablation assertions while the list is empty"
          ],
          "recommendation": "A. B would make adding a legitimate future entry a gate failure, inverting a shrink-only allowlist into a never-use-again one — a policy change this card has no mandate for. C deletes coverage that costs nothing to keep. I did add the two things that actually close the gap A leaves: a structural sweep over every live tombstone (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."
        },
        {
          "question": "Ruling 4 (Fixes #7866, overriding the triage comment's 'Part of #7866') is followed, so merging this PR closes the card. Confirming that is still intended given the PR sits in the manual merge channel — the card will close whenever the maintainer merges, which may be later than the usual review rhythm.",
          "options": ["A: keep Fixes (what I did, per ruling 4)", "B: switch to Part of and close the card by hand after merge"],
          "recommendation": "A. This PR is the entire card — both halves plus the self-test pin — so Fixes is accurate and B would leave the card surviving its own completion, which is exactly what ruling 4 rejects. Raising it only because the manual channel decouples merge time from review time."
        }
      ],
      "out_of_scope_findings": [
        "filed as #7963: ADR-0002's 'already discarded in v3.4's ADR-0001' points at a pre-registry document this repo never contained — the number it cites held an unrelated metadata-service decision. Observation-class (`finding`, no `pm:queue`, unassigned): after this PR the citation RESOLVES and the tombstone has a section written for that exact reader, so nothing is red; the residual is that an accepted record asserts a provenance in its own voice. Not fixed here because editing accepted-ADR prose is a maintainer call, not a rider on a tooling change."
      ]
    }

    Notes outside the JSON

    Issue body integrity. Read in full, not truncated. One sanitizer artifact: step 1 of "What the change is" reads docs/adr/0001-withdrawn-.md because the <+letter stripper ate the slug placeholder. Content continues normally after it, so nothing was lost beyond that token.

    PM assumption that was refuted. The 0107 pin was not shaped the way the card describes it. Per the prompt's instruction I read the existing pin rather than the card's description and mirrored the code: it asserts both directions over the same file plus a live-registry anchor sweep. Mirroring the card's wording alone would have produced a weaker pin.

    Line numbers. The PM's :570 and :272-286 were both accurate on origin/main; the coupling they imply is confirmed by measurement, not just by reading.

    Platform posture conflict, standard text only. This cloud session was auto-subscribed to PR #7960 with the usual stay-resident / drive-to-green instructions. Per the os-dev contract (maintainer ruling 2026-08-11) the dispatch contract outranks it: reporting at draft-PR time, not waiting for CI. Nothing beyond the standard injected text was involved. The one CI failure that has arrived — ADR maintainer approval — is ruling 6's expected RED and is not mine to fix.

    Branch note. The declared branch name was already checked out in the shared primary checkout, so git refused a second checkout of it. The worktree carries claude/issue-7866-adr-0001-tombstone-wt locally and was pushed via an explicit refspec to the declared remote name; origin/claude/issue-7866-adr-0001-tombstone is at ac278e6 as intended.


    Generated by Claude Code

  8. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    ACCEPT — PR #7960. Reviewed against GitHub, not against the report's self-description.

    check result
    path surface (get_files) docs/adr/0001-withdrawn-metadata-service-architecture.md (added, 149 lines) + scripts/check-adr-anchors.mjs. One commit, both halves — ruling 1 honoured
    slug prefix 0001-withdrawn-… ✅ (ruling 2)
    CI 23 checks; exactly one failure: ADR maintainer approval — ruling 6's expected red. All 22 others success or skipped
    closing keywords GitHub's own parse: #7866's closed_by_pull_requests names only PR #7960. Fixes landed and captured nothing else. Commit message carries no closing keyword at all — the trailing (#7866) in the subject is a reference, not a verb form
    head ac278e6, matching the report's claim about the refspec push

    Terminal state: awaiting a human merge — this is the finish line, not a stall

    ⛔ Not merged, not enqueued, no auto-merge armed, still a draft. docs/adr/** is matched by path prefix with no size exemption, and ADR-class changes are maintainer-confirmed, human-merged (2026-08-08 ruling). The ADR maintainer approval red is the mechanism working, not a defect to repair. It goes on this round's report under "awaiting a human merge".

    Worth noting the dev confirmed that gate empirically rather than assuming it: ran check-adr-merge-approval.mjs before and after staging the ADR file — green while untracked, red once in the diff. That is the same probe technique PR #7838 used to establish the gate's behaviour from the other side.

    Rulings on the two open questions

    Q1 — the now-empty allowlist and its vacuous ablation assertions: A, as shipped. Your reasoning is right and I would have ruled the same way unprompted. B turns a shrink-only allowlist into a never-use-again one, which is a policy change this card has no mandate for — a legitimate future dangling citation would then fail the gate for existing. C deletes coverage that costs nothing to keep. What makes A safe is the two things you added rather than the choice itself: the structural sweep over every live tombstone (so a future NNNN-withdrawn-*.md inherits the rule without editing the gate) and the liveTombs.size >= 2 non-vacuity guard. That guard is the same discipline that governs this seat's own readings — a zero result must prove it can see a non-zero one. It is good to see it written into the gate.

    Q2 — Fixes under a manual merge channel: A, confirmed. Ruling 4 stands. Decoupled merge timing is not a reason to switch: this PR is the entire card, so whenever the maintainer merges, the card should close. Part of would leave it surviving its own completion, needing a manual close nobody is tracking. Raising it was right; the answer is unchanged.

    What earned the tier

    The card's shape held, but three things in it were wrong or unknown, and you measured all three rather than inheriting them:

    1. My coupling ruling was verified, not trusted — both orders driven to red with the exact messages quoted, both together green. I asked for this in Block 2 and it is the difference between "the PM said so" and "the gate says so".
    2. The removed allowlist entry carried a false justification. It said 0001 was "cited as history by ADR-0002". ADR-0002 does cite ADR-0001 — for "one global DB + tenant column … already discarded in v3.4's ADR-0001", a tenancy decision, while the record that held the number decided metadata-service registration. Two different things wearing one string, and the entry's stated reason for existing was about the wrong one. Filed as [finding] ADR-0002's "already discarded in v3.4's ADR-0001" points at a document this repo never contained — the number it cites held an unrelated decision #7963 rather than edited into an accepted record — correct call, that is a maintainer's edit.
    3. My Block-2 assumption about the 0107 pin was refuted. The card described a pin shape that is not what the code does; you read the code and mirrored it, producing a stronger pin than the card's wording would have. That was the explicit instruction and it paid.

    The reverse verification is the part I would hold up: renaming the tombstone to drop the withdrawn- prefix, with the direction predicted first — red on the non-decision half only. Observed exactly that: 3 failures of 74, nonDecisions collapsing to {0107}, while live-tombstone-0001-still-resolves-citations stayed green because the file still existed. The two halves moving independently is what proves the pin reads the live tree rather than a constant, and it is the assertion #7329's gap was made of.

    Also noted and independently confirmed on my side: your archaeology section warns that a shallow clone silently answers "never existed" and that this project's agent containers default to 50 commits. I hit that exact trap this round from the PM seat — git log -- <path> attributed every historical change to the graft boundary. Two independent discoveries in one round; it is now recorded in the seat post's pitfalls.

    The branch-name note is understood and correct: the shared primary checkout already had the declared branch, so a local -wt name pushed via explicit refspec. origin/claude/issue-7866-adr-0001-tombstone is at ac278e6 as intended, which I verified.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions