Repository navigation
finding: check-role-word.mjs's header says --update expands the baseline "which is the shrink-only direction" — backwards on either reading of the relative clause #10042
Description
Activity
Claim: PM loop round 25
Session:session_01XqDQYVU5smx29ts9pAErja
Branch:claude/issue-10042-shrink-only-direction-prose
Worktree:objectstack-issue-10042
Domain:domain:devx
File surface:scripts/check-role-word.mjs(the--updateheader sentence) (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (--tieron this path returns "no path-derived mandate … This line is a FLOOR, never a clearance"; content limbno— comment prose only)
Clause-②: no
Serial constraints cleared: swept all open PRs for any touchingcheck-role-word— none. Both predecessors on this file are MERGED: PR #9931 (#9910, the per-rootScanned:line and the--updateconfirmation fix) and PR #10044 (#9947, which removed the hardcoded ledger size and left this sentence deliberately untouched as a different defect class).
Generated by Claude Code
- added a commit that references this issue
on Aug 19, 2026 { "issue": 10042, "status": "done", "branch": "claude/issue-10042-shrink-only-direction-prose", "pr": "https://github.com/objectstack-ai/objectstack/pull/10065", "premise_still_valid": true, "summary": "The header sentence at scripts/check-role-word.mjs L29-31 is replaced. Per ruling 1 the rewrite fixes the CONCEPT, not the clause: it states the MECHANICAL fact (--update rewrites the baseline from the current tree and never reads the old ledger, so it moves whichever way the tree moved -- shrinking where the word is gone, EXPANDING where it is new, in one stroke) separately from the POLICY fact (the baseline is shrink-only, so ratcheting down is the author's own remedy while expanding weakens the gate and is a maintainer's call), and closes by saying the marker pins the WORDING, not the act. Comment prose only: git diff contains zero changed non-comment lines; no verdict, population, exit code or message text moves. Nothing PR #10044 settled was touched. NOTE for the PM: the platform appended a SECOND signature footer to the PR body at creation time, so #10065's body ends with the footer twice -- platform behaviour on create, not another actor editing the PR; left alone rather than risk an edit round.", "tests": "All at final commit f2ad79dba3, exit codes captured by redirect-then-$? (never through a pipe). Gate set re-derived AFTER the commit with `node scripts/pm/dispatch-gates.mjs` (no paths passed): 1 path vs merge base 20b9a9ce1, 115 families discovered, 3 matched. It named check:cross-package-test-inputs, which the dispatch prompt did not -- ran it. UNION: (1) check:role-word self-test EXIT=0 -> 'OK self-test: the NEW-use remedy marks baseline expansion as maintainer-only, the predicate rejects an unmarked offer, the ratchet-DOWN remedy stays the author's own, and both success texts report what was READ'; (2) check:role-word run EXIT=0 -> 'check-role-word: OK, no new occurrences of the reserved word. / Scanned: 216 .md/.mdx file(s) read across 2 root(s) -- content/docs 180, skills 36. / Ledger: 43 baselined file(s) still carrying it (129 occurrence(s))'; (3) check:cross-package-test-inputs EXIT=0 -> 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; (4) check:nul-bytes EXIT=0 -> 'check-nul-bytes: OK (scanned 6358 text file(s) -- 6358 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'; (5) check:ratchet-remedy-authority self-test EXIT=0 and run EXIT=0 -> 'OK check-ratchet-remedy-authority: 97 scripts swept (scripts/*.{mjs,mts}); 6 mark the expanding remedy MAINTAINER-ONLY, 3 turn it down outright, 88 hand out no ratchet-expanding remedy. Control corpus: 18 hand-classified scripts, set-equality audited both ways.' REVERSE VERIFICATION -- predicted direction: NO CHANGE, because H4's grep found nothing keying on the sentence. Captured check:ratchet-remedy-authority's output before the edit and diffed it against the after run: byte-identical. That is the measurement that matters for a prose edit to a gate whose CONTROL pins it as 'marked' -- the sweep's classification does not read this sentence, and the prediction held. No build/ablation applies (root .mjs scripts, no dist, no package deps -- the heavy-verify lock was never needed and never taken). Extra corroboration for ruling 4: the LIVE scan now reads 216 files (content/docs 180, skills 36) while the self-test fixture pins 215 (179 + 36). The fixture is closed and synthetic and is working exactly as its L295-297 comment says; NOT refreshed.", "open_questions": [], "out_of_scope_findings": [] }H1 —
--update's actual code path (quoted, measured independently)Confirmed, and stronger than inherited:
--updateis not merely also expanding, it is not directional at all.const update = process.argv.includes('--update'); // L45 if (update) { // L374 writeFileSync(BASELINE_PATH, JSON.stringify(current, null, 2) + '\n'); // L375 console.log(updateSummary(scanned, current)); process.exit(0); // L377 — leaves here } const baseline = existsSync(BASELINE_PATH) // L380 — NEVER REACHED on --update ? JSON.parse(readFileSync(BASELINE_PATH, 'utf8')) : {};
Three facts fall out of that ordering:
- It is a whole-file overwrite, not a merge, append or targeted delete —
writeFileSyncofJSON.stringify(current). current(L364-372) is a pure function of the tree walk: every scanned file whose name+body hits exceed zero.- Decisively, the old baseline is never read on this path — the
const baseline = …read sits after the branch'sprocess.exit(0). With no knowledge of the previous contents, the write cannot be "only add" or "only remove" by construction.
So the ledger moves whichever way the tree moved, both directions in the same stroke. PR #10044's finding is confirmed independently — its premise and this card's premise both stand. The file already said this correctly at L70-73 and L127-128; only L29 disagreed.
H2 — is the #8435 marker machine-checked? Yes for the wording, no for the act.
Machine-checked, in two independent places:
- This file's own self-test, assertions (1)-(4) at L257-286, wired into CI as the first half of
check:role-word(package.json:45→.github/workflows/lint.yml:591). It pins the marker's placement innewUseMessage(), and (3) proves the predicate discriminates rather than approving everything. check:ratchet-remedy-authority(lint.yml:1817), whoseCONTROLcorpus pinscheck-role-word.mjsasexpect: 'marked'at L588 — "PR fix(devx): mark ratchet-editing remedies as maintainer-only in three more gates (#8519) #8539. Names its ledger by path, the case a dot-excluding gap class loses." — set-equality audited both ways. Presence there, placement here; the gate's header states that split deliberately.
Not machine-checked: the policy itself. No CODEOWNERS entry covers
scripts/role-word-baseline.json(.github/CODEOWNERSnames only/scripts/pm/check-governed-merges.mjs), there is no growth guard on the baseline, and--updateruns unconditionally at L374 for anyone who passes the flag. The enforcement is entirely textual: the gate is obliged to say the path is a maintainer's; it cannot stop anyone taking it.That is exactly why the corrected sentence ends with "Both pin the WORDING, not the act — the flag takes either direction from whoever runs it", satisfying ruling 1's prohibition on implying enforcement that does not exist.
H3 — sweep of the six
MAINTAINER-ONLYgatesTotal 6 · confused 1 · fixed here 1 · others needing a fix 0.
gate phrasing verdict check-engine-double-contract.mjs"That baseline is shrink-only, so an entry weakens a…" (L1786, L1866, L2827) correct check-type-check-coverage.mjs"Raising the entry weakens a shrink-only ratchet…" (L2140, L2780) correct check-role-word.mjs"expands the baseline, which is the shrink-only direction" (L29) confused — fixed here check-durability-degradation-log-level.mjs"That baseline is shrink-only and hand-edited, so an entry weakens a ratchet" (L2163, L4058) correct check-driver-conformance.mjsno shrink vocabulary at all — reachable only through the governance limb, exactly as its CONTROL entry records n/a check-test-typecheck.mts"which EXPANDS a shrink-only ratchet. The rule saying that is a maintainer's" (L120, L403) correct A repo-wide grep for the literal phrase
shrink-only directionreturns exactly two hits: the defect atcheck-role-word.mjs:29, andcheck-published-readme-exports.mjs:719— "N known instance(s) STILL in <file>carries the shrink-only direction in the word 'still'". That second one is correct and unrelated: it uses "direction" as the ledger's ratchet direction, not as an attribution of expansion to the shrink direction. Nothing to fix there, and it is outside the marked six in any case.The five correct gates all use the same shape — the LEDGER is shrink-only (a governance property of the registry) + expanding WEAKENS it + so it is a maintainer's. That shape is what the new sentence adopts.
H4 — nothing mechanical depends on the sentence
$ grep -rn "expands the baseline" --include="*.mjs" --include="*.mts" --include="*.ts" \ --include="*.json" --include="*.md" --include="*.mdx" --include="*.yml" . | grep -v node_modules ./scripts/check-role-word.mjs:29:// `--update` expands the baseline, which is the shrink-only direction of this ./scripts/check-role-word.mjs:64:// This gate's second remedy is `--update`, which expands the baseline. That isTwo hits, both inside the file itself — L29 the defect, L64 the correct statement. No gate, self-test, fixture or workflow keys on the header's words. Of the files that read
check-role-word.mjsat all, onlycheck-ratchet-remedy-authority.mjsreads its source text, and the before/after byte-identical sweep output above is the empirical confirmation that its classification does not depend on this sentence. The fix has no mechanical half.Ruling 2 — the sites converged on, and a refinement
The card named two. Both check out, and they agree with each other (line numbers at
origin/main20b9a9c):- L64-66, the
check:engine-double-contractfires at CI time, not authoring time — four independent PRs tripped it on brand-new test files in one shift, and a pre-warning in the brief did not prevent it #8435 block: "This gate's second remedy is--update, which expands the baseline. That is a shrink-only ratchet, so taking that path WEAKENS the gate" - L128-130,
newUseMessage(): "The baseline is shrink-only, so this weakens a ratchet and needs a maintainer to agree the boundary is genuine first"
Refinement rather than a disagreement: the file states it correctly four times, not twice. Two further sites the card did not name —
- L261-263, the self-test's own label: "the baseline is shrink-only, so running --update is a maintainer action, not the author's second option"
- L156-163, the finding: check-role-word's green line counts only the LEDGER, so when the debt is finally paid a total scan failure and a clean repo print the same OK #9910 block: "a ceiling rots too, because the same
--updateis also the baseline-EXPANDING path thecheck:engine-double-contractfires at CI time, not authoring time — four independent PRs tripped it on brand-new test files in one shift, and a pre-warning in the brief did not prevent it #8435 marker above gates" — the one site that already stated the bidirectionality outright
All four agree. The header was the sole outlier out of five statements, which strengthens rather than complicates the card.
Rulings 3-6
- Ruling 3 (comment prose only) — held. Zero changed non-comment lines; no behaviour change was needed, so no STOP condition arose.
- Ruling 4 (do not touch what docs(gate): rest check-role-word's comment figures on invariants, not on the ledger's current size #10044 settled) — held. The
Ngeneralisation, the cardinality-identity sentence, the legitimate-KINDS rule and the synthetic fixtures are untouched. Corroborating evidence that the 215 pin is doing its job: the live tree now scans 216 (content/docs 180 + skills 36) against the fixture's 215 (179 + 36). The gap is the closed fixture behaving exactly as L295-297 says it must — not drift. - Ruling 5 (governed surfaces) — held. One file changed:
scripts/check-role-word.mjs. - Ruling 6 (no changeset) — held.
skip-changesetapplied instead, written as the read-then-union set and read back:size/s+skip-changeset. PR left draft; auto-merge not enabled.
Out-of-scope findings: none. One near-miss checked and dismissed —
check-ratchet-remedy-authorityreports an 18-entry control corpus while quoted keys number 17; the 18th is the computed[SELF_FILE]key at L666. No discrepancy, nothing to file.Generated by Claude Code
Generated by Claude Code
- It is a whole-file overwrite, not a merge, append or targeted delete —
- added a commit that references this issue
on Aug 23, 2026
Found while working #9947 (comment-figure cleanup in the same file). Not fixed there: different defect class (garbled prose, not a self-invalidated figure), so it failed the bounded in-place exemption's same-class condition.
The sentence
scripts/check-role-word.mjs, header block:The relative clause has no correct antecedent:
which= the baseline → "the baseline, which is the shrink-only direction of this ratchet" — a baseline is not a direction;which= expanding the baseline → factually backwards. Expanding is the weakening direction; shrink-only is the direction the ratchet guarantees.Why it is worth fixing rather than tolerating
This is garbled about precisely the fact the
⛔ MAINTAINER-ONLYconvention exists to make unmistakable — which way the privileged path moves the ratchet. The same file states it correctly twice elsewhere, so the header is the odd one out:newUseMessage(): "The baseline is shrink-only, so this weakens a ratchet and needs a maintainer to agree the boundary is genuine first"check:engine-double-contractfires at CI time, not authoring time — four independent PRs tripped it on brand-new test files in one shift, and a pre-warning in the brief did not prevent it #8435 block: "--update, which expands the baseline. That is a shrink-only ratchet, so taking that path WEAKENS the gate"Scope
Comment prose in
scripts/check-role-word.mjsonly — no verdict, exit code or population change. Likely a one-sentence rewrite along the lines the two sites above already use.Ref: #9947 is where this surfaced and is not addressed by it.