Ratchet AGENTS.md's published spellings list to the detector's constant - #11908
Conversation
…tant `scripts/check-cross-package-test-inputs.mjs` is a source scan: a path spelling it does not know produces no flag, so a test whose reads escape its package goes undeclared silently. That is why its recognised set is published in AGENTS.md rather than left in the implementation -- and nothing compared the two copies. The mirror drifted three times (#10163, #10854, and this one). Twice the stale line was the stated REASON FOR A PROHIBITION, so a rotting mirror does not merely misinform: it launders an obsolete rule into a live one. And one claim was already false BEFORE the PR that supposedly staled it, so a lag-only check would not have caught it either. Adds `scripts/check-published-list-mirrors.mjs`, which asserts line-for-line EQUALITY between a declared constant and the block a document publishes: - equality, not containment -- a comment-only drift is invisible to containment, and the comments are where the prohibitions live; - the block is located by heading + fence, never by line number (this card was filed against `AGENTS.md:96-106`; the block sat at `:92-104` three days later); - every unreadable state REFUSES: renamed heading, duplicate heading, re-tagged fence, unterminated fence, empty block, two candidate fences, and a constant that is missing, renamed, empty or not a string list; - it can only ever go RED. AGENTS.md is governed, human-merge-only, so the gate never repairs -- it prints the exact block to paste. Stated in its header. Repairs the published block in the same change: it was short by 13 of the constant's 24 lines, including the two `findUp` ANCHOR seeds PR #10852 added, and the lead-in prose named only two of the three seed kinds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
Two standing rulings on AGENTS.md govern what a mirror may put there, and a byte-identical copy of the whole constant breaks one of them outright: - the shrink-only line ceiling (`check-skill-line-ratchet`), and - "operative text carries lessons self-contained ... numbers go" (maintainer ruling 2026-08-12, `check-skill-id-lint`). The constant is the text of a CLI failure box: spellings (code, each with the comment that annotates it) plus free-standing NOTE PARAGRAPHS set off by blank entries, two of which carry issue-ID citations. Publishing all 24 lines put those 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. So the mirror judges a projection with no judgement in it: an entry is PUBLISHED unless it is blank, or it is a comment line that no published entry directly precedes. A comment continuing a spelling travels with that spelling; a note paragraph standing alone stays in the failure box. Every spelling is code, so no spelling can hide from it. 24 entries project to 17 published lines, zero issue-ID citations, and the published block grows 11 -> 17: the two `findUp` anchor seeds, the ⛔ manifest prohibition that qualifies them, and the `-> repo root` annotation. A self-test case now pins the citation property against the live constant, so an issue number added to a spelling line goes red HERE, naming the ruling, instead of landing in AGENTS.md. check:pm-skill-id-lint: 22 file(s) clean. check:pm-skill-ratchet is red by 8 lines (969 vs 961) and needs a maintainer ruling -- see the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
|
ACCEPT on the implementation; the ceiling is escalated, not decided here. Verified against the branch and current What I confirmedGoverned discipline held. The CEILINGS map is untouched — structurally, not by assertion. The diff is 4 files ( "It can only ever go RED" — I checked this rather than taking it. A raw grep for Structural location, confirmed in code ( The drift premise re-measured on current
The detector moved by one while this sat in review, which sharpens the card rather than weakening it. The control makes the zero a reading. On your branch The zero-headroom measurement, re-derived independently. I did not re-run your greedy rewrap; I measured the raw line widths of the five paragraphs against the 90-column target: The projection is pinned against the live constant, not against a fixture (
|
…iner ruling The one-line follow-up PR #11908's body pre-wrote: the published-spellings mirror re-sync costs +8 lines, lossless rewrap headroom measured 0, and the mirror is now mechanically enforced. Maintainer ruling 2026-08-25, verbatim: 「同意,帮我合并,然后继续」 (option A as presented in the PM-chat batch review; provenance on PR #11908). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
Maintainer ruling recorded — Option A: ceiling 961 → 969 — and merging on instructionMaintainer ruling (2026-08-25, PM chat session; this PR was presented in the batch-1 governed review with option A as the recommendation, and the maintainer accepted and ordered the merges verbatim: 「同意,帮我合并,然后继续」). Recorded by PM session Executed: commit Merge path: repository rules require the merge queue (direct merge returns 405), so per the same instruction this PR is flipped ready with auto-merge armed — the queue takes it the moment the re-run is green. This comment is the audit record for the governed-merges checklist. Generated by Claude Code |
|
Maintainer ruling, 2026-08-25 (option C1): 「我看到了,你分析过了,接受你的建议」 — the +8-line-scale content for the spellings-mirror ratchet is approved in principle, but this PR needs a rework before it can land:
Please redo on top of current Generated by Claude Code |
Resolution: TAKE MAIN as the base everywhere. The only conflict was scripts/pm/check-skill-line-ratchet.mjs, where this branch's obsolete `['AGENTS.md', 961] -> 969` edit collided with main's re-pinned `['AGENTS.md', 1150]`. Main's map wins wholesale, which dissolves commit d41ef56 -- that raise was measured against the pre-reflow layout and its 961 baseline no longer exists. AGENTS.md, lint.yml and check-cross-package-test-inputs.mjs auto-merged: the 120-byte reflow left the spellings section untouched (its first change after line 32 lands at 128), and the lint.yml rewrite left the gate step beside its sibling, "Hand-written declaration mirrors". The re-measured ceiling lands in the next commit.
…ut, 1150 -> 1158 The branch's original raise (961 -> 969) was measured against the pre-#11948 layout and was dissolved by the merge. Re-measured on current main: AGENTS.md is 1150 there and 1158 with the mirror re-sync applied, so the honest cost is again +8 -- published block 11 -> 17 lines, prose paragraph 4 -> 6. The maintainer's option-C1 ruling is quoted verbatim and untranslated beside the value, per the map's convention. Gate reads at the new value: check-skill-line-ratchet: AGENTS.md is 1158 lines (ceiling 1158; headroom 0). check-skill-line-ratchet self-test: 71 cases pass.
{
"issue": 10855,
"status": "done",
"branch": "claude/issue-10855-spellings-mirror-ratchet",
"pr": "https://github.com/objectstack-ai/objectstack/pull/11908",
"premise_still_valid": true,
"summary": "REWORK of PR #11908 per the maintainer's C1 ruling, executed on top of current origin/main (9abe4e48). Merged origin/main into the branch with merge commits only (never a rebase, never a force-push); the sole conflict was scripts/pm/check-skill-line-ratchet.mjs, resolved by taking main wholesale, which dissolves commit d41ef56's obsolete 961->969 raise. AGENTS.md, lint.yml and check-cross-package-test-inputs.mjs auto-merged cleanly: #11948's 120-byte reflow left the spellings section untouched (its first change after line 32 lands at 128), and #11905's lint.yml rewrite left the gate step beside its sibling 'Hand-written declaration mirrors'. Re-measured the ceiling on the reflowed layout rather than carrying the old number: main is 1150 (read at merge time via git show origin/main, never assumed) and 1158 with the re-sync applied, so the cost is again +8 (block 11->17 lines, prose 4->6); CEILINGS now reads ['AGENTS.md', 1158] with the C1 ruling quoted verbatim and untranslated beside it per the map's convention, and the same quote is in the PR body because check-skill-line-ratchet.mjs:66 requires the raising ruling to be quoted THERE. The published block was NOT copied from the branch: the mirror gate is the oracle, and it holds the block byte-identical to TODAY's constant. Two side effects to flag: (1) I updated the PR body, which was materially stale (it still presented 961->969 as an open decision and said the PR was never armed) and which the ratchet rule requires to carry the ruling; (2) my write-route probe pushed a throwaway ref claude/probe-10855-writeroute and the DELETE is policy-denied in this container (HTTP 403 from the egress proxy on every ref deletion, 3 attempts, not retried further) — the dangling branch points at the old head d41ef56b, has no PR, and needs someone with delete rights to remove it. I did not touch draft state, auto-merge, assignees or labels.",
"tests": "All verdict lines below are each gate's OWN output; every exit code was captured before any pipe (cmd > log 2>&1; E=$?), never a bare $? after a tail. Gate families derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` on the real change set — the assertion held ('--repo checked against this checkout's origin remote — it holds'), change set 5 paths, three-dot semantics vs merge base 9abe4e482, no hand-built path list. 28 of the 29 derived families green at final HEAD 97d431da. Key verdicts: 'All 37 self-test cases passed.' / 'OK: 1 published list mirror(s) match their constants line for line.' / 'check-skill-line-ratchet: AGENTS.md is 1158 lines (ceiling 1158; headroom 0).' / 'check-skill-line-ratchet self-test: 71 cases pass.' / 'check-skill-id-lint: 22 file(s) clean (pattern /#[0-9]{3,}/g).' / 'check-governed-merges --self-test: 129 assertions'. pnpm lint (whole repo, eslint . --no-inline-config) green in 75s, exit 0 — a measurement, not a declared narrowing. The one non-green family is check:type-check-debt, the same environmental refusal declared before the rework: it fail-closed refuses to --re-measure without the workspace build closure on disk ('56 workspace dependenc(ies) ... have no built type entry point'), which lint.yml builds before that step, and this diff contains zero TypeScript; its sibling check:type-check-coverage runs the same script and is green. NON-VACUITY, re-run on the reworked tree — each leg mutated, PROVED on disk (anchored grep -c on the exact injected/deleted text plus a sha256 change, never an editor exit code), ran, restored, and proved restored by sha match, all under trap '<restore>' EXIT INT TERM. No rebuild was required or performed and the reason is structural, not an omission: both gates read the SOURCE files directly (check-published-list-mirrors imports scripts/check-cross-package-test-inputs.mjs by path and readFileSync's AGENTS.md), so no dist/ copy exists that could hold a stale mutant. Legs: A0 control -> mirror OK, ratchet 1158/1158. L1 lengthened one AGENTS.md prose line in place to 138B holding the line count at 1158 -> ratchet RED '1 line(s) over the 120-byte budget: L127 (138B)'. M1 deleted the REPO findUp seed from the published block -> mirror FAIL lines 14/15/16. M2 added a spelling to the constant -> mirror FAIL 'line 18: in the constant but NOT PUBLISHED'. L1 is load-bearing for the ceiling claim: the 120-byte rule prints nothing when green, so a silent run would NOT have been evidence it judges this file — L1 turns 'my re-applied lines are all <=91 bytes' into a measurement instead of a silence. ADVERSARIAL RE-READ of the merge resolution: git diff origin/main...HEAD is exactly 5 files (+555/-14, matching the PR's own additions/deletions on read-back), merge-base(origin/main, HEAD) == origin/main so the branch fully contains main, and all 14 deleted lines were enumerated and accounted for (13 stale AGENTS.md lines + the old ceiling row) — nothing from the dead layout. Controls for the three sibling AGENTS.md edits all present and none appearing as a removal: #11934's 'git fetch origin main &&' worktree-recipe prefix, #11829's version-PR schedule sentence, the shared-ref-namespaces bullet. Also verified: exactly ONE closing keyword in the PR body (Fixes #10855) and zero card-relation trailers in either commit message (squash-safe), and no raw control bytes in any touched file.",
"open_questions": [],
"out_of_scope_findings": []
}Notes for the PM, outside the machine-checkable fields:
Generated by Claude Code Generated by Claude Code |
|
os-dev-report
{
"issue": 10855,
"status": "done",
"branch": "claude/issue-10855-spellings-mirror-ratchet",
"pr": "https://github.com/objectstack-ai/objectstack/pull/11908",
"premise_still_valid": true,
"summary": "REWORK of PR #11908 per the maintainer's C1 ruling, executed on top of current origin/main (9abe4e48). Merged origin/main into the branch with merge commits only (never a rebase, never a force-push); the sole conflict was scripts/pm/check-skill-line-ratchet.mjs, resolved by taking main wholesale, which dissolves commit d41ef56's obsolete 961 to 969 raise. AGENTS.md, lint.yml and check-cross-package-test-inputs.mjs auto-merged cleanly: #11948's 120-byte reflow left the spellings section untouched (its first change after line 32 lands at 128), and #11905's lint.yml rewrite left the gate step beside its sibling 'Hand-written declaration mirrors'. Re-measured the ceiling on the reflowed layout rather than carrying the old number: main is 1150 (read at merge time via git show origin/main, never assumed) and 1158 with the re-sync applied, so the cost is again +8 (block 11 to 17 lines, prose 4 to 6); CEILINGS now reads ['AGENTS.md', 1158] with the C1 ruling quoted verbatim and untranslated beside it per the map's convention, and the same quote is in the PR body because check-skill-line-ratchet.mjs:66 requires the raising ruling to be quoted THERE. The published block was NOT copied from the branch: the mirror gate is the oracle, and it holds the block byte-identical to TODAY's constant. Two side effects to flag: (1) I updated the PR body, which was materially stale (it still presented 961 to 969 as an open decision and said the PR was never armed) and which the ratchet rule requires to carry the ruling; (2) my write-route probe pushed a throwaway ref claude/probe-10855-writeroute and the DELETE is policy-denied in this container (HTTP 403 from the egress proxy on every ref deletion, 3 attempts, not retried further) - the dangling branch points at the old head d41ef56b, has no PR, and needs someone with delete rights to remove it. I did not touch draft state, auto-merge, assignees or labels.",
"tests": "All verdict lines below are each gate's OWN output; every exit code was captured before any pipe (redirect to a log, then E=$?), never a bare $? after a tail. Gate families derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` on the real change set - the assertion held ('--repo checked against this checkout's origin remote - it holds'), change set 5 paths, three-dot semantics vs merge base 9abe4e482, no hand-built path list. 28 of the 29 derived families green at final HEAD 97d431da. Key verdicts: 'All 37 self-test cases passed.' / 'OK: 1 published list mirror(s) match their constants line for line.' / 'check-skill-line-ratchet: AGENTS.md is 1158 lines (ceiling 1158; headroom 0).' / 'check-skill-line-ratchet self-test: 71 cases pass.' / 'check-skill-id-lint: 22 file(s) clean (pattern /#[0-9]{3,}/g).' / 'check-governed-merges --self-test: 129 assertions'. pnpm lint (whole repo, eslint . --no-inline-config) green in 75s, exit 0 - a measurement, not a declared narrowing. The one non-green family is check:type-check-debt, the same environmental refusal declared before the rework: it fail-closed refuses to --re-measure without the workspace build closure on disk ('56 workspace dependenc(ies) ... have no built type entry point'), which lint.yml builds before that step, and this diff contains zero TypeScript; its sibling check:type-check-coverage runs the same script and is green. NON-VACUITY, re-run on the reworked tree - each leg mutated, PROVED on disk (anchored grep -c on the exact injected/deleted text plus a sha256 change, never an editor exit code), ran, restored, and proved restored by sha match, all under a trap on EXIT INT TERM. No rebuild was required or performed and the reason is structural, not an omission: both gates read the SOURCE files directly (check-published-list-mirrors imports scripts/check-cross-package-test-inputs.mjs by path and readFileSync's AGENTS.md), so no dist/ copy exists that could hold a stale mutant. Legs: A0 control gives mirror OK and ratchet 1158/1158. L1 lengthened one AGENTS.md prose line in place to 138B holding the line count at 1158, giving ratchet RED '1 line(s) over the 120-byte budget: L127 (138B)'. M1 deleted the REPO findUp seed from the published block, giving mirror FAIL lines 14/15/16. M2 added a spelling to the constant, giving mirror FAIL 'line 18: in the constant but NOT PUBLISHED'. L1 is load-bearing for the ceiling claim: the 120-byte rule prints nothing when green, so a silent run would NOT have been evidence it judges this file - L1 turns 'my re-applied lines are all at most 91 bytes' into a measurement instead of a silence. ADVERSARIAL RE-READ of the merge resolution: git diff origin/main...HEAD is exactly 5 files (+555/-14, matching the PR's own additions/deletions on read-back), merge-base(origin/main, HEAD) equals origin/main so the branch fully contains main, and all 14 deleted lines were enumerated and accounted for (13 stale AGENTS.md lines plus the old ceiling row) - nothing from the dead layout. Controls for the three sibling AGENTS.md edits all present and none appearing as a removal: #11934's 'git fetch origin main' worktree-recipe prefix, #11829's version-PR schedule sentence, the shared-ref-namespaces bullet. Also verified: exactly ONE closing keyword in the PR body (Fixes #10855) and zero card-relation trailers in either commit message (squash-safe), and no raw control bytes in any touched file.",
"open_questions": [],
"out_of_scope_findings": []
}Notes for the PM, outside the machine-checkable fields:
Generated by Claude Code Generated by Claude Code |
Fixes #10855
⛔ GOVERNED SURFACE. This PR edits
AGENTS.md, which is human-merge-only(
scripts/pm/check-governed-merges.mjs). Its ready/auto-merge state was set by the PMseat on the maintainer's merge instruction (comment 5409341735) — ⛔ I have not touched
draft state or auto-merge in this rework, and no one else should flip them back.
Status: REWORKED onto current
origin/main(2026-08-25)The maintainer ruled option C1 on this PR — verbatim and untranslated:
— approving the +8-line-scale content in principle, with a rework required because
AGENTS.mdhad since been wholesale-reflowed to the 120-byte-wrapped layout (#11948, thenedited by #12103, #11934, #11829). The branch's diff and its old
961 → 969ask were bothmeasured against a layout that no longer exists.
This branch now carries
origin/mainmerged in (⛔ never a rebase, never a force-push — itcarries other authors' commits), with the resolution taking main as the base everywhere
and this PR's semantic content re-applied on top.
What the rework changed
AGENTS.mdbaselineThe cost re-measures to the same +8 on the reflowed layout — published block 11 → 17 lines,
prose paragraph 4 → 6 — so commit
d41ef56's961 → 969is dissolved by the merge andreplaced by a
1150 → 1158entry carrying the C1 ruling verbatim, per the map's convention.⭐ The mirror gate is its own oracle here. The 00:08Z review re-measured the detector and
found the constant had already moved (22 → 23
findUpoccurrences) while this sat in review.Rather than trust the branch's copy of the block, the re-applied block is held byte-identical
to today's constant by the gate itself —
node scripts/check-published-list-mirrors.mjsgreen is the proof that the published copy matches what the detector recognises right now.
What was wrong
scripts/check-cross-package-test-inputs.mjsis a source scan: a path spelling it does notknow produces no flag, so a test whose reads escape its package goes undeclared silently.
That is why its recognised set is published in
AGENTS.mdrather than left in theimplementation — and nothing compared the two copies. The published block was short by the
two
findUpanchor seeds, the ⛔ manifest-name prohibition that qualifies them, and the-> repo rootannotation.⭐ Twice before, the stale line over there was the stated reason for a prohibition
(#10163, #10854). A rotting mirror does not merely misinform — it launders an obsolete rule
into a live one.
The card's line reference had already rotted: it names
AGENTS.md:96-106; the block sat at:92-104three days later, and at:93-109today. The gate therefore locates the blockstructurally — by its heading and fence — and never by line number.
The gate
scripts/check-published-list-mirrors.mjs, wired intolint.ymlbeside its closest sibling(Hand-written declaration mirrors — the post-#11905 rewrite left that neighbour intact),
--self-testfirst (37 cases).a spelling authors are told is recognised while the scanner is blind to it. Containment
cannot see it, and cannot see a comment-only drift either — and the comments are where the
prohibitions live.
each with its annotating comment) plus free-standing NOTE PARAGRAPHS set off by blank
entries. An entry is PUBLISHED unless it is blank, or a comment line that no published entry
directly precedes. Every spelling is code, so no spelling can hide from it.
⭐ Publishing all 24 entries verbatim would put the note paragraphs' issue-ID citations into
AGENTS.mdand turncheck:pm-skill-id-lintred — the gate meant to keep the documenthonest would have broken the document's own prose standard (maintainer ruling 2026-08-12).
A self-test case pins that property against the live constant.
fence, empty block, two candidate fences, and on the code side a constant that is missing,
renamed, empty, or not a string list.
AGENTS.mdis governed, so the gate never repairs; itprints the exact block to paste. The module imports
readFileSync, existsSyncand nowrite API at all.
Non-vacuity, re-run on the reworked tree
Every leg: mutate → prove it landed on disk (anchored
grep -c+ sha change) → run the gate→ restore → prove the sha is back, all under a
trap ... EXIT INT TERM. No build/dist stepis involved and none is needed: both gates read the source files directly
(
check-published-list-mirrorsimportsscripts/check-cross-package-test-inputs.mjsby pathand
readFileSyncsAGENTS.md), so there is nodist/copy that could go stale.AGENTS.mdprose line lengthened in place to 138B (line count held at 1158)1 line(s) over the 120-byte budget: L127 (138B)REPOfindUpseed deleted from the published blockin the constant but NOT PUBLISHED⭐ L1 is the leg that matters for the ceiling claim. The 120-byte rule prints nothing when
green, so a silent run is not evidence it judged this file. L1 makes the cap demonstrably
reach
AGENTS.mdprose, which is what turns "my re-applied lines are ≤91 bytes" into ameasurement instead of a silence.
The ceiling, now ruled
scripts/pm/check-skill-line-ratchet.mjs:66— "RAISING one requires a maintainer rulingquoted in the raising PR's body". The C1 ruling quoted at the top of this body is that
ruling; it is reproduced verbatim and untranslated beside the value in the map, per the
convention the
958 → 961and1149 → 1150entries already follow.AGENTS.mdsits at its ceiling with zero headroom by construction, and lossless rewrapheadroom across the section measures 0 — so the re-sync cannot be paid in place without
deleting load-bearing prose. From here the mirror is mechanically enforced, so any later
drift is priced at the moment it is incurred rather than accruing silently.
Verification
Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon thereal change set (5 paths, three-dot semantics, repo assertion held) — never a hand-built path
list. 28 of the 29 derived families green at
97d431da, exit codes captured before anypipe, each verdict line the gate's own:
pnpm lint— the whole repo,eslint . --no-inline-config— green in 75s, so therepo-wide scan is a measurement here rather than a declared narrowing.
check:type-check-debt, and it is the same environmentalrefusal this PR declared before the rework, not a regression from the diff: it refuses to
--re-measurewithout the workspace build closure on disk ("56 workspace dependenc(ies) ofthe ledgered packages have no built type entry point"), which
lint.ymlbuilds before thatstep. It is a deliberate fail-closed refusal — measuring from here would "silently measure a
DIFFERENT WORLD" — and this diff contains no TypeScript at all. Declared, not skipped.
Its sibling
check:type-check-coverageruns the same script and is green.Merge resolution re-read adversarially.
git diff origin/main...HEADis exactly the fivefiles above, and every one of its 14 deleted lines is accounted for (13 stale
AGENTS.mdlines + the old ceiling row) — nothing from the dead layout, no reversion of the sibling
AGENTS.md edits. Controls for all three, each confirmed present and absent from the removals:
#11934's
git fetch origin main &&worktree-recipe prefix, #11829's version-PR schedulesentence, and the shared-ref-namespaces bullet.
Co-tenancy
The
lint.ymlhazard the review named (#11905's 95/76-line rewrite) landed and is merged inhere; the gate step sits beside Hand-written declaration mirrors as intended.
AGENTS.mdco-tenants #11829 / #11934 / #12103 are all merged and preserved.
No changeset: this diff publishes nothing (scripts, workflow, and the instruction file) —
skip-changesetis on the PR.Generated by Claude Code