Skip to content

Commit 200fc82

Browse files
claude[bot]claude
andauthored
fix(devx): burn the KNOWN_IMPORT_UNSAFE ledger 10 -> 5 — a main() for the five scripts/ modules that ran on import (#11917)
* fix(pm): make check-governed-prose.mjs inert on import, and drop its dead argv workaround Its top level ran the whole gate and then called process.exit() — importing it for its exported predicates terminated the importer mid-import, with exit 0. The silent-success direction: a caller reading status got "clean" from a gate that never reported. The module-scope `process.argv` mutation that withheld `--self-test` from the sibling import is deleted rather than preserved. check-governed-merges.mjs now guards both of its module-scope triggers on isEntrypoint(import.meta.url) (main at :1201, self-test at :1614), so the flag cannot reach either one on an import path. Measured: importing it with `--self-test` planted in process.argv runs no self-test, prints nothing, returns its 34 exports. The mutation was never free — process.argv is process-global, so it edited the importer's argv too. CLI byte-identical before/after on both paths (default and --self-test): stdout, stderr and exit code all cmp-clean. Import probe with clean argv: sentinel only, empty stderr, exit 0. check:entry-guard named the file STALE before its ledger line was removed; ledger 10 -> 9, gate green. * fix(devx): give checklist-select.mjs a main(), so importing selectItems does not kill the importer This one was the fatal shape in the ledger. Its top-level CLI ran on import: importing it for `selectItems` — the pure resolver the checklist-test skill's front half is built on — printed a usage block to the importer's stderr and called process.exit(2) mid-import. The probe's own sentinel never printed. The self-test block becomes selfTest(), the CLI block becomes main(), and both run only under isEntrypoint(import.meta.url). No executable line changed other than its enclosing scope; the process.exit() calls stay exactly where they were. Verified byte-for-byte against the pristine base tree, since a main() extraction moves executable lines where a wrap does not — stdout, stderr and exit code all cmp-clean on six legs: default (usage, exit 2), --self-test, `all --json`, `area:approvals`, `all --include-blocked`, and `nope.xxx` (the no-match exit-1 path). Import probe with clean argv: sentinel only, empty stderr, exit 0. check:entry-guard named it STALE before its ledger line went; ledger 9 -> 8. * fix(devx): give check-release-page-status.mjs a main() so its verdict stops running inside importers The file exports eleven predicates, and check-release-section-coverage.mjs is a real reader of that surface — it inherits this gate's scope floor and pins the two equal in its own self-test. Unguarded, importing any one of those predicates ran the entire release-page gate and printed its verdict, so a caller's output carried another gate's OK line above its own. The run block becomes main(); it and the self-test dispatch now sit behind isEntrypoint(import.meta.url). Byte-for-byte against the pristine base tree on both paths, plus both paths of check-release-section-coverage.mjs — the sibling that reads this module — all cmp-clean on stdout, stderr and exit code. Import probe with clean argv: sentinel only, empty stderr, exit 0. check:entry-guard named it STALE first; ledger 8 -> 7. * fix(devx): give check-error-status-conformance.mjs a main() instead of running the derivation on import The module exports the whole derivation — parseStandardErrorCodes, buildConstantIndex, deriveRuntimeStatuses, deriveDoorMap, reconcile and the message builders. Unguarded, importing any one of them walked the scan root, read every non-test source file under it and printed this gate's full report into the importer's stdout before the import returned. Measured on this tree: the probe's own sentinel printed only after ~8s of another gate's output. `walk` stays a module-scope function declaration — a declaration is not a side effect, and the entry-guard rule does not reach one. Everything from `const update` down becomes main(), behind isEntrypoint(import.meta.url). Byte-for-byte against the pristine base tree on every code path this file has: default, --self-test, --report, and --update — for --update, the rewritten baseline artifact is byte-identical too, not just the stdout. All four legs cmp-clean on stdout, stderr and exit code. Import probe with clean argv: sentinel only, empty stderr, exit 0. STALE first; ledger 7 -> 6. * fix(devx): stop check-query-options-erasure-ratchet.mjs re-execing its importer The loudest entry in the ledger. Two separate import side effects, fixed two different ways: - `ensureStackHeadroom()` at module scope. It re-execs with --stack-size and then calls process.exit(status), so importing this module REPLACED the importer's process with a fresh run of this gate. Guarded IN PLACE rather than moved into main(): its docblock's invariant is an ordering one ("re-exec once, before any linting -- including before --self-test"), and leaving the call at its original position in module order is what keeps that ordering checkable by reading rather than by re-deriving it. - The tail becomes async main(), with the self-test dispatch, behind the guard. No module-scope declaration between the two constructs an ESLint instance (all three `new ESLint` sites are inside functions), so nothing heavy moved across the re-exec point. checkHeadroomAdoption() still reads this gate as armed: it tests /ensureStackHeadroom\s*\(/ over comment-stripped source, which an indented call satisfies. CLI byte-identical before/after on both paths — stdout, stderr and exit code — captured through scripts/pm/os-verify-lock.sh. Import probe with clean argv now returns in 0.5s with the sentinel only and empty stderr, where before it never returned at all. STALE first; ledger 6 -> 5. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9760d72 commit 200fc82

6 files changed

Lines changed: 344 additions & 285 deletions

scripts/check-entry-guard.mjs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,7 @@ export function importUnsafeStatements(source) {
460460
const KNOWN_IMPORT_UNSAFE = new Set([
461461
'scripts/check-changeset-no-major.mjs',
462462
'scripts/check-empty-changeset.mjs',
463-
'scripts/check-error-status-conformance.mjs',
464-
'scripts/check-query-options-erasure-ratchet.mjs',
465-
'scripts/check-release-page-status.mjs',
466-
'scripts/checklist-select.mjs',
467463
'scripts/objectui-range.mjs',
468-
'scripts/pm/check-governed-prose.mjs',
469464
'scripts/qa/qa-rollup.mjs',
470465
'scripts/ts-parse.mjs',
471466
]);

scripts/check-error-status-conformance.mjs

Lines changed: 130 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
import { readdirSync, readFileSync, writeFileSync, statSync, existsSync } from 'node:fs';
113113
import { maskComments } from './js-comment-mask.mjs';
114114
import { join, relative } from 'node:path';
115+
import { isEntrypoint } from './invoked-as.mjs';
115116

116117
const SCAN_ROOT = 'packages';
117118
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', 'coverage', 'build', 'fixtures']);
@@ -1083,7 +1084,6 @@ function selfTest() {
10831084
process.exit(0);
10841085
}
10851086

1086-
if (process.argv.includes('--self-test')) selfTest();
10871087

10881088
// ───────────────────────────────────────────────────────────────────────────
10891089
// The real check
@@ -1099,134 +1099,145 @@ function walk(dir, out) {
10991099
}
11001100
}
11011101

1102-
const update = process.argv.includes('--update');
1103-
1104-
const files = [];
1105-
walk(SCAN_ROOT, files);
1106-
const sources = new Map();
1107-
for (const f of files.sort()) sources.set(relative('.', f).replace(/\\/g, '/'), readFileSync(f, 'utf8'));
1108-
1109-
const errorsZod = readFileSync(ERRORS_ZOD, 'utf8');
1110-
const members = parseStandardErrorCodes(errorsZod);
1111-
const index = buildConstantIndex(sources);
1112-
const derived = deriveRuntimeStatuses(sources, index);
1113-
for (const { code, status } of deriveDoorMap(errorsZod)) {
1114-
if (!derived.emitted.has(code)) derived.emitted.set(code, new Map());
1115-
const perStatus = derived.emitted.get(code);
1116-
if (!perStatus.has(status)) perStatus.set(status, []);
1117-
if (!perStatus.get(status).includes(`${ERRORS_ZOD}: HttpStatusErrorCodeMap`)) {
1118-
perStatus.get(status).push(`${ERRORS_ZOD}: HttpStatusErrorCodeMap`);
1102+
function main() {
1103+
const update = process.argv.includes('--update');
1104+
1105+
const files = [];
1106+
walk(SCAN_ROOT, files);
1107+
const sources = new Map();
1108+
for (const f of files.sort()) sources.set(relative('.', f).replace(/\\/g, '/'), readFileSync(f, 'utf8'));
1109+
1110+
const errorsZod = readFileSync(ERRORS_ZOD, 'utf8');
1111+
const members = parseStandardErrorCodes(errorsZod);
1112+
const index = buildConstantIndex(sources);
1113+
const derived = deriveRuntimeStatuses(sources, index);
1114+
for (const { code, status } of deriveDoorMap(errorsZod)) {
1115+
if (!derived.emitted.has(code)) derived.emitted.set(code, new Map());
1116+
const perStatus = derived.emitted.get(code);
1117+
if (!perStatus.has(status)) perStatus.set(status, []);
1118+
if (!perStatus.get(status).includes(`${ERRORS_ZOD}: HttpStatusErrorCodeMap`)) {
1119+
perStatus.get(status).push(`${ERRORS_ZOD}: HttpStatusErrorCodeMap`);
1120+
}
11191121
}
1120-
}
11211122

1122-
const doc = parseDocumentedStatuses({
1123-
handling: readFileSync(DOC_HANDLING, 'utf8'),
1124-
catalog: readFileSync(DOC_CATALOG, 'utf8'),
1125-
});
1126-
const { vocabulary, docPublishedBeyondStandard } = reconciledVocabulary({ members, ...doc });
1127-
const result = reconcile({ vocabulary, emitted: derived.emitted, ...doc });
1128-
1129-
const baseline = existsSync(BASELINE_PATH)
1130-
? JSON.parse(readFileSync(BASELINE_PATH, 'utf8'))
1131-
: { unpinned: [] };
1132-
const baselined = new Set(baseline.unpinned ?? []);
1133-
const newlyUnpinned = result.unpinned.filter((c) => !baselined.has(c));
1134-
const nowPinnedFindings = nowPinned({
1135-
baselined: [...baselined], unpinned: result.unpinned, vocabulary, documented: doc.documented,
1136-
});
1137-
1138-
if (update) {
1139-
writeFileSync(
1140-
BASELINE_PATH,
1141-
`${JSON.stringify({
1142-
note:
1143-
'StandardErrorCode members documented with an HTTP status that NO producer this gate can read '
1144-
+ 'declares a status for — nothing pins the doc claim on either side. Shrink-only: a new entry is a '
1145-
+ 'gate failure, and a row that becomes pinned must be removed. Regenerate with '
1146-
+ '`node scripts/check-error-status-conformance.mjs --update`.',
1147-
unpinned: result.unpinned,
1148-
}, null, 2)}\n`,
1149-
);
1150-
console.log(`Baseline rewritten: ${result.unpinned.length} unpinned code(s).`);
1151-
process.exit(0);
1152-
}
1123+
const doc = parseDocumentedStatuses({
1124+
handling: readFileSync(DOC_HANDLING, 'utf8'),
1125+
catalog: readFileSync(DOC_CATALOG, 'utf8'),
1126+
});
1127+
const { vocabulary, docPublishedBeyondStandard } = reconciledVocabulary({ members, ...doc });
1128+
const result = reconcile({ vocabulary, emitted: derived.emitted, ...doc });
11531129

1154-
// The residual, as a SUBTRACTION from what the pages publish rather than a
1155-
// claim about it: codes the deriver found that are neither a `StandardErrorCode`
1156-
// member nor published with a status by a scanned page. Nothing here is a
1157-
// literal, so registering a ledger code moves no number that has to be edited.
1158-
const unreconciledLedger = [...derived.emitted.keys()].filter((c) => !vocabulary.includes(c)).sort();
1159-
1160-
// `--report` prints the whole derivation rather than only the disagreements.
1161-
// A finding is only as trustworthy as the evidence behind it, and "which
1162-
// producers did you actually see for this code?" is the first question anyone
1163-
// reading a failure asks.
1164-
if (process.argv.includes('--report')) {
1165-
for (const code of vocabulary) {
1166-
const runtime = derived.emitted.get(code);
1167-
if (!runtime) continue;
1168-
console.log(`${code}`);
1169-
for (const [status, where] of [...runtime].sort((a, b) => a[0] - b[0])) {
1170-
console.log(` ${status} ${where.join('\n ')}`);
1130+
const baseline = existsSync(BASELINE_PATH)
1131+
? JSON.parse(readFileSync(BASELINE_PATH, 'utf8'))
1132+
: { unpinned: [] };
1133+
const baselined = new Set(baseline.unpinned ?? []);
1134+
const newlyUnpinned = result.unpinned.filter((c) => !baselined.has(c));
1135+
const nowPinnedFindings = nowPinned({
1136+
baselined: [...baselined], unpinned: result.unpinned, vocabulary, documented: doc.documented,
1137+
});
1138+
1139+
if (update) {
1140+
writeFileSync(
1141+
BASELINE_PATH,
1142+
`${JSON.stringify({
1143+
note:
1144+
'StandardErrorCode members documented with an HTTP status that NO producer this gate can read '
1145+
+ 'declares a status for — nothing pins the doc claim on either side. Shrink-only: a new entry is a '
1146+
+ 'gate failure, and a row that becomes pinned must be removed. Regenerate with '
1147+
+ '`node scripts/check-error-status-conformance.mjs --update`.',
1148+
unpinned: result.unpinned,
1149+
}, null, 2)}\n`,
1150+
);
1151+
console.log(`Baseline rewritten: ${result.unpinned.length} unpinned code(s).`);
1152+
process.exit(0);
1153+
}
1154+
1155+
// The residual, as a SUBTRACTION from what the pages publish rather than a
1156+
// claim about it: codes the deriver found that are neither a `StandardErrorCode`
1157+
// member nor published with a status by a scanned page. Nothing here is a
1158+
// literal, so registering a ledger code moves no number that has to be edited.
1159+
const unreconciledLedger = [...derived.emitted.keys()].filter((c) => !vocabulary.includes(c)).sort();
1160+
1161+
// `--report` prints the whole derivation rather than only the disagreements.
1162+
// A finding is only as trustworthy as the evidence behind it, and "which
1163+
// producers did you actually see for this code?" is the first question anyone
1164+
// reading a failure asks.
1165+
if (process.argv.includes('--report')) {
1166+
for (const code of vocabulary) {
1167+
const runtime = derived.emitted.get(code);
1168+
if (!runtime) continue;
1169+
console.log(`${code}`);
1170+
for (const [status, where] of [...runtime].sort((a, b) => a[0] - b[0])) {
1171+
console.log(` ${status} ${where.join('\n ')}`);
1172+
}
11711173
}
1174+
console.log(`\nderived but NOT reconciled — no scanned page publishes a status for these ${unreconciledLedger.length}:`);
1175+
for (const c of unreconciledLedger) console.log(` ${c}`);
11721176
}
1173-
console.log(`\nderived but NOT reconciled — no scanned page publishes a status for these ${unreconciledLedger.length}:`);
1174-
for (const c of unreconciledLedger) console.log(` ${c}`);
1175-
}
11761177

1177-
console.log('check:error-status-conformance — documented HTTP status ⇄ runtime-emitted status');
1178-
console.log(
1179-
` scope: ${vocabulary.length} code(s) reconciled = ${members.length} StandardErrorCode member(s) `
1180-
+ `+ ${docPublishedBeyondStandard.length} ledger code(s) a doc page publishes a status for`
1181-
+ `${docPublishedBeyondStandard.length ? ` (${docPublishedBeyondStandard.join(', ')})` : ''}; `
1182-
+ `${sources.size} source files scanned; ${derived.sites} producer site(s) derived; `
1183-
+ `${unreconciledLedger.length} further ledger code(s) derived but NOT reconciled — no scanned page publishes `
1184-
+ 'a status for them, so there is nothing to reconcile them against.',
1185-
);
1186-
console.log(
1187-
` reconciled: ${result.reconciledCodes} code(s) with a derived producer, `
1188-
+ `${result.reconciledPairs} (code, status) pair(s) matched against the docs.`,
1189-
);
1190-
console.log(` unpinned: ${result.unpinned.length} documented code(s) with no derivable producer (baselined: ${baselined.size}).`);
1191-
const ungraded = ungradedEntries(doc);
1192-
if (ungraded.length) {
1178+
console.log('check:error-status-conformance — documented HTTP status ⇄ runtime-emitted status');
11931179
console.log(
1194-
` ungraded: ${ungraded.length} doc entr(y|ies) whose heading was read but for which no page publishes a `
1195-
+ 'status in a graded shape (reported, not failed — see `ungradedEntries`) —',
1180+
` scope: ${vocabulary.length} code(s) reconciled = ${members.length} StandardErrorCode member(s) `
1181+
+ `+ ${docPublishedBeyondStandard.length} ledger code(s) a doc page publishes a status for`
1182+
+ `${docPublishedBeyondStandard.length ? ` (${docPublishedBeyondStandard.join(', ')})` : ''}; `
1183+
+ `${sources.size} source files scanned; ${derived.sites} producer site(s) derived; `
1184+
+ `${unreconciledLedger.length} further ledger code(s) derived but NOT reconciled — no scanned page publishes `
1185+
+ 'a status for them, so there is nothing to reconcile them against.',
11961186
);
1197-
for (const e of ungraded) console.log(` ${e.code} ${e.where}`);
1198-
}
1199-
if (doc.unreadableHeadings.length) {
1200-
console.log(` unreadable: ${doc.unreadableHeadings.length} doc heading(s) naming a code in an unrecognised shape —`);
1201-
for (const u of doc.unreadableHeadings) console.log(` ${u.path}:${u.line} ${JSON.stringify(u.text)}`);
1202-
}
1203-
if (derived.unresolved.length) {
1204-
console.log(` unresolved: ${derived.unresolved.length} declaration(s) the deriver could not read —`);
1205-
for (const u of derived.unresolved) console.log(` ${u}`);
1206-
}
1207-
1208-
if (result.reconciledPairs === 0) {
1209-
console.error(
1210-
'\n✗ the deriver matched ZERO (code, status) pairs. A green run with nothing reconciled is a blind run, '
1211-
+ 'not a clean one — the source anchors this gate reads have moved.\n',
1187+
console.log(
1188+
` reconciled: ${result.reconciledCodes} code(s) with a derived producer, `
1189+
+ `${result.reconciledPairs} (code, status) pair(s) matched against the docs.`,
12121190
);
1213-
process.exit(1);
1214-
}
1191+
console.log(` unpinned: ${result.unpinned.length} documented code(s) with no derivable producer (baselined: ${baselined.size}).`);
1192+
const ungraded = ungradedEntries(doc);
1193+
if (ungraded.length) {
1194+
console.log(
1195+
` ungraded: ${ungraded.length} doc entr(y|ies) whose heading was read but for which no page publishes a `
1196+
+ 'status in a graded shape (reported, not failed — see `ungradedEntries`) —',
1197+
);
1198+
for (const e of ungraded) console.log(` ${e.code} ${e.where}`);
1199+
}
1200+
if (doc.unreadableHeadings.length) {
1201+
console.log(` unreadable: ${doc.unreadableHeadings.length} doc heading(s) naming a code in an unrecognised shape —`);
1202+
for (const u of doc.unreadableHeadings) console.log(` ${u.path}:${u.line} ${JSON.stringify(u.text)}`);
1203+
}
1204+
if (derived.unresolved.length) {
1205+
console.log(` unresolved: ${derived.unresolved.length} declaration(s) the deriver could not read —`);
1206+
for (const u of derived.unresolved) console.log(` ${u}`);
1207+
}
12151208

1216-
const failures = [];
1217-
for (const f of result.emittedNotDocumented) failures.push(emittedNotDocumentedMessage(f));
1218-
for (const f of result.documentedNotReachable) failures.push(documentedNotReachableMessage(f));
1219-
for (const u of doc.unreadableHeadings) failures.push(unreadableHeadingMessage(u));
1220-
for (const c of newlyUnpinned) failures.push(newUnpinnedMessage(c));
1221-
for (const f of nowPinnedFindings) {
1222-
failures.push(f.reason === 'producer' ? nowPinnedProducerMessage(f.code) : nowPinnedDocRemovedMessage(f.code));
1223-
}
1209+
if (result.reconciledPairs === 0) {
1210+
console.error(
1211+
'\n✗ the deriver matched ZERO (code, status) pairs. A green run with nothing reconciled is a blind run, '
1212+
+ 'not a clean one — the source anchors this gate reads have moved.\n',
1213+
);
1214+
process.exit(1);
1215+
}
1216+
1217+
const failures = [];
1218+
for (const f of result.emittedNotDocumented) failures.push(emittedNotDocumentedMessage(f));
1219+
for (const f of result.documentedNotReachable) failures.push(documentedNotReachableMessage(f));
1220+
for (const u of doc.unreadableHeadings) failures.push(unreadableHeadingMessage(u));
1221+
for (const c of newlyUnpinned) failures.push(newUnpinnedMessage(c));
1222+
for (const f of nowPinnedFindings) {
1223+
failures.push(f.reason === 'producer' ? nowPinnedProducerMessage(f.code) : nowPinnedDocRemovedMessage(f.code));
1224+
}
12241225

1225-
if (failures.length) {
1226-
console.error('');
1227-
for (const f of failures) console.error(` ✗ ${f}`);
1228-
console.error(`\n✗ check:error-status-conformance — ${failures.length} finding(s).\n`);
1229-
process.exit(1);
1226+
if (failures.length) {
1227+
console.error('');
1228+
for (const f of failures) console.error(` ✗ ${f}`);
1229+
console.error(`\n✗ check:error-status-conformance — ${failures.length} finding(s).\n`);
1230+
process.exit(1);
1231+
}
1232+
1233+
console.log('\n✓ every derivable runtime status is documented, and every documented status is reachable.');
12301234
}
12311235

1232-
console.log('\n✓ every derivable runtime status is documented, and every documented status is reachable.');
1236+
// Guarded: this module exports the whole derivation (parseStandardErrorCodes,
1237+
// deriveRuntimeStatuses, reconcile, and the message builders), and unguarded an
1238+
// import of any of them walked the scan root, read every source file and printed
1239+
// this gate's full report into the importer's stdout before returning a binding.
1240+
if (isEntrypoint(import.meta.url)) {
1241+
if (process.argv.includes('--self-test')) selfTest();
1242+
main();
1243+
}

0 commit comments

Comments
 (0)