Skip to content

Commit 74a32a2

Browse files
claude[bot]claude
andauthored
fix(devx): declare the non-recursive scripts population check:ratchet-remedy-authority really sweeps (#14231)
Fixes #13813 The gate builds its corpus at runtime from readdirSync(SCRIPTS_DIR), so the string naming that population lived only in messages and one comment, never as a literal the hint extractor can read. The derivation therefore made no claim and the family landed in the residue's undetermined bucket -- outside the matched list a dispatch brief prints, which cost one PR a CI round. Declares one hint per admitted extension beside the constants the walk is a function of, pinned in the gate's own --self-test in both directions, and re-decides the bare-root worklist row the declaration falsifies. Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV Co-authored-by: Claude <noreply@anthropic.com>
1 parent b360cc7 commit 74a32a2

2 files changed

Lines changed: 184 additions & 8 deletions

File tree

scripts/check-ratchet-remedy-authority.mjs

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
// node scripts/check-ratchet-remedy-authority.mjs --self-test # the detector's own rules
105105

106106
import { readFileSync, readdirSync } from 'node:fs';
107-
import { join, dirname, resolve } from 'node:path';
107+
import { join, dirname, resolve, relative } from 'node:path';
108108
import { fileURLToPath } from 'node:url';
109109
import process from 'node:process';
110110
import { isEntrypoint } from './invoked-as.mjs';
@@ -115,6 +115,72 @@ const SCRIPTS_DIR = join(REPO_ROOT, 'scripts');
115115
const SELF_FILE = 'check-ratchet-remedy-authority.mjs';
116116
const SELF = `scripts/${SELF_FILE}`;
117117

118+
/**
119+
* The two extensions the corpus walk admits, as ONE constant rather than two
120+
* `endsWith` calls inline — so the declaration below can be held against the
121+
* thing the sweep really filters on instead of against a second copy of it.
122+
* The `.mts` half is the #8538 blind spot and is not decoration: see the header.
123+
*/
124+
const CORPUS_EXTENSIONS = ['.mjs', '.mts'];
125+
126+
/**
127+
* The population this gate READS, declared for `scripts/pm/dispatch-gates.mjs`
128+
* -- the `ROOT_DIR_WATCH_HINTS` idiom, spelled as a literal array because the
129+
* hint extractor reads SOURCE TEXT (`scripts/check-watch-hint-literal.mjs`
130+
* holds that spelling for every declarer in the tree).
131+
*
132+
* ## Why this gate declared NOTHING, and what that cost (#13813)
133+
*
134+
* `extractWatchHints` scans the module body for path-shaped literals. This file
135+
* builds its population at RUNTIME from `readdirSync(SCRIPTS_DIR)`, and the
136+
* string naming that population appeared only in the failure text, in the
137+
* `--list` banner and in the header comment -- never as a literal the scanner
138+
* could read. So the derivation read no population here and made no claim, and
139+
* the family landed in the residue's `undetermined` bucket: not matched, not
140+
* known-irrelevant, and absent from the `matched` list a dispatch brief prints.
141+
*
142+
* That is not a theoretical gap. A PR touching three `scripts/**` files ran its
143+
* whole derived family locally, green, and CI reddened on THIS gate -- both
144+
* readings true, of the same tree. Eight families matched those same three
145+
* paths, every one of them at `gate source 'scripts/**'`, because their
146+
* populations are spelled where the derivation can read them.
147+
*
148+
* ## Why the spelling is the flat-directory glob and NOT `scripts/**`
149+
*
150+
* `corpusFiles()` is a NON-RECURSIVE `readdirSync` admitted by extension. It
151+
* reads the top level of `scripts/` only -- never `scripts/pm/`,
152+
* `scripts/docs-audit/` or any other nested directory -- and only the two
153+
* extensions above. `scripts/**` would be the easy spelling and it would be
154+
* FALSE: it pastes this gate onto every card touching any nested script, for
155+
* files this gate never opens, which is the costlier error `hintCovers`' own
156+
* docblock prices above a missing declaration.
157+
*
158+
* The two hints here are 100% precise AND complete against this tree, set-equal
159+
* in both directions to what `corpusFiles()` walks -- 183 of 183, against 310
160+
* tracked files under the bare root -- and the self-test below re-measures that
161+
* rather than trusting this sentence.
162+
*
163+
* ⚠️ NOT `scripts/*.{mjs,mts}`, however natural it looks beside the header and
164+
* the failure text, which both spell the corpus that way for a human reader.
165+
* `hintCovers` has no brace expansion, so that one string reaches NOTHING --
166+
* a dead declaration, printed as the same silence as declaring nothing at all.
167+
* One hint per extension is the spelling that is live, and a self-test case
168+
* pins the brace form as dead so the tidier spelling cannot quietly land.
169+
*
170+
* ## Why this spelling exists at all today
171+
*
172+
* `scripts/pm/bare-root-worklist.mjs` recorded this population as
173+
* REFUSE-UNSPELLABLE on the grounds that "the idiom has no non-recursive
174+
* spelling". That was TRUE when it was written: `collapseHint` deleted the glob
175+
* and spliced the remainder inside the final segment, so this hint collapsed to
176+
* `scripts/.mjs`, a string no tree can hold. #13448 retired that collapse for
177+
* exactly this shape -- a glob carrying a literal SUFFIX in the final segment is
178+
* MATCHED now, through `judgedAsPattern`/`triggerCovers`, not collapsed -- so
179+
* the refusal became false of the tree without anything reddening. That row is
180+
* re-decided alongside this declaration, in the file that owns it.
181+
*/
182+
const ROOT_DIR_WATCH_HINTS = ['scripts/*.mjs', 'scripts/*.mts'];
183+
118184
/** The compliance token. Byte-identical to every instrumented gate's const. */
119185
const RATCHET_AUTHORITY_MARKER = '⛔ MAINTAINER-ONLY';
120186

@@ -838,7 +904,7 @@ const CONTROL = {
838904
/** @returns {string[]} corpus filenames, sorted. `*.{mjs,mts}` — never `*.mjs`. */
839905
export function corpusFiles() {
840906
return readdirSync(SCRIPTS_DIR)
841-
.filter((f) => f.endsWith('.mjs') || f.endsWith('.mts'))
907+
.filter((f) => CORPUS_EXTENSIONS.some((ext) => f.endsWith(ext)))
842908
.sort();
843909
}
844910

@@ -1119,6 +1185,79 @@ function selfTest() {
11191185
+ 'control is a declaration registry, and its offer-shaped quotes live in comments)',
11201186
results.get(SELF_FILE) !== undefined && results.get(SELF_FILE).verdict === 'excluded');
11211187

1188+
// ── (20) The declared population, held to the walk in BOTH directions (#13813)
1189+
//
1190+
// ⛔ Checked with a LOCAL matcher rather than by importing
1191+
// `scripts/pm/dispatch-gates.mjs`: the import specifier would itself be a path
1192+
// literal in this file's source, so the derivation would hand this gate that
1193+
// module's declared population as if it were this gate's own — a fabricated
1194+
// watch surface. (The same refusal `check-logger-receiver-detach.mjs` records.)
1195+
// The liveness of these hints in `hintCovers`' own terms is pinned where
1196+
// `hintCovers` is already in scope: `scripts/pm/bare-root-worklist.mjs`.
1197+
//
1198+
// Both directions, because either alone passes against the defect this closes.
1199+
// "The declaration is non-empty" was true of every gate that ever went silent;
1200+
// "it names the root" is true of `scripts/**`, which is the FALSE spelling
1201+
// here. What is asserted is that the hints are a function of the two constants
1202+
// the walk is a function of — move the read and this reds, in this file.
1203+
const CORPUS_ROOT = relative(REPO_ROOT, SCRIPTS_DIR).split('\\').join('/');
1204+
expect('declaration — one hint per admitted extension, each the flat-directory glob under the '
1205+
+ `very root the walk reads from (declared: ${JSON.stringify(ROOT_DIR_WATCH_HINTS)}, root: `
1206+
+ `${CORPUS_ROOT}, extensions: ${CORPUS_EXTENSIONS.join(' ')})`,
1207+
ROOT_DIR_WATCH_HINTS.length === CORPUS_EXTENSIONS.length
1208+
&& CORPUS_EXTENSIONS.every((ext) => ROOT_DIR_WATCH_HINTS.includes(`${CORPUS_ROOT}/*${ext}`)));
1209+
1210+
// ⛔ The subtree spelling is the one this declaration must never take. The walk
1211+
// is a NON-RECURSIVE readdir; `scripts/**` would name this gate for every
1212+
// nested script it never opens, which is the costlier error the idiom prices
1213+
// above a missing declaration.
1214+
expect('declaration — the subtree spelling is refused: the walk is non-recursive, so no hint '
1215+
+ 'ends in a subtree glob and none collapses back to the bare root',
1216+
!ROOT_DIR_WATCH_HINTS.some((h) => h.endsWith('/**') || h.replace(/\/\*+$/, '') === CORPUS_ROOT));
1217+
1218+
// The local matcher for `<root>/*<ext>`: one directory level, one extension.
1219+
const declaresPath = (p) => ROOT_DIR_WATCH_HINTS.some((h) => {
1220+
const m = /^([^/]+)\/\*(\.[a-z]+)$/.exec(h);
1221+
return Boolean(m) && p.startsWith(`${m[1]}/`)
1222+
&& !p.slice(m[1].length + 1).includes('/') && p.endsWith(m[2]);
1223+
});
1224+
const corpusPaths = corpusFiles().map((f) => `${CORPUS_ROOT}/${f}`);
1225+
const unnamed = corpusPaths.filter((p) => !declaresPath(p));
1226+
expect('declaration — COMPLETE: every file the sweep opens is named by a declared hint '
1227+
+ `(${corpusPaths.length} swept, ${unnamed.length} unnamed`
1228+
+ `${unnamed.length ? `: ${unnamed.slice(0, 3).join(', ')}` : ''})`,
1229+
corpusPaths.length > 0 && unnamed.length === 0);
1230+
expect('declaration — no hint is DEAD: each one names at least one file this gate really reads',
1231+
ROOT_DIR_WATCH_HINTS.every((h) => corpusPaths.some((p) => declaresPath(p) && p.endsWith(h.slice(h.indexOf('*') + 1)))));
1232+
1233+
// PRECISE, against the two shapes `scripts/**` would wrongly sweep in, both
1234+
// taken from the tree rather than spelled: a nested script, and a top-level
1235+
// file at an extension the walk does not admit. Non-vacuous by construction —
1236+
// the assertion fails if the tree stops carrying an example of either.
1237+
const topLevel = readdirSync(SCRIPTS_DIR, { withFileTypes: true });
1238+
const nested = topLevel
1239+
.filter((e) => e.isDirectory())
1240+
.flatMap((d) => readdirSync(join(SCRIPTS_DIR, d.name))
1241+
.filter((f) => CORPUS_EXTENSIONS.some((ext) => f.endsWith(ext)))
1242+
.map((f) => `${CORPUS_ROOT}/${d.name}/${f}`));
1243+
expect('declaration — PRECISE against depth: the tree carries nested scripts at the admitted '
1244+
+ `extensions (${nested.length}) and the declaration names NONE of them`,
1245+
nested.length > 0 && !nested.some(declaresPath));
1246+
const otherExt = topLevel
1247+
.filter((e) => e.isFile() && !CORPUS_EXTENSIONS.some((ext) => e.name.endsWith(ext)))
1248+
.map((e) => `${CORPUS_ROOT}/${e.name}`);
1249+
expect('declaration — PRECISE against extension: the root carries top-level files the walk does '
1250+
+ `not admit (${otherExt.length}) and the declaration names NONE of them`,
1251+
otherExt.length > 0 && !otherExt.some(declaresPath));
1252+
1253+
// ⚠️ The brace form is what the header, the `--list` banner and the OK line all
1254+
// spell for a human reader, and it is DEAD as a hint — no brace expansion in
1255+
// the matcher, so it would reach nothing while reading like a declaration.
1256+
// Pinned so the tidier-looking spelling cannot quietly replace the live one.
1257+
expect('declaration — the brace form the messages spell is NOT what is declared (it reaches '
1258+
+ 'nothing, and a dead declaration prints as the same silence as declaring nothing)',
1259+
!ROOT_DIR_WATCH_HINTS.some((h) => h.includes('{')));
1260+
11221261
if (failures.length > 0) {
11231262
for (const f of failures) console.error(` x self-test: ${f}`);
11241263
console.error(`\ncheck-ratchet-remedy-authority --self-test: ${failures.length} failure(s).\n`);

scripts/pm/bare-root-worklist.mjs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,22 @@ const POPULATION_CONSTANT = /^(?:[A-Z0-9_]*_ROOTS?|[A-Z0-9_]*_DIRS?|POPULATION|[
202202
* re-measured on the 2026-08-29 tree and NOT carried from its sibling, which this
203203
* docblock forbids by name.
204204
*
205+
* ⭐ A NINETEENTH row was re-decided on 2026-09-01 under that same authorisation
206+
* sentence and no wider one: `check:ratchet-remedy-authority SCRIPTS_DIR scripts`.
207+
* Its refusal rested on the deletion-collapse too, in the variant that splices
208+
* WITHIN a final segment rather than across a separator — `scripts/*.mjs` became
209+
* `scripts/.mjs`, a string no tree can hold — so "the idiom has no non-recursive
210+
* spelling" was true of the derivation that wrote it and false of this one.
211+
* #13448 retired that collapse for the shape, which `dispatch-gates.mjs` states
212+
* in its own docblock as "the same defect as the non-final case one level
213+
* finer". So this is the identical retired mechanism, not a new licence, and it
214+
* is a RE-DECISION rather than a repair for the same reason the eighteen before
215+
* it were. Its consumer is #13813 and it is MEASURED: the family sat in the
216+
* residue's undetermined bucket, outside the matched list a brief prints, and a
217+
* PR that ran its whole derived family green locally lost a CI round to this
218+
* gate. ⛔ Its numbers are re-measured on the 2026-09-01 tree in BOTH terms and
219+
* NOT carried from the row they replace, which this docblock forbids by name.
220+
*
205221
* ⚠️ One row of that seventeen was re-measured into a DIFFERENT population, not
206222
* merely fresher digits: #12392 (PR #12423, `69d0e18`) made
207223
* `check-skills-token-ratchet`'s walk RECURSIVE over whole skill directories, so
@@ -393,6 +409,33 @@ const TRIAGE = new Map([
393409
+ 'missed defect. The row STAYS in the sweep because the bare root is still not covered — '
394410
+ 'no arbitrary file at the top of packages/ is reached — which is what this verdict says',
395411
}],
412+
['check:ratchet-remedy-authority SCRIPTS_DIR scripts', {
413+
verdict: 'DECLARED-NARROWER',
414+
why: 'RE-DECIDED 2026-09-01 (#13813) from REFUSE-UNSPELLABLE, whose stated reason — "the idiom '
415+
+ 'has no non-recursive spelling" — was TRUE when written and is FALSE of this tree. It '
416+
+ 'rested on the deletion-collapse: a glob carrying a literal SUFFIX in the final segment '
417+
+ 'was spliced WITHIN the segment, so the only non-recursive spelling reduced to a string no '
418+
+ 'tree can hold and reached nothing. #13448 retired that collapse for exactly this shape — '
419+
+ 'judgedAsPattern routes it to triggerCovers now — which is the same retired collapse the '
420+
+ 'seventeen rows of 2026-08-26 and the eighteenth of 2026-08-29 were re-decided under, one '
421+
+ 'refinement finer, and this row is re-decided under that authorisation sentence and no '
422+
+ 'wider one. Both terms re-measured together on this tree, never refreshed apart: the gate '
423+
+ 'own corpusFiles() walk admits 183 files, against 310 tracked under the bare root (59%). '
424+
+ 'The gate now declares ONE hint per admitted extension beside SCRIPTS_DIR under the '
425+
+ 'ROOT_DIR_WATCH_HINTS idiom, and the pair is SET-EQUAL to that walk in both directions — '
426+
+ '183 of 183, nothing read left uncovered, nothing covered left unread — so 100% precise '
427+
+ 'and complete. ⛔ NO `spelling` is recorded and that is deliberate, not an omission: '
428+
+ 'SPELLINGS holds ONE hint per entry and this population needs one per extension, the same '
429+
+ 'shape as the check:logger-receiver-detach and check:dual-build-cjs-loads rows above, so '
430+
+ 'the liveness-and-precision coupling is held in the gate own --self-test, which pins the '
431+
+ 'hints against SCRIPTS_DIR and CORPUS_EXTENSIONS and refuses both the subtree spelling and '
432+
+ 'the brace form its own messages print. The consumer is MEASURED, not argued: before this, '
433+
+ 'the derivation placed this family in the residue undetermined bucket, absent from the '
434+
+ 'matched list a brief prints, and a PR that ran its whole derived family green locally '
435+
+ 'lost a CI round to this gate. The row STAYS in the sweep because the bare root is still '
436+
+ 'not covered — no arbitrary file at the top of the root is reached, and no nested script '
437+
+ 'at any depth — which is what this verdict says and is correct, not outstanding debt',
438+
}],
396439
// ── Refused: the population is the whole root, and the root is saturated ──
397440
['check:skill-identifier-liveness IMPL_ROOTS packages', {
398441
verdict: 'REFUSE-WIDE',
@@ -489,12 +532,6 @@ const TRIAGE = new Map([
489532
why: '162 of 238 (68%), refused with its packages half for the reason above',
490533
}],
491534
// ── Refused: the population is a filter the idiom cannot spell ────────────
492-
['check:ratchet-remedy-authority SCRIPTS_DIR scripts', {
493-
verdict: 'REFUSE-UNSPELLABLE',
494-
why: 'reads the TOP LEVEL of the root only, and only two extensions (`.mjs` and `.mts`) — 144 '
495-
+ 'of 261 (55%), re-derived from the gate own corpusFiles() walk. The idiom has no '
496-
+ 'non-recursive spelling: a subtree hint claims every nested directory too',
497-
}],
498535
['scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts', {
499536
verdict: 'REFUSE-UNSPELLABLE',
500537
why: 'a RECURSIVE walk admitted by EXTENSION — every `scripts/**/*.d.mts`, 2 of 261 (0.77%), '

0 commit comments

Comments
 (0)