Repository navigation
finding: check-role-word.mjs hardcodes its own regenerated baseline size (43) in two prose sites — accurate today, wrong on the next --update #9947
Description
Filed by the domain:devx PM seat from PR #9944 (#9915)'s H4 sibling sweep. Named there rather than fixed, per that dispatch's ruling 4.
The defect
scripts/check-role-word.mjs states its own regenerated baseline's size in two prose sites:
- L133 —
check-role-word: OK (43 baselined file(s), no new occurrences). - L144 —
ledger untouched — `43 problem(s)`, exit 1
scripts/role-word-baseline.json holds exactly 43 entries today, so both are accurate right now.
⇒ Latent, not live. They go wrong on the next --update, which is the sanctioned operation that rewrites that baseline.
Why file it while it is still correct
This is the identical state check-engine-double-contract.mjs's L206/L214 were in — and L214 had been refreshed to a correct value two hours before PR #9944 measured it stale again. Refreshing a self-describing number does not fix it; it resets the clock.
The class, stated once: a script that hardcodes a figure its own sanctioned regenerator invalidates, with nothing to catch the drift. --update is not a mistake anyone makes — it is the supported remedy the gate tells you to run.
The fix shape that worked next door
PR #9944 established that run-time derivation is not available for comments — nothing renders a comment, and making --update rewrite the script's own source would be a behaviour change (and would flip Clause-② to yes).
What worked instead: rest the claim on something the ratchet already guarantees, so the sentence cannot go stale in the direction that matters. Its examples:
308 generated rows→ "a census that ALREADY OUTNUMBERS them and, by the opposite polarities below, can only outnumber them further" — monotone-true given grow-only/shrink-only invariants;one problem per census row -- hundreds of them, and growing, since this ledger is grow-only— a floor on a grow-only ledger cannot rot.
check-role-word's baseline is shrink-only (it is debt being paid down), so the polarity is the opposite one and the phrasing must follow its invariant, not that file's. Do not copy the sentence; copy the method.
Scope
scripts/check-role-word.mjsonly. Prose/comment sites.- ⛔ No verdict, exit code or population change — that would flip
Clause-②and bind the entry gate. ⚠️ PR fix(gate): check-role-word's green line reports what it READ, not just the ledger #9931 (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) merged into this file this morning, adding the per-rootScanned:line and a fix to the--updateconfirmation. Read that first: it changed what the success branch prints, so the sample output in these comments may already be stale in a second way the sweep did not test for.
The bound on how this was found
PR #9944's sweep enumerated scripts/ files carrying a --write/--update/--fix mode (11), resolved each one's artifact, computed its array lengths, and grepped the script's comments for those values. Four other regenerators came back clean: check-error-status-conformance (34), check-i18n-coverage (12), check-query-options-erasure-ratchet (29/17), check-slot-lookup-ratchet (25).
Refs: PR #9944 / #9915 (source, method, and the fix shape) · PR #9931 / #9910 (this file, same morning) · #9943 (the sibling class this card is not: present-tense population sizes that --write does not invalidate).
Generated by Claude Code
Activity
claude commentedon Aug 19, 2026
Claim: PM loop round 21
Session:
session_01XqDQYVU5smx29ts9pAErjaBranch:
claude/issue-9947-role-word-baseline-sizeWorktree:
objectstack-issue-9947Domain:
domain:devxFile surface:
scripts/check-role-word.mjs(the two prose sites) (stop on breach; explain in the report)Container & model: S, mode:subagent, model: opus (
node scripts/pm/dispatch-gates.mjs --tier scripts/check-role-word.mjsrun this round: "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 touching
role-word— none. PR #9931 (#9910), which rewrote this file's success branch and its--updateconfirmation, is MERGED — read it first, because it changed what the sample output in these comments is supposed to look like.Generated by Claude Code
docs(gate): rest check-role-word's comment figures on invariants, not…
os-steve commentedon Aug 19, 2026
{ "issue": 9947, "status": "done", "branch": "claude/issue-9947-role-word-baseline-size", "pr": "https://github.com/objectstack-ai/objectstack/pull/10044", "premise_still_valid": true, "summary": "H1: the ledger still holds exactly 43 entries (129 occurrences) on current main, so both named sites were still accurate and this stayed LATENT, not live — severity unchanged. Ruling 1's second axis is FALSIFIED: the sample output is NOT stale, because both named sites were authored BY #9931 itself as a deliberate before/after narrative — the two old-format quotes are past-tense by construction ('The green line used to be, in full'), and the one fragment quoting the NEW format ('0 .md/.mdx file(s) read') is an exact substring of what the gate prints today; verified the old 'OK (' line is genuinely gone from the program. RULING 3 per-site verdicts: site 1 (was L133) is a FORMAT EXAMPLE — the argument it supports ('Every number in it came from the LEDGER') rests on the line's shape, not N's value, and decisively the figure is UNREFRESHABLE because #9931 deleted that line from the program, so after the next --update there is no correct value at all; the number went generic (N). Site 2 (was L144, '43 problem(s)') is a MEASUREMENT — a real ablation result offered as evidence a dead scan fails loudly — so it got ruling 2's treatment, not a refresh. H2 INVARIANT + POLARITY: I rest on the cardinality IDENTITY the ratchet-DOWN loop enforces — it walks Object.entries(baseline) and raises one error per entry missing from current, so a dead scan raises as many problems as the ledger holds, true at 43, at 1 and at 0. I deliberately did NOT use the mirror-image ceiling, and that is the polarity finding: check-role-word's 'shrink-only' is a GOVERNANCE rule, not a mechanical monotone — the same --update flag is also the baseline-EXPANDING path the #8435 MAINTAINER-ONLY marker gates — so the ledger CAN rise and NO bound is durable in either direction (a floor rots on the first sanctioned ratchet-down, the very remedy the gate tells authors to run; a ceiling rots on the maintainer-approved expansion the gate explicitly provides for, and would rest on a policy, which is the one thing a comment cannot verify). Per H2's own escape clause: no monotone magnitude claim is available here, so the magnitude claim was removed rather than re-based. Ruling 4 honoured and proven mechanically: every changed line is a comment, gate output is byte-identical before/after, baseline JSON untouched; Clause-② stays no.", "tests": "All gates on final commit 163d8292d7 (= PR head), quoting each gate's own printed verdict line, exit codes captured before any pipe. Gate set derived by `node scripts/pm/dispatch-gates.mjs` with NO paths (it takes the changeset from the merge base itself): 1 path, 3 families named for scripts/**. (1) `pnpm check:role-word` -> 'OK self-test: the NEW-use remedy marks baseline expansion as maintainer-only... ' and '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))', EXIT_CODE=0. (2) `pnpm check:nul-bytes` -> 'check-nul-bytes: OK (scanned 6358 text file(s) -- 6358 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).', EXIT_CODE=0; plus a manual grep -naP over the edited file, clean. (3) `node scripts/check-cross-package-test-inputs.mjs` -> 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.', EXIT_CODE=0. NO-BEHAVIOUR-CHANGE PROOF (ruling 4), two independent observations: (a) `git diff -U0 | grep -E '^[+-]' | grep -vE '^[+-]\\s*//'` returns NOTHING — every added/removed line is a comment; (b) the gate's stdout captured before the edit and after is BYTE-IDENTICAL (`diff` exit 0), exit 0 both times, and `git status` lists only scripts/check-role-word.mjs so the baseline JSON is untouched. No ablation was run: this change cannot alter behaviour by construction (comments only), so an ablation would have nothing to measure. H1 MUTATION CONFIRMATION (per the card's warning that sed/perl/str.replace report success on zero matches): each replacement asserted an exact match count of 1 and aborted otherwise, and the result was then confirmed by an observation independent of the edit tool — `grep -n '\\b43\\b' scripts/check-role-word.mjs` now exits 1 (no matches remain anywhere in the file), and `git diff --stat` shows the file changed.", "open_questions": [], "out_of_scope_findings": [ "filed as #10042: check-role-word.mjs's header says `--update` expands the baseline 'which is the shrink-only direction of this ratchet' — backwards on either reading of the relative clause (if 'which' = the baseline, a baseline is not a direction; if it = expanding, expanding is the WEAKENING direction). The same file states it correctly twice elsewhere. NOT fixed here: different defect class (garbled prose, not a self-invalidated figure), so it failed the bounded in-place exemption's same-class condition. Unassigned, no labels, left for PM triage.", "H3 (already-stale check, the sibling method's blind spot — reported, deliberately NOT 'corrected'): the self-test scan fixtures read content/docs 179 + skills 36 = 215, while the live tree is 180 + 36 = 216. They are SYNTHETIC fixtures every assertion closes over, so they stay correct however the corpus moves and the 215 regex can never fail for a tree-related reason. Refreshing them to 180/216 would convert a closed fixture into a figure the tree CAN falsify — manufacturing the exact defect this card removes. They are instead pinned as synthetic with a MAINTAINER-ONLY-style note against future refreshing. No issue filed: not a defect.", "H4 census (14 figures/claims audited in the file; 3 self-invalidated by --update; 3 changed, plus the 1 preventive fixture pin above). The third self-invalidated site is the header, which listed the ledger's legitimate contents as an INVENTORY: 'many are legitimate — the better-auth boundary, ARIA `role=` in samples, educational \"formerly roles\" mentions'. Measured against the live baseline: better-auth 14 baselined files, ARIA `role=` ZERO, 'formerly roles' 3. ALREADY STALE — the only two `role=` hits in baselined files are `sys_member.role = ...`, better-auth org-plugin vocabulary, not ARIA. Fixed in this PR per the card's H4 ('an example entry ... belongs in this card's population'), and fixed the ruling-2 way: it now names the legitimate KINDS (a rule about what may be baselined, which --update cannot falsify) rather than taking a census. Claims audited and found SOUND, changed nothing: the three failure conditions vs the code, the scope/ROOTS/SKIP_DIRS description, 'Four assertions' in the #8435 block, 'two non-existent directories' (ROOTS.length 2), the old --update line quote, and the cross-file claim that the #8435 convention landed in check-engine-double-contract.mjs and check-type-check-coverage.mjs (both still carry the marker).", "SCOPE DECLARATION for PM: the claim declared the file surface as 'the two prose sites'; this PR touches FOUR sites in that same file — the two named, plus the H4 header inventory (which the card's H4 pre-authorised into the population) and the H3 fixture pin. Same file, same defect class, same gate family, no new verification surface. Flagged here rather than passed as silent widening." ] }Generated by Claude Code
docs(gate): rest check-role-word's comment figures on invariants, not…