tooling(scripts): widen the ratchet-remedy-authority sweep to scripts/pm/ (#15179) - #15199
Merged
Merged
Conversation
…/pm/ (#15179) The #8435 detector walked one directory — a non-recursive readdir of `scripts/` — so the fourteen `.mjs` files under `scripts/pm/` had never been read, and the cross-file-move exception the 2026-09-03 ruling wrote into this gate's header was documentary for exactly that reason: the ratchet it amends lives one level down. A self-test assertion pinned that absence so a wider walk would red here rather than let the class land in silence. The walk now reads two directories, one level each. The roster was MEASURED first, in a throwaway worktree at the same tree: thirteen of the fourteen files classify `excluded`, and one — `dispatch-gates.mjs` — classified `unmarked`. That one relays `check:type-check-debt`'s shrink-only ratchet remedy to the landing author; its lead already named the maintainer as the owner of the raise in prose, so what it lacked was the token, and it now carries it in that same message. `check-skill-line-ratchet.mjs` — the gate the ruling amends — reads `excluded`: its ceiling-raise remedy names the authority it requires and spells the act outside this file's offer grammar, so the ruled exception governs an act this detector never reaches and no verdict moved when the walk arrived. Both readings are pinned rather than asserted. The declaration is now derived from the walk itself — one flat-directory glob per (walked directory, admitted extension) pair the sweep really opens a file for, held set-equal in both directions — and the self-test assertion that used to demand the narrow walk now pins the two-level one and the reached gate's `excluded` verdict, from the walk, never from a typed count. No new verdict class; not recursive. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
…medy-authority-scripts-pm
This was referenced Sep 4, 2026
os-steve
marked this pull request as ready for review
September 4, 2026 06:15
os-steve
enabled auto-merge
September 4, 2026 06:15
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15179
check-ratchet-remedy-authoritywalked one directory — a non-recursivereaddirSyncofscripts/— so the fourteen.mjsfiles underscripts/pm/had never been read. Thatis why the cross-file-move exception the 2026-09-03 ruling wrote into this gate's header
was documentary: the per-file line ratchet it amends lives one level down. A self-test
assertion pinned the absence so a wider walk would red here rather than let the class
land in silence.
Per the 2026-09-04 ruling (batch #27, #14978 item 3 -> A), the walk now reads
scripts/pm/as well, measurement first.Step 1 — the roster, measured before the walk moved
Measured in a throwaway detached worktree at
origin/mainefb35131, withcorpusFiles()widened toscripts/pm/*.mjsand nothing else changed; the worktree wasrestored (
git diff HEADempty) and removed afterwards.scripts/pm/has no subdirectories (find scripts/pm -mindepth 1 -type dreturnsnothing), so "one level" and the ruling's
scripts/pm/**name the same fourteen filestoday. Corpus size 187 -> 201.
scripts/)pm/bare-root-worklist.mjsexcludedpm/check-clause2-carriers.mjsexcludedpm/check-dispatch-gates.mjsexcludedpm/check-governed-merges.mjsexcludedpm/check-governed-prose.mjsexcludedpm/check-governed-queue-guard.mjsexcludedpm/check-half-states.mjsexcludedpm/check-label-desc-cap.mjsexcludedpm/check-skill-id-lint.mjsexcludedpm/check-skill-line-ratchet.mjsexcludedpm/ci-failure.mjsexcludedpm/dispatch-gates.mjsunmarkedpm/git-history.mjsexcludedpm/release-rehearsal-clone.mjsexcluded#15165(dispatch-gates.mjs) and#15164(bare-root-worklist.mjs) were still in themerge queue when the roster was taken, so both were also measured at their PR heads:
identical verdicts, same single offer. Both have since landed and are merged into this
branch.
The one
unmarkedhit, and its author-facing remedyThe sweep reaches
pm/dispatch-gates.mjsthrough its change-KIND sentence —— whose target resolves to a declared
ROOTconstant, anchoredshrinkby the testimonybeside that constant's other mentions. The registry-growing act the reader is actually
handed sits in the
check:type-check-debtlead rendered under that heading:Step 2 — disposition
pm/dispatch-gates.mjs: MARK, not refuse. Read from its own remedy text, as theruling directs. This file is the dispatch derivation rather than a gate over a registry
of its own, and the offer it hands out is a relay of
check:type-check-debt's ratchetremedy — but it reaches the same author with the same act, and #8435 asks who owns the
act, never which file printed it. Refusal would be untrue: the lead's own prose already
says the raise belongs to a maintainer and never to the landing author, so there is a
legitimate act with a real owner. What was missing was the token. One remedy-text edit,
in that same message:
The prose keeps the lower-case
maintainer-onlydeliberately:dispatch-gates.mjs's ownself-test asserts
/shrink-only/and/maintainer-only/on that rendered line, and thetoken alone (upper case) does not satisfy the second. No behaviour, no verdict line, and
no other
scripts/pmfile changed.pm/check-skill-line-ratchet.mjs:excluded, measured, recorded. Its ceiling remedyalready names the authority it requires — "Raising a ceiling requires a maintainer ruling
quoted in the PR" — and spells the act as a raise, which is not one of
OFFER_VERB'sverbs, so stage 1 finds nothing to judge and stages 2 and 3 are never consulted. The
ruled exception therefore governs an act this detector cannot see, in a gate this
detector now reads. ⛔ No new verdict class stands on that measurement rather than on
the walk having been too narrow to find out.
Control corpus
Two rows, both recorded the way every instance in this file arrived — from the sweep's own
verdict, not from author intent.
pm/dispatch-gates.mjsasmarked(required: it is notexcluded).pm/check-skill-line-ratchet.mjsasexcluded— not required, and addedon purpose: without it, a respelling of that gate's ceiling remedy into the offer grammar
would surface as an UNCLASSIFIED finding with no history, and the header sentence resting
on this reading would already have been false for however long it took someone to notice.
Corpus keys now carry their directory, because
corpusFiles()returns paths relative toscripts/rather than bare filenames.The self-test flip (PR #14860's assertion)
The assertion that pinned
!corpusFiles().includes('check-skill-line-ratchet.mjs')isreplaced by the state that succeeded it, pinned from the walk and never as a typed
count of anything:
corpusFiles()returnspm/check-skill-line-ratchet.mjs, and returns at least onepath under
pm/— so a narrowing back to one directory reds here instead of quietlyrestoring the documentary reading.
excluded, asserted from the sweep — so a respelling ofits remedy reds here and is read and recorded like any other instance.
Battery (21)'s pinned floor rises 2 -> 3 to cover the added case. The header paragraph
that said the exception is documentary now says what is true instead, in comment text
only — the phrase stays out of author-facing strings, which the battery's first
assertion still pins.
Declaration, walk shape and precision
ROOT_DIR_WATCH_HINTSgainsscripts/pm/*.mjs(verified live inhintCovers' own terms:it reaches the fourteen
.mjsfiles, and refusesscripts/pm/ensure-pm-labels.sh,scripts/check-role-word.mjsandscripts/audits/*.mjs).scripts/pm/*.mtsisdeliberately absent — that directory carries no
.mts, and a hint reaching nothing isa dead declaration.
The walk's shape is now a named constant,
CORPUS_DIRS = ['', 'pm'], read one level each.Not recursive, and not
scripts/**: the other eight nested directories underscripts/hold codemods, benchmarks and audits rather than gates. Battery (20) isrewritten around that:
directory, admitted extension) pair the sweep really opens a file for — and held
set-equal to the declared array in both directions, so a third
CORPUS_DIRSentry, or a.mtsappearing underscripts/pm/, reds until its hint is declared;scripts/pm/**isrefused on the same terms as
scripts/**;have answered "unnamed" for every file the new hint covers);
since one nested directory is now read on purpose;
Seven cases, as before; the floor is unchanged at 7.
Reverse verification
Run from the committed implementation, each leg proved on disk before the run and each
restore proved by
git diff HEADempty plus a blob-hash comparison against the HEAD blob(script carried
trap ... EXIT INT TERM):CORPUS_DIRSnarrowed back to['']pm/dispatch-gates.mjs; SET-EQUAL; PRECISE-against-depth; both battery (21) cases) and the run reports 2 STALE control rowsscripts/pm/*.mjshint dropped, walk keptscripts/pm/dispatch-gates.mjsand MISCLASSIFIED against the control rowAfter the third restore the tree is byte-identical to HEAD and both legs are green again.
Gates — all run at head
a8325640Family derived after the commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths); all 22 run, exit code captured beforeany pipe, each gate's own verdict line quoted.
pnpm check:ratchet-remedy-authorityOK check-ratchet-remedy-authority: 201 scripts swept (scripts/*.{mjs,mts} + scripts/pm/*.{mjs,mts}); 13 mark the expanding remedy ⛔ MAINTAINER-ONLY, 6 turn it down outright, 182 hand out no ratchet-expanding remedy. Control corpus: 29 hand-classified scripts, set-equality audited both ways.node scripts/check-ratchet-remedy-authority.mjs --self-testOK self-test: the lexer holds, messages are bounded, both offer word orders and path-named registries are reached, declaration registries are not, the authority token cannot anchor itself, refusal is told apart from discouragement, and the sweep still reaches every known instance.node scripts/pm/bare-root-worklist.mjs --self-testOK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.(…20 records carry a spelling, every one of 14 distinct spellings pinned LIVE, PRECISE and COMPLETE)pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1353 cases pass.pnpm linteslint . --no-inline-config— repo-wide, no findingspnpm check:watch-hint-literal✓ check-watch-hint-literal: 48 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 32 [...] every one an array of quoted literals inside its own statementpnpm check:declared-population-live✓ check:declared-population-live — 200 of 251 famil(ies) declare a path population, and every one of them reaches this tree's 8268 tracked file(s).pnpm check:entry-guard✓ check:entry-guard: 208 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:nul-bytescheck-nul-bytes: OK (scanned 8261 text file(s) [...] no raw ASCII control bytes).pnpm check:parse-guardpnpm check:cli-command-ids✓ check-cli-command-ids: 328 command-id literal(s) across 115 file(s) [...] all resolve to a real command pathpnpm check:agent-test-spellingpnpm check:bash32-floor✓ check-bash32-floor: 26 tracked shell file(s) [...] name no bash 4+ constructpnpm check:cross-package-test-inputsOK: 26 package(s) read outside themselves, all declaredpnpm check:pnpm-filter-targets✓ check:pnpm-filter-targets: 142/181 --filter occurrence(s) [...] resolvepnpm check:refd-timer-probeOK check-refd-timer-probe: 5865 source file(s) sweptnode scripts/check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are coverednode scripts/check-closing-keyword-parity.mjs(+--self-test)check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords…)/✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.node scripts/check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 5870 files, 0 disagree, 0 unparseablenode scripts/check-self-test-wired.mjs(+--self-test)✓ every one of the 167 script(s) CI runs that ship a --self-test has that self-test run by CI./4 live ledger row(s) verified […] every battery at or above its pinned floor.node scripts/check-whole-set-label-write.mjs(+--self-test)✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)No changeset: nothing here is published from any package (
scripts/**only, no.md).Finding, not fixed here
scripts/pm/bare-root-worklist.mjs's rowscripts/check-ratchet-remedy-authority.mjs SCRIPTS_DIR scripts(verdictDECLARED-NARROWER, re-decided 2026-09-01) now describes a narrower walk than the gateperforms: it records the population as the scripts root read one level, set-equal at
"183 of 183", and states that "no nested script at any depth is reached". The second half
is false as of this PR —
scripts/pm/*.mjsis reached and the declared set is three hintsrather than two. Re-deciding a row on that shrink-only map is the owning file's act and
not this card's, so it is reported rather than edited: the gate's own docblock now carries
a pointer to the staleness. Nothing mechanical rests on it —
bare-root-worklist --self-testis green here, because that map pins its recorded spellings against thetree, not against this gate's array.
Generated by Claude Code