Skip to content

tooling(scripts): widen the ratchet-remedy-authority sweep to scripts/pm/ (#15179) - #15199

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-15179-remedy-authority-scripts-pm
Sep 4, 2026
Merged

tooling(scripts): widen the ratchet-remedy-authority sweep to scripts/pm/ (#15179)#15199
os-steve merged 2 commits into
mainfrom
claude/issue-15179-remedy-authority-scripts-pm

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15179

check-ratchet-remedy-authority walked one directory — a non-recursive readdirSync of
scripts/ — so the fourteen .mjs files under scripts/pm/ had never been read. That
is 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/main efb35131, with
corpusFiles() widened to scripts/pm/*.mjs and nothing else changed; the worktree was
restored (git diff HEAD empty) and removed afterwards.

scripts/pm/ has no subdirectories (find scripts/pm -mindepth 1 -type d returns
nothing), so "one level" and the ruling's scripts/pm/** name the same fourteen files
today. Corpus size 187 -> 201.

# file (relative to scripts/) verdict note
1 pm/bare-root-worklist.mjs excluded PM worklist tool; no offer this grammar reaches
2 pm/check-clause2-carriers.mjs excluded
3 pm/check-dispatch-gates.mjs excluded
4 pm/check-governed-merges.mjs excluded
5 pm/check-governed-prose.mjs excluded
6 pm/check-governed-queue-guard.mjs excluded
7 pm/check-half-states.mjs excluded
8 pm/check-label-desc-cap.mjs excluded
9 pm/check-skill-id-lint.mjs excluded
10 pm/check-skill-line-ratchet.mjs excluded the gate the ruling amends — see below
11 pm/ci-failure.mjs excluded PM tool, not a gate
12 pm/dispatch-gates.mjs unmarked the one violation — see below
13 pm/git-history.mjs excluded PM tool, not a gate
14 pm/release-rehearsal-clone.mjs excluded PM tool, not a gate

#15165 (dispatch-gates.mjs) and #15164 (bare-root-worklist.mjs) were still in the
merge 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 unmarked hit, and its author-facing remedy

The sweep reaches pm/dispatch-gates.mjs through its change-KIND sentence —

adds or edits TypeScript in the ROOT tsc program (outside the directories tsconfig.json excludes)

— whose target resolves to a declared ROOT constant, anchored shrink by the testimony
beside that constant's other mentions. The registry-growing act the reader is actually
handed sits in the check:type-check-debt lead rendered under that heading:

the ROOT ledger entry (@objectstack/spec-monorepo) IS this program, so a file here
moves its raw tsc count even though your diff touches no package [...] It is a
shrink-only ratchet: the repair is to make the file typecheck, and raising the entry is
maintainer-only, never the co-equal option.

Step 2 — disposition

pm/dispatch-gates.mjs: MARK, not refuse. Read from its own remedy text, as the
ruling 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 ratchet
remedy — 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:

-and raising the entry is maintainer-only, never the co-equal option.
+and raising the entry is maintainer-only — ⛔ MAINTAINER-ONLY under the #8435 convention — never the co-equal option.

The prose keeps the lower-case maintainer-only deliberately: dispatch-gates.mjs's own
self-test asserts /shrink-only/ and /maintainer-only/ on that rendered line, and the
token alone (upper case) does not satisfy the second. No behaviour, no verdict line, and
no other scripts/pm file changed.

pm/check-skill-line-ratchet.mjs: excluded, measured, recorded. Its ceiling remedy
already 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's
verbs, 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.mjs as marked (required: it is not
excluded). pm/check-skill-line-ratchet.mjs as excludednot required, and added
on 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 to
scripts/ rather than bare filenames.

The self-test flip (PR #14860's assertion)

The assertion that pinned !corpusFiles().includes('check-skill-line-ratchet.mjs') is
replaced by the state that succeeded it, pinned from the walk and never as a typed
count of anything:

  • corpusFiles() returns pm/check-skill-line-ratchet.mjs, and returns at least one
    path under pm/ — so a narrowing back to one directory reds here instead of quietly
    restoring the documentary reading.
  • the reached gate classifies excluded, asserted from the sweep — so a respelling of
    its 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_HINTS gains scripts/pm/*.mjs (verified live in hintCovers' own terms:
it reaches the fourteen .mjs files, and refuses scripts/pm/ensure-pm-labels.sh,
scripts/check-role-word.mjs and scripts/audits/*.mjs). scripts/pm/*.mts is
deliberately absent — that directory carries no .mts, and a hint reaching nothing is
a 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 under
scripts/ hold codemods, benchmarks and audits rather than gates. Battery (20) is
rewritten around that:

  • the expected hint set is derived from the walk — one flat-directory glob per (walked
    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_DIRS entry, or a
    .mts appearing under scripts/pm/, reds until its hint is declared;
  • the subtree spelling is refused against every walked root, so scripts/pm/** is
    refused on the same terms as scripts/**;
  • the local path matcher takes a multi-segment prefix (the old single-segment class would
    have answered "unnamed" for every file the new hint covers);
  • PRECISE-against-depth is measured against the unwalked directories (29 files today),
    since one nested directory is now read on purpose;
  • PRECISE-against-extension covers both walked directories.

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 HEAD empty plus a blob-hash comparison against the HEAD blob
(script carried trap ... EXIT INT TERM):

ablation expected direction observed
CORPUS_DIRS narrowed back to [''] red self-test 5 failures (positive control loses pm/dispatch-gates.mjs; SET-EQUAL; PRECISE-against-depth; both battery (21) cases) and the run reports 2 STALE control rows
scripts/pm/*.mjs hint dropped, walk kept red self-test 2 failures — SET-EQUAL, and COMPLETE naming 14 unnamed swept files
the authority token stripped from the relayed remedy red run reports UNMARKED on scripts/pm/dispatch-gates.mjs and MISCLASSIFIED against the control row

After the third restore the tree is byte-identical to HEAD and both legs are green again.

Gates — all run at head a8325640

Family derived after the commit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths); all 22 run, exit code captured before
any pipe, each gate's own verdict line quoted.

gate exit verdict line
pnpm check:ratchet-remedy-authority 0 OK 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-test 0 OK 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-test 0 OK 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 0 ✓ dispatch-gates self-test: 1353 cases pass.
pnpm lint 0 eslint . --no-inline-config — repo-wide, no findings
pnpm check:watch-hint-literal 0 ✓ 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 statement
pnpm check:declared-population-live 0 ✓ 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 0 ✓ check:entry-guard: 208 scripts/ file(s) — every entry guard goes through invoked-as.mjs
pnpm check:nul-bytes 0 check-nul-bytes: OK (scanned 8261 text file(s) [...] no raw ASCII control bytes).
pnpm check:parse-guard 0 (green)
pnpm check:cli-command-ids 0 ✓ check-cli-command-ids: 328 command-id literal(s) across 115 file(s) [...] all resolve to a real command path
pnpm check:agent-test-spelling 0 (green)
pnpm check:bash32-floor 0 ✓ check-bash32-floor: 26 tracked shell file(s) [...] name no bash 4+ construct
pnpm check:cross-package-test-inputs 0 OK: 26 package(s) read outside themselves, all declared
pnpm check:pnpm-filter-targets 0 ✓ check:pnpm-filter-targets: 142/181 --filter occurrence(s) [...] resolve
pnpm check:refd-timer-probe 0 OK check-refd-timer-probe: 5865 source file(s) swept
node scripts/check-ci-filter-parity.mjs 0 OK: all 143 declared cross-package glob(s) (99 unique) are covered
node scripts/check-closing-keyword-parity.mjs (+ --self-test) 0 / 0 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 0 ✓ comment-mask corpus sweep: 5870 files, 0 disagree, 0 unparseable
node scripts/check-self-test-wired.mjs (+ --self-test) 0 / 0 ✓ 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) 0 / 0 green / ✓ 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 row
scripts/check-ratchet-remedy-authority.mjs SCRIPTS_DIR scripts (verdict
DECLARED-NARROWER, re-decided 2026-09-01) now describes a narrower walk than the gate
performs
: 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/*.mjs is reached and the declared set is three hints
rather 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-test is green here, because that map pins its recorded spellings against the
tree, not against this gate's array.


Generated by Claude Code

…/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
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@os-steve
os-steve marked this pull request as ready for review September 4, 2026 06:15
@os-steve
os-steve enabled auto-merge September 4, 2026 06:15
@os-steve
os-steve added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 3933819 Sep 4, 2026
36 checks passed
@os-steve
os-steve deleted the claude/issue-15179-remedy-authority-scripts-pm branch September 4, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants