fix(pm): a pure-regeneration head move keeps the contract-review record, decided on the committed trees - #19634
Conversation
The maintainer ruled 2026-09-20 that a pure regeneration commit does not re-open an at-tier review record. The criterion is machine-read on the COMMITTED trees — the paths that moved between the recorded head and the new one, restricted to those carrying no `merge=os-regen` attribute, must be empty — and never a seat's own statement. The `Regen-provenance:` line is a pointer a later reader re-runs, not the evidence. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Seventeen cases in `check-clause2-carriers.mjs` and seven at the queue: the chain read off either thread, the multi-hop walk, the ambiguity that carries nothing, the empty tree that carries, the hand-written path that does not, the record id the line must name, and the unreachable tree that is UNJUDGED rather than clean in both readers. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
…and edit The ruled criterion reads 'every touched path is a generated artefact OR the merge commit's own carry-over from main'. Round 1 implemented only the first arm, so a head-to-head diff read every path main carried over as hand-written and the exception never fired on the loop the card measured. The second arm, machine-read on committed trees: a path this pull request never touched at EITHER head moved only because the base moved. The PR's own delta is read once per head (merge-base + one name-only diff), which is the same verdict as a per-path diff comparison at four calls instead of two per path. A run naming no base ref is UNJUDGED, never clean. The reference text returns to its ceiling: the exception compresses to one line, and the duplicated-and-drifted --pair exit table retires to the checker's own header, which is the authority on that detail. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments(a) Criterion — faithful, and the second arm is the ruling's own, not an invention. ② Semver levelNo release. ③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
…two carriers is one hop Three things the at-tier review named. (1) The rule line now carries the literal `Regen-provenance:` token, so a seat reading the governed text alone can write a line the reader matches; it stays one line at 110 bytes and the file stays at its ceiling. (2) regenChainToHead de-duplicates hops on record + from + to BEFORE the ambiguity test: the reader searches both carriers and the text trains the dual-carrier habit, so one hop posted on the PR and on its card arrived twice and ended the walk. Two DIFFERENT hops into one head still do. (3) The queue battery's duplicate specimen becomes a real slipped-in-edit case, and the path it names is asserted at the reader that owns the reason. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments(a) Criterion — faithful, both arms re-walked in code. ② Semver levelNone — no release. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #19244 (the card stays open for the ruling's fourth bullet — arm-time drift re-measurement in
references/landing-operations.md, PR #19379's region — and for thecheck-governed-merges.mjsCURRENT-head prose; the seat returns it to the queue at this PR's landing)Clause-②: no
A review record binds to a head, so any push re-owes the review. On a generated-artefact-dense surface that is a loop the reviewed seat cannot exit: somebody else lands, baselines drift, the seat regenerates, the head moves, the record is owed again — measured four times in one round on this card, with two PASSed pull requests left unlanded.
The maintainer ruled it (2026-09-20, director seat batch #193 item 2, letter B′, comment 5749024878), verbatim and untranslated:
What lands
The rule text —
references/contract-review.mdgains ONE line under the head-binding sentence at :20 and retires one, so the file stays at its ceiling of 60 (headroom 0), the new line at 110 bytes — it names the literalRegen-provenance:token the reader matches, which is what the ruling's execution paragraph orders into this line:Regen-provenance:行。Retired as a provable duplicate: 「0 = 确定性行全清;4 = 任一不成立,只确定性行红才挡落地;3 = 环境答不了 ⛔ 不作干净。」 — its surviving homes are the checker's own header section
## Exit codes — the refusal to read as clean, in one table(which AGENTS.md makes the authority on that detail, and which shows the retired line had also drifted: 3 is PREREQUISITE NOT MET there, and 2 is the cannot-answer verdict) andSKILL.md〈入队与落地〉 :657 for 「只确定性行红才挡」. TheRegen-provenance:exact format now lives in the checker's C3moved-after-clearremedy, the row a seat whose head moved lands on.The criterion, both arms, on committed trees: of the paths
git diff -z --name-only OLD NEWlists, drop every one carryingmerge=os-regen(git check-attr --source NEW -z merge --stdin), then drop every one this pull request never touched at either head — a path absent from bothgit diff --name-only MERGE-BASE-OLD OLDandgit diff --name-only MERGE-BASE-NEW NEWmoved only because the base moved, which is the ruling's own 「the merge commit's own carry-over from main」. Empty is the whole criterion. The second arm is what makes the exception fire at all: without it a merge-forward lists every path main carried over and reads them as hand-written.The mechanism, once, in
scripts/pm/check-clause2-carriers.mjs, and reached by the queue guard through the lazy import it already takes (⛔ no second parser):REGEN_PROVENANCE_LINEreads one hop off either thread — the PR's or its card's — in the shapeRegen-provenance: RECORD-ID · OLD-HEAD → NEW-HEAD · COMMAND → (empty). Everything after the second sha is the seat's own transcript and is deliberately unread.regenChainToHeadwalks back from the pull request's current head over as many hops as the thread carries. Hops are de-duplicated on record + from + to BEFORE the ambiguity test — the reader searches both carriers and the governed text trains the dual-carrier habit, so one hop posted on the PR and on its card is one hop — while two DIFFERENT hops arriving at one head still end the walk rather than being ranked.unexplainedPathsBetweenruns the ruled test on the two committed trees: the two-dot name-only diff, the attribute read with--source NEW(so.gitattributesitself is read out of a commit, never out of the working tree), and the PR's own delta at each head againstmerge-base BASE head— the base isorigin/mainin the carriers reader and the merge group's own base sha in the queue guard.regenCarryanswers four states that are never folded:none(no line — today's rule, untouched),carried,refused(a line that does not certify),unreadable(the environment could not answer — a commit or the base ref this reader cannot reach).gateBindingStateno longer reportsmoved-after-clearfor a carried move, andlocateReviewOfRecordre-reads the record at the carried head — pinned to the record id the chain names, so a line pointing at a comment the thread does not carry certifies nothing.The line is a pointer, never the evidence. Every reader re-runs the test itself. A reader that cannot reach both commits or the base answers with a gap: the pair is UNJUDGED in
--pairand the queue guard refuses onEXIT_REFUSED_UNREADABLE— ⛔ never clean, in either reader. A seat that writes the line and nothing else has certified nothing.The committed-trees half is not stylistic: it is comment 5748085403's reading, where one un-added regeneration answered
git status,git diff --cachedandgit diffthree different ways and the--cachedreading was main's side, which looks exactly like the answer.Measured, on real committed trees
Measured with the installed git (2.43.0), on this branch's OWN history rather than a fixture:
60c99d8→180ce09.changeset/*.mdthat main brought; unexplained = 0744a0a3→180ce090b4022b→744a0a3Both directions demonstrated on real trees. The line reader was also run against the real specimen this card recorded (record 5746847791,
5dd391125e→e1ae025756) and parses it.Ablation, one-shot through
scripts/ablation-replace.mjson the committed tree: removing the hop de-duplication (blob9203b8a3c258→4369e387fbc9, anchor 1 → 0, proved on disk) fails exactly the two pins that cover it — 2 of 1140, by name — and the restore is byte-identical to the HEAD blob with a cleangit diff HEAD.Gates — every one, with the exit code captured before any pipe
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 44 families for these three paths at the final head; all 44 ran and--ranreconciles 44/44 with 0 NOT-MEASURED (a derived zero: every row recorded an exit code and none is 3).check:pm-clause2-carriers(1140 cases; 1115 on main),node scripts/pm/check-governed-queue-guard.mjs --self-test(312 cases; 301 on main),check:pm-skill-ratchet,check:pm-skill-id-lint,check:skill-frame-sync,check:pm-dispatch-gates,check:pm-governed-merges,check:nul-bytes,check:doc-authoring,check:cross-package-test-inputs.check:pm-governed-proseexit 0,node scripts/check-skills-token-ratchet.mjsexit 0.pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst exited 3 (PREREQUISITE NOT MET — nothing measured). Re-run afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lint: exit 0.git merge origin/mainat the final head brought49d5069(17 files:packages/specfield-scale andscripts/pm/post-stamped.mjs) — no conflict, nomerge=os-regenpath moved, no deferral (node scripts/check-regen-pending.mjsexit 0),pnpm-lock.yamlunmoved; every gate above was run AFTER that merge, on the final head.Line ratchet — green, paid in the file's own currency
pnpm check:pm-skill-ratchet :: exit 0— 「contract-review.md is 60 lines (ceiling 60; headroom 0)」. The ceiling was NOT raised andscripts/pm/check-skill-line-ratchet.mjsis untouched: round 1's two lines compress to one and one provably duplicated line retires, the only currency a line ratchet takes. Two other duplicate candidates were examined and left in place because each carries a residue with no surviving home (a scheduling clause at :26; the ③ of a numbered list at :47).Concurrency
PR #19379 (draft,
claude/pm-superseded-references-tier) is open onscripts/pm/check-governed-queue-guard.mjs. Its diff was read first: it rewrites the THIRD-leg header prose around the superseded references-tier wording (lines ~279–310) and one self-test summary string. This PR touches neither — its hunks aregit()'s stdin,runGuard's reader parameter, the pair it builds,recordVerdict's carried fields, the CLEAR rendering and a new self-test battery. The later lander merges once.origin/mainwas merged into this branch before this PR opened.#19068— theexports-map sentence in the same reference file — is a different card and a different region; it is not addressed here and remains open.Acceptance notes
check-half-states.mjsH51 was measured, as the ruling's execution stroke asks: it does not refuse a moved head. It fires only on a verdict for the current head while the label is still hung, and says so itself — "A review naming an OLDER head is NOT this row". It is report-only patrol input and writes nothing, so nothing there needed changing.check-governed-merges.mjsis the post-merge audit and refuses nothing; its Tier S prose says a record "for the CURRENT head". That sentence is now narrower than the rule, but it is prose in a report-only tool and outside this card's file surface.references/landing-operations.mdis PR fix(pm): repair two carriers still spelling the superseded references-tier boundary #19379's region. It is not landed here.unexplainedPathsBetween, the reader that owns the reason. Making the queue print it is a ~9-line change, named and not taken.skip-changeset:.claude/**andscripts/pm/**are in no package'sfiles[]; this diff publishes nothing.Generated by Claude Code