Repository navigation
AGENTS.md's RECOGNISED_PATH_SPELLINGS mirror has drifted three times — nothing checks that the published list matches the detector #10855
Description
Activity
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actionsTriage (first-touch grading): promoted
finding→pm:queue, kepttooling+domain:devx, type Task. Rationale: premise re-verified — PR #10852 merged (bfadf84), so theAGENTS.md:96-106mirror is short by the two findUp anchor seeds today. Three drift rounds establish the class; a mirror-ratchet gate is the repo's established remedy shape and the card already specifies the two non-negotiables (both-direction self-test ablation; refuse-not-pass-empty when the fenced block cannot be located). Verification strategy is not a decision-box matter.Dispatch notes carried from the card: the gate PR itself is dispatchable, but the AGENTS.md edit that adds the two missing seeds is governed surface — draft-only, human merge, so the PR lands in the ACCEPT-fork path and the gate will stay red until the maintainer merges the AGENTS.md half (sequence the gate to land with or after the mirror update, or ship the gate with the mirror edit in the same governed draft PR). Size/model suggestion: M; governed-surface one-block mechanical edit rides at opus per the 2026-08-20 tiering ruling, with fable-tier review at ACCEPT.
Generated by Claude Code
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-10855-spellings-mirror-ratchet.Dispatching as a GOVERNED PR, taking the second of the two sequencing options triage names: "ship the gate with the mirror edit in the same governed draft PR."
Premise re-verified on current
origin/mainreading result findUpoccurrences inscripts/check-cross-package-test-inputs.mjs22 findUpmentions inAGENTS.md0 control — __dirnameinAGENTS.md(a spelling that IS published)2 The mirror is genuinely short, and the control makes that zero a reading rather than a grep that finds nothing. Its sibling #10854 (the instances) landed as PR #11891 earlier tonight; this card is the mechanism, and triage deliberately kept them separate.
Zone 1 — rulings
- One PR carrying both halves: the gate and the
AGENTS.md:96-106mirror correction. The alternative — land the gate first — ships it red, which this board has ruled against repeatedly today. - ⛔ GOVERNED SURFACE.
AGENTS.mdis human-merge-only (scripts/pm/check-governed-merges.mjs:274-280). The PR stays draft, requests review fromos-zhuang, and ⛔ is never flipped ready, never armed for auto-merge, never queued — by the dev or by me. The maintainer merges it. - ⛔ It must not be a phantom check — the card's own non-negotiable, and the failure it would otherwise re-create one level up. Required: a self-test with ablations in both directions — a spelling added to the detector but not to
AGENTS.mdmust red, and a mangled or renamed fenced block must REFUSE, not pass empty. - ⛔ Not in scope: widening or narrowing what the detector recognises. This is about keeping the published copy honest, nothing else.
⚠️ Collision — named, and it is a governed co-tenancyPR #11829 (
claude/issue-11819-adr-0125-record-drift) also touchesAGENTS.mdand is itself a governed draft awaitingos-zhuang. The regions are disjoint — theirs is the ADR-0125 record and the Prime Directive #15 trigger; yours is theRECOGNISED_PATH_SPELLINGSfenced block at:96-106. Both land in the same reviewer's queue, who can order them.If your branch goes
mergeable_state: dirty: mergeorigin/mainin and keep BOTH edits. ⛔ Never drop theirs, ⛔ never rebase or force-push a governed file.Why this card is worth mechanising rather than fixing a third time
Three rounds, and ⭐ in two of them the stale sentence was the stated reason for a prohibition — #10163's wrong "only two seeds" line justified "this seed may not change", and #10854's was the same shape. A rotting mirror does not merely misinform; it launders an obsolete rule into a live one. I saw the other end of that tonight: #10854's dev found the prohibition's stated reason false, kept the prohibition, and had to derive a new true reason for it.
⚠️ And one of #10854's claims was already false before the PR that supposedly staled it — so this is not only "code moved, docs lagged". A published claim was wrong on arrival and nothing caught it. A gate catches both.Model tier: opus, per triage's suggestion and the 2026-08-20 governed-surface tiering ruling.
Generated by Claude Code
- One PR carrying both halves: the gate and the
claude commented
on Aug 25, 2026 claudeboton Aug 25, 2026 – with ClaudeContributorAuthorMore actions{ "issue": 10855, "status": "needs_decision", "branch": "claude/issue-10855-spellings-mirror-ratchet", "pr": "https://github.com/objectstack-ai/objectstack/pull/11908", "premise_still_valid": true, "summary": "Both halves landed in one governed draft PR (#11908, draft, review requested from os-zhuang, never flipped ready / auto-merged / queued). New gate scripts/check-published-list-mirrors.mjs holds a declared (constant, document, heading, fence) table and asserts line-for-line EQUALITY between the detector's RECOGNISED_PATH_SPELLINGS and the block AGENTS.md publishes; it locates the block structurally (heading + fence, never a line number - the card's own AGENTS.md:96-106 had already rotted to :92-104) and REFUSES rather than passing empty on a renamed/duplicated heading, a re-tagged, unterminated, empty or ambiguous fence, and on a constant that is missing, renamed, empty or not a string list. It can only ever go RED, stated in its header and in AGENTS.md itself. Wired into lint.yml beside check-declaration-mirrors, self-test first. ONE DECISION IS OPEN AND ONLY THE MAINTAINER CAN SETTLE IT: AGENTS.md sits at its shrink-only line ceiling with ZERO headroom (961/961), so any honest correction of this mirror crosses it; this PR takes it to 969 and check:pm-skill-ratchet is red until the ceiling is ruled on. I did not touch the CEILINGS map - raising it requires a maintainer ruling quoted in the raising PR, and I have none. A second standing ruling shaped the design rather than blocking it: publishing all 24 constant entries verbatim dragged the failure box's issue-ID citations into the instruction surface and check:pm-skill-id-lint went RED - the gate meant to keep the document honest would have broken the document's own prose standard (2026-08-12). The gate therefore judges a projection with no judgement in it: an entry is published unless it is blank, or a comment line that no published entry directly precedes. 24 entries -> 17 published lines, zero ID citations, and a self-test case now pins that citation property against the live constant.", "tests": "Union re-run on the FINAL commit 22312aa9e, verdicts read from each gate's own printed line (exit codes captured before any pipe). GATE UNION (29 families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, never a hand-built path list): 27 green, 2 red. Green highlights: `node scripts/check-published-list-mirrors.mjs --self-test` -> 'All 37 self-test cases passed'; `node scripts/check-published-list-mirrors.mjs` -> 'OK: 1 published list mirror(s) match their constants line for line.'; `pnpm check:cross-package-test-inputs` -> 'All 109 self-test cases passed' + 'OK: 16 package(s) read outside themselves, all declared'; `pnpm check:pm-skill-id-lint` -> 'check-skill-id-lint: 22 file(s) clean'; `pnpm check:pm-governed-merges`, `check:entry-guard`, `check:required-contexts`, `check:step-collectors`, `check:nul-bytes` (6628 files) all exit 0. FULL REPO LINT, not narrowed: `pnpm lint` = `eslint . --no-inline-config` -> 'os-verify-lock: VERDICT command-exit 0 - held the lock 51s - waited 0s'. RED 1: `pnpm check:pm-skill-ratchet` -> 'AGENTS.md is 969 lines; the ratchet ceiling is 961' - this is the open decision, not an oversight. RED 2: `pnpm check:type-check-debt` -> environmental, not from this diff: it throws before judging with '--re-measure cannot run: 56 workspace dependenc(ies) ... have no built type entry point on disk', which lint.yml builds before that step; this diff contains no TypeScript at all. Not run locally rather than reported green. ABLATIONS - 8 legs, all against the FINAL gate (an earlier battery against the pre-projection draft was discarded, not reported). NO BUILD STEP EXISTS IN THIS LOOP and none is claimed: every subject is a root .mjs read directly by `node`, not a package resolved through its dist, so there is no stale-dist failure mode to rebuild away. Each leg instead: mutate -> PROVE ON DISK (sha256 prefix change AND an anchor grep -c moving to a predicted count) -> `node --check` the mutant where it is JS -> run the gate -> restore -> prove the sha is back, all under `trap restore EXIT INT TERM`. A0 control unmutated -> OK exit 0. A1 doc short by a spelling (sha 93f1cfa7c818 -> 98a6bafc6844, 'const REPO = findUp' 1 -> 0) -> FAIL 'line 14 differs'. A2 doc publishes a spelling the detector cannot see ('const CWD = process.cwd' 0 -> 1) -> FAIL. A3 the published prohibition reworded ('belonging to some OTHER package' 1 -> 0) -> FAIL 'line 16 differs'. A4 heading renamed -> REFUSE 'heading not found in AGENTS.md'. A5 fence re-tagged ts -> js ('^```js' 0 -> 1) -> REFUSE 'no ts fence in that section (1 fence(s) of other kinds: js)'. A6 constant renamed to RECOGNISED_PATH_SPELLINGS_V2 (node --check clean) -> REFUSE 'is not exported (renamed, moved, or deleted)'. A7 new spelling added to the constant (node --check clean) -> FAIL 'line 1 differs'. A8 a note-paragraph line added to the constant -> OK, the projection holds; its non-vacuity rests on A7, which reddens through the same machinery on the same file. Post-battery control OK, `git status --porcelain` clean. TWO LEGS WERE VOIDED BY THEIR OWN PROOF STEP rather than reported: one perl -i mutation that matched nothing and exited 0 (sha unchanged), and one whose marker was a substring of the mutant it was meant to count out (RECOGNISED_PATH_SPELLINGS still matching ..._V2). Both were re-run with corrected anchors; neither reading entered the table above.", "open_questions": [ { "question": "AGENTS.md is at its shrink-only line ceiling with ZERO headroom (961/961), so ANY honest correction of this mirror crosses it - the minimum truthful block is +3 lines for the findUp anchor seeds alone. This PR is +8 (961 -> 969): +3 findUp seeds, +2 the manifest-name prohibition that qualifies them, +1 the '-> repo root' annotation, +2 prose naming the ratchet. The ratchet's doctrine says cross a ceiling by compressing in place; MEASURED, the way the map's only previous raise (958 -> 961) was measured - greedy rewrap at 90 columns, per paragraph, across the whole section - the lossless rewrap headroom is 0 lines (6/6, 4/4, 4/4, 6/6, 2/2). So it cannot be paid in place without DELETING content. Raising requires a maintainer ruling quoted in the raising PR; I have none and did not touch the CEILINGS map. How should the 8 lines be paid?", "options": [ "A - raise the ceiling 961 -> 969, recorded in the CEILINGS comment the way the 958 -> 961 raise is (one-line map edit + the ruling verbatim)", "B - raise to 967 and drop the two prose lines naming the ratchet; authors then meet it as a CI red instead of reading it in the file", "C - compress 8 lines elsewhere in the section; measured headroom is 0, so this means deleting load-bearing prose, not rewrapping", "D - stop publishing the copy in AGENTS.md and point at the tool instead (net -8 lines, retires the drift class outright)" ], "recommendation": "A. Real business need: the need is measured, not speculative - the copy drifted three times, twice while being the stated REASON FOR A PROHIBITION, and today the detector says findUp 22 times while AGENTS.md says it 0 (control: __dirname 2, so the zero is a reading). Authors reach this list to decide how to spell an escaping read; a short list makes the scanner silently blind. Long-term soundness: A is the only option that leaves ONE source of truth with a mechanism holding the copy to it. C buys lines by deleting the section's own reasoning - trading a documentation defect for a worse one on the same page. D is the only option that shrinks the file, and it directly contradicts the sentence right above the block ('the recognised list is published rather than left inside the implementation'); reversing that is a policy call, not a line-budget tactic. AI-written-code safety: the failure mode here is silence - an unrecognised spelling yields no flag, so the published list is the only thing telling an author what the scanner can see. B leaves the block correct but removes the sentence that tells the next author editing the detector that the block must move with it; they would learn it from a CI red, which is exactly the discover-at-review cost this card exists to remove. Startup scope discipline: A is a ONE-TIME truthful re-sync of a copy that had rotted, not a growth trend - and it is now ratcheted, so every later line has to be paid at the moment it is incurred. The +8 is the smallest number that leaves the block honest AND self-documenting; +6 (B) is the smallest that leaves it merely honest. If the ceiling must hold at 961, D is the honest choice, not C - but D needs the published-list paragraph rewritten deliberately, which is a maintainer call." } ], "out_of_scope_findings": [ "filed as #11909: the cross-package gate publishes its PATH spellings in AGENTS.md but nothing at all for RECOGNISED_IMPORT_SPELLINGS - the escaping-import axis is unpublished, and publishing it is one row in the new mirror table plus a line budget that hits the same zero-headroom ceiling (labelled `finding`, unassigned, no pm:queue)" ] }
Generated by Claude Code
Implementation ACCEPTED and open as governed draft PR #11908 (never flipped ready, never armed, never queued; review requested from @os-zhuang). Verified by content on the branch and on current
origin/main— the full review is on the PR.Both halves of this card landed: the new gate
scripts/check-published-list-mirrors.mjsasserts line-for-line equality between the detector'sRECOGNISED_PATH_SPELLINGSand the blockAGENTS.mdpublishes, locates that block structurally (heading + fence — this card's ownAGENTS.md:96-106reference had already rotted to:92-104by implementation time), and refuses rather than passing empty on any unreadable state.The premise re-measures stronger than when the card was filed: the detector now says
findUp23 times whileAGENTS.mdsays it 0 (control:__dirname2, so the zero is a reading, not a dead grep).⚠️ One thing is NOT resolved and this card is not done until it is.AGENTS.mdis at its shrink-only line ceiling with zero headroom (961/961), so any honest repair of the mirror crosses it; the PR takes it to 969 andcheck:pm-skill-ratchetis red until the ceiling is ruled on. That is a maintainer ruling by construction —scripts/pm/check-skill-line-ratchet.mjs:66requires it quoted verbatim in the raising PR, and:58requires it to have its own card. The dev correctly did not touch the CEILINGS map, and neither will I.Filed as #11910, labelled
needs-user-decision.Blocked-by: #11910 Restart-when: the ceiling ruling lands on #11910 Unlock-action: apply the one-line CEILINGS edit with the ruling quoted, then the PR is mergeableAlso recorded for whoever picks the PR up: its co-tenancy note names #11829 (
AGENTS.md:191, disjoint) but the collision that will actually bite is #11905, which rewriteslint.ymlacross 171 lines and is already in the merge queue. Expect this branch to go dirty onlint.yml, not onAGENTS.md.Spin-off filed unassigned for triage: #11909 — the same detector publishes its path spellings but nothing at all for
RECOGNISED_IMPORT_SPELLINGS, so the escaping-import axis is unpublished. It is one row in the new mirror table plus a line budget that hits this same ceiling.
Generated by Claude Code
Filed by the
domain:devxPM seat (#6023, sessionsession_01DdCnBGcHeufjrq7drTD3wt) as ruling C on PR #10852's open question 1. Unassigned; recording the class, not claiming it.Blocked-by: #11910
session_01UjM2ia8Av1v5NqfqQEQmC6): the implementation is done and reviewed — governed draft PR #11908, ACCEPTed 2026-08-25T00:09Z. What remains is not work:AGENTS.mdis at its shrink-only ceiling with zero headroom (961/961) and the honest mirror repair takes it to 969, socheck:pm-skill-ratchetis red until the ceiling is ruled on. That ruling is #11910, in the maintainer's decision box.Restart-when:the ceiling ruling lands on #11910.Unlock-action:apply the one-line CEILINGS edit with the ruling quoted verbatim, then PR #11908 is mergeable (still human-merge-only — governed surface).(Label corrected from
pm:dispatchedtopm:blockedby the incoming seat: the blocking relationship was recorded in a comment on 2026-08-25T00:09Z but the label was never swapped, so the queue views read this as live dispatch. TheBlocked-by:line is moved into the body here because that is the machine-greppable reverse index the unlock sweep reads — a comment is invisible to it.)The mirror, and that it is unchecked
scripts/check-cross-package-test-inputs.mjsrecognises a fixed set of directory-seed and path spellings. Its own header says the set is "printed in the failure text and mirrored inAGENTS.md" — the published copy lives in the fenced block atAGENTS.md:96-106.Nothing mechanically compares the two. The detector can grow a spelling and the published list can stay short indefinitely; the only signal is a human noticing.
Three rounds, which is what makes it a class
findUpwalk; after PR #10852 it canAGENTS.md:96-106is short by the twofindUpanchor seeds PR #10852 adds⭐ Rounds 1 and 2 share a nastier property, and it is the reason this is worth mechanising rather than re-fixing: in both, the stale sentence was the stated reason for a prohibition. #10163's wrong "only two seeds" line was the justification for "this seed may not change"; #10854's is the same shape. A rotting mirror does not merely misinform — it launders an obsolete rule into a live one.
process.cwd()appears nowhere in that detector") was already false onorigin/mainbefore PR #10852 — it occurs once, in prose at:1346. So the drift is not only "the code moved and the docs lagged"; a published claim was wrong on arrival and nothing caught it.What a remedy looks like
A gate asserting that every entry in the detector's
RECOGNISED_PATH_SPELLINGSappears inAGENTS.md's published block — the same shape as the repo's other mirror ratchets. Cheap, and it retires the class instead of its third instance.AGENTS.mdis governed (scripts/pm/check-governed-merges.mjs:274-280), human-merge-only. A gate that reads it is fine; a fix that edits it is a maintainer merge. So the gate can only ever red — it can never auto-repair — and the card should say so up front rather than discovering it at review.AGENTS.md, or that passes vacuously when the block cannot be located, re-creates the defect it is closing one level up. It needs a self-test with an ablation in both directions: a spelling added to the detector and not toAGENTS.mdmust red, and a mangled/renamed fenced block must refuse rather than pass empty.⛔ Not in scope: widening or narrowing what the detector recognises. This is about keeping the published copy honest, nothing else.
Refs: PR #10852 (#10029 direction B, where this surfaced) · #10854 (round 2, open) · #10163 → #10690 (round 1) · #8995 / #9763 (the widenings that started the drift)