From f726727cdba8ef3085d6ab58b4f9e15c4ab5da38 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 16:26:50 +0000 Subject: [PATCH] docs(tooling): correct invoked-as.mjs's claim that a gate enforces its single-spelling rule scripts/check-entry-guard.mjs has never existed in this repository. The header named it as the enforcement that stops a twelfth entry-guard spelling, so the module read to the next session as evidence that the tree was already converted and policed. Neither is true: one module imports isEntrypoint, 29 hand-typed process.argv[1] guards remain in nine distinct spellings, and no gate checks any of it. The port (#5984) brought the module and its prose out of objectstack without the gate or the sweep that made the prose true, so four of the paths the header names resolve only in that repo. Attribute the borrowed evidence, replace it with the same measurement taken on a real blocking gate here, and point the reader at the card that tracks the actual work. Also un-vacuums one self-test case: it reached for js-comment-mask.mjs, which is another objectstack-only path, so 'an unrelated existing file is not this module' had silently become a duplicate of the case testing an unreadable path. It now names a file that is really here, with an existence assertion so the case cannot degrade that way again. --- scripts/invoked-as.mjs | 105 +++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/scripts/invoked-as.mjs b/scripts/invoked-as.mjs index 58b1ac3e04..8f1b4afd8d 100644 --- a/scripts/invoked-as.mjs +++ b/scripts/invoked-as.mjs @@ -7,11 +7,16 @@ * node scripts/invoked-as.mjs --self-test * * Every CLI script in `scripts/` has to separate "node ran me" from "something - * imported me" before it decides whether to do anything. Each one used to - * answer that with its own hand-typed comparison of `process.argv[1]` against - * `import.meta.url`, and the copies had drifted into ELEVEN distinct spellings - * across 33 files -- measured, not estimated. Nine of the eleven were wrong, - * and wrong in a direction nothing in CI can see. + * imported me" before it decides whether to do anything. Left to a hand-typed + * comparison of `process.argv[1]` against `import.meta.url`, those answers + * drift: in the objectstack tree this module was ported FROM (#5984) they had + * reached ELEVEN distinct spellings across 33 files -- measured, not estimated + * -- and nine of the eleven were wrong, in a direction nothing in CI can see. + * + * THIS repository has not been swept, and no gate here enforces the rule. Read + * "## Nothing here enforces this yet" below before concluding otherwise: every + * path this header names outside `scripts/` is objectstack's, and several under + * `scripts/` are too. * * ## The bug every hand-typed spelling had * @@ -22,19 +27,25 @@ * * That is the silent-success direction this tree treats as worse than no check * at all. The CI wrappers spawn these tools and hold `result.status` only, so - * an inert child is a GREEN gate. Measured on the tree that motivated this - * module: + * an inert child is a GREEN gate. Measured HERE, on a real blocking gate, with + * one guide file made deliberately wrong so that the direct run is RED: + * + * node scripts/check-skills-paths.mjs + * direct : exit=1, 696 bytes naming the dead path and how to fix it + * symlink : exit=0, no output at all * - * scripts/pm/check-governed-merges.mjs --test AGENTS.md - * direct : exit=3, "GOVERNED -- no seat arms auto-merge" - * symlink : exit=0, no output + * Same tree, same defect, same gate -- reached through a symlink it reports the + * clean answer, and a wrapper holding `result.status` cannot tell that apart + * from a pass. `check-skills-paths.mjs` carries the no-realpath spelling, as do + * 27 of its neighbours; see below. * - * and `EXIT_TEST_NOT_GOVERNED` is 0. So through a symlink the register's - * "this PR is GOVERNED, a human merge is the review record" answer and its - * "NOT governed, ordinary queue landing applies" answer are the SAME EXIT CODE. - * A seat reading the status rather than the printed verdict gets a clearance - * to arm auto-merge from a tool that never ran, on the one surface where human - * merge IS the review record (Prime Directive #14). + * The same measurement in objectstack, where this module came from, lands on a + * gate whose exit codes make it worse still: `scripts/pm/check-governed-merges.mjs` + * prints "GOVERNED -- no seat arms auto-merge" with exit 3 directly and exits 0 + * with no output through a symlink -- and its `EXIT_TEST_NOT_GOVERNED` is also + * 0, so the two opposite verdicts collapse to one status and a seat reading the + * status gets a clearance to arm auto-merge from a tool that never ran. That + * file and that directive number are objectstack's; neither exists here. * * ## The two other directions the copies failed in * @@ -70,27 +81,54 @@ * it, exported so the predicate is pinned by cases rather than trusted by * reading. * - * `scripts/check-entry-guard.mjs` enforces this: a `process.argv[1]` in an - * entry-guard position anywhere in `scripts/**` that is not this module is a - * failure. That gate is what stops a TWELFTH spelling, which is the whole - * reason this file exists rather than a one-time sweep. + * ## Nothing here enforces this yet -- the rule is a CONVENTION + * + * An earlier version of this paragraph said `scripts/check-entry-guard.mjs` + * enforces the rule -- that a `process.argv[1]` in an entry-guard position + * anywhere in `scripts/**` outside this module is a failure. It does not. That + * file has never existed in this repository. It exists in objectstack, wired + * there as `check:entry-guard`; the port (#5984) brought this module and its + * prose, and neither the gate nor the sweep the prose describes. Measured on + * `7c96c9420`: + * + * git grep -n 'check-entry-guard' -> ONE hit: this paragraph + * git grep -l 'isEntrypoint' -- scripts -> this file, and ONE adopter + * git grep -l 'process.argv\[1\]' -- scripts -> 30 files + * + * The one adopter is `scripts/pm/check-half-states.mjs`, which arrived in the + * same PR. The 30 files are this one plus TWENTY-NINE hand-typed guards: 28 + * `.mjs`, in NINE textually distinct spellings and not one carrying a realpath + * leg -- including the exact percent-encoding spelling this header warns about + * above, still in `check-node-esm-load.mjs:847` -- and `scripts/shadcn-sync.js`, + * the only hand-typed guard here that does carry the realpath leg. + * + * A gate is still the right answer: without one, converting those 29 is a + * one-time sweep that starts rotting the day it merges, because nothing stops a + * THIRTIETH spelling from being typed next week. Both halves -- the gate and + * the sweep -- are tracked in #6092. Until that lands, `isEntrypoint` here is a + * convention: reach for it in new scripts, and do not read this file as + * evidence that the tree already has. + * + * ## The siblings, and why the duplication is deliberate * - * ## The sibling in `packages/cli`, and why the duplication is deliberate + * objectstack's `packages/cli/src/utils/invocation.ts` exports `isProcessEntry`, + * the same predicate for the same reason (its header cites this defect). Note + * the repo: objectui's `packages/cli` has no such util, and `isProcessEntry` + * appears nowhere in this tree, so the pairing below is a CROSS-REPO obligation + * rather than a local one. * - * `packages/cli/src/utils/invocation.ts` exports `isProcessEntry`, the same - * predicate for the same reason (its header cites this defect). It is NOT - * imported here and this is not imported there: `scripts/` runs as plain .mjs + * Why the copies are not collapsed into one: `scripts/` runs as plain .mjs * against a possibly-unbuilt tree, and making the whole tooling layer depend on * a package build to answer "was I run?" trades this bug for a worse one. * - * The duplication is therefore structural, but DIVERGENCE is not allowed -- - * two predicates answering this question differently is precisely the defect - * being closed. Both carry the same two legs: realpath for symlinks, and - * directory resolution for `node `. Change one, change the other. + * The duplication is therefore structural, but DIVERGENCE is not allowed -- two + * predicates answering this question differently is precisely the defect being + * closed. All three copies carry the same two legs: realpath for symlinks, and + * directory resolution for `node `. Change one, change the others. */ import { spawnSync } from 'node:child_process'; -import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, relative, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; @@ -164,7 +202,14 @@ export function selfTest() { t('an absent argv[1] is not this module -- the `node --eval` importer', !invokedAs(undefined, SELF) && !invokedAs('', SELF)); t('an exact path is this module', invokedAs(SELF, SELF)); t('a relative path resolving to this module is this module', invokedAs(relative(process.cwd(), SELF), SELF)); - t('an unrelated existing file is not this module', !invokedAs(resolve(SELF, '..', 'js-comment-mask.mjs'), SELF)); + // A neighbour that must really be there. The ported spelling of this case + // named `js-comment-mask.mjs`, which exists in objectstack and NOT here -- so + // it silently became a second copy of the case below it, and both passed. The + // existence assertion is what stops that from happening again the next time + // the named file moves. + const neighbour = resolve(SELF, '..', 'check-control-bytes.mjs'); + t('the neighbour fixture still exists (or the next case tests nothing)', existsSync(neighbour), neighbour); + t('an unrelated existing file is not this module', !invokedAs(neighbour, SELF)); t('an entry path that cannot be read is not this module (no throw)', !invokedAs(resolve(SELF, '..', 'no-such-file-here.mjs'), SELF)); // ── the fixture: a probe reached three ways ────────────────────────────────