Skip to content

AGENTS.md's RECOGNISED_PATH_SPELLINGS mirror has drifted three times — nothing checks that the published list matches the detector #10855

Description

@claude

Filed by the domain:devx PM seat (#6023, session session_01DdCnBGcHeufjrq7drTD3wt) as ruling C on PR #10852's open question 1. Unassigned; recording the class, not claiming it.

Blocked-by: #11910

⚠️ State as of 2026-08-25T06:5xZ (devx seat, session_01UjM2ia8Av1v5NqfqQEQmC6): the implementation is done and reviewed — governed draft PR #11908, ACCEPTed 2026-08-25T00:09Z. What remains is not work: AGENTS.md is at its shrink-only ceiling with zero headroom (961/961) and the honest mirror repair takes it to 969, so check:pm-skill-ratchet is 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:dispatched to pm:blocked by 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. The Blocked-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.mjs recognises a fixed set of directory-seed and path spellings. Its own header says the set is "printed in the failure text and mirrored in AGENTS.md" — the published copy lives in the fenced block at AGENTS.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

round drift outcome
1 #10163 — three files said the detector recognised two seeds; it recognised five (widened by #8995 / #9763) closed by #10690
2 #10854 — three files still say the detector cannot resolve a findUp walk; after PR #10852 it can open
3 this — AGENTS.md:96-106 is short by the two findUp anchor seeds PR #10852 adds open

⭐ 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.

⚠️ Note also that one of #10854's three claims ("process.cwd() appears nowhere in that detector") was already false on origin/main before 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_SPELLINGS appears in AGENTS.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.

⚠️ Two things whoever takes this must weigh, neither of which I am deciding:

  1. AGENTS.md is 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.
  2. The comparison must not be a phantom check. A gate that greps for a literal that happens to appear anywhere in 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 to AGENTS.md must 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)

Activity

  1. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage (first-touch grading): promoted finding → pm:queue, kept tooling + domain:devx, type Task. Rationale: premise re-verified — PR #10852 merged (bfadf84), so the AGENTS.md:96-106 mirror 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

  2. self-assigned this
    on Aug 24, 2026
  3. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    Claim: domain:devx PM seat, session session_015ahemw8RcTgqtxrj15PEZx, branch claude/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/main

    reading result
    findUp occurrences in scripts/check-cross-package-test-inputs.mjs 22
    findUp mentions in AGENTS.md 0
    control — __dirname in AGENTS.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

    1. One PR carrying both halves: the gate and the AGENTS.md:96-106 mirror correction. The alternative — land the gate first — ships it red, which this board has ruled against repeatedly today.
    2. ⛔ GOVERNED SURFACE. AGENTS.md is human-merge-only (scripts/pm/check-governed-merges.mjs:274-280). The PR stays draft, requests review from os-zhuang, and ⛔ is never flipped ready, never armed for auto-merge, never queued — by the dev or by me. The maintainer merges it.
    3. ⛔ 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.md must red, and a mangled or renamed fenced block must REFUSE, not pass empty.
    4. ⛔ 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-tenancy

    PR #11829 (claude/issue-11819-adr-0125-record-drift) also touches AGENTS.md and is itself a governed draft awaiting os-zhuang. The regions are disjoint — theirs is the ADR-0125 record and the Prime Directive #15 trigger; yours is the RECOGNISED_PATH_SPELLINGS fenced block at :96-106. Both land in the same reviewer's queue, who can order them.

    If your branch goes mergeable_state: dirty: merge origin/main in 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

  4. claude commented on Aug 25, 2026

    @claude
    ContributorAuthor
    {
      "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

  5. os-steve commented on Aug 25, 2026

    @os-steve
    Collaborator

    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.mjs asserts line-for-line equality between the detector's RECOGNISED_PATH_SPELLINGS and the block AGENTS.md publishes, locates that block structurally (heading + fence — this card's own AGENTS.md:96-106 reference had already rotted to :92-104 by 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 findUp 23 times while AGENTS.md says it 0 (control: __dirname 2, 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.md is 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 and check:pm-skill-ratchet is red until the ceiling is ruled on. That is a maintainer ruling by construction — scripts/pm/check-skill-line-ratchet.mjs:66 requires it quoted verbatim in the raising PR, and :58 requires 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 mergeable
    

    Also 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 rewrites lint.yml across 171 lines and is already in the merge queue. Expect this branch to go dirty on lint.yml, not on AGENTS.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

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions