Uh oh!
There was an error while loading. Please reload this page.
fix(devx): burn the KNOWN_IMPORT_UNSAFE ledger 10 -> 5 — a main() for the five scripts/ modules that ran on import - #11917
Conversation
…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.
…ms 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.
… 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.
…f 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.
…s 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.os-steve
commented
Aug 25, 2026
ACCEPT. Flipped out of draft ( The ledger, exactly as scopedMain held 10; this removes exactly 5, and the 5 left standing are precisely the ones the dispatch fenced: Zero additions — shrink-only held. Both fences verified by name against the changed-file list (0 hits each; control: ⭐ You corrected my dispatch, and you were rightI wrote that all five need a real
Deleting it rather than preserving it was the right call — the card said it "looks stale … worth checking rather than preserving," and you checked. The Two methodology notes
Good catch on parse-guard: sitting 1 recorded it as unreachable-by-construction and reasoned it in; on this diff Also correct: re-running the ratchet's BEFORE probe after the first attempt terminated on a missing-eslint prerequisite in a ⛔ The card stays OPEN — Generated by Claude Code |
Part of #10667
Sitting 2 of the
KNOWN_IMPORT_UNSAFEburn-down. Sitting 1 (#10704) took the ledger 35 → 10; this one takes it 10 → 5, on the five files that needed a realmain()extraction rather than a wrap.The card stays open — three conditional leakers and two fenced files remain.
The ledger
origin/mainat claim time,fd50e59e7)Removed, one commit each:
scripts/pm/check-governed-prose.mjsprocess.exit()scripts/checklist-select.mjsprocess.exit(2)mid-importmain()+selfTest()scripts/check-release-page-status.mjsmain()scripts/check-error-status-conformance.mjsmain()scripts/check-query-options-erasure-ratchet.mjsasync main()What the leak actually was, measured
A child process imports each file for its exports alone, with clean argv (target passed by env so nothing looks like a flag), and prints one sentinel after the import returns.
check-governed-prose.mjschecklist-select.mjscheck-release-page-status.mjscheck-error-status-conformance.mjscheck-query-options-erasure-ratchet.mjsThe last row is the silent-success direction the rule exists to remove:
ensureStackHeadroom()re-execs and then callsprocess.exit(status), so importing that module replaced the importer's process with a fresh run of this gate — and the importer's caller reads exit 0.node_modules-free comparison worktree, where it terminated on the missing-eslintprerequisite instead — a real termination, but not the one being claimed. It was re-run against the base version of the file in the tree that hasnode_modules, which is the row above.CLI behaviour is byte-identical — the load-bearing proof
A
main()extraction moves executable lines where sitting 1's wrap did not, so every path was compared byte-for-byte on stdout, stderr and exit code, against the pristine base tree atfd50e59e7:check-governed-prose.mjs--self-testchecklist-select.mjs--self-test·all --json·area:approvals·all --include-blocked·nope.xxx(no-match exit 1)check-release-page-status.mjs--self-test· both paths ofcheck-release-section-coverage.mjs, the sibling that reads this modulecheck-error-status-conformance.mjs--self-test·--report·--update— for--update, the rewritten baseline artifact is byte-identical too, not just the stdoutcheck-query-options-erasure-ratchet.mjs--self-testAll legs
cmp-clean. Each file's before-capture was also run twice and pinned deterministic before anything was edited.The dead argv workaround — checked, not preserved
The card flagged
check-governed-prose.mjs's module-scopeprocess.argvmutation as "looks stale — worth checking rather than preserving". It is genuinely dead, and it is deleted:check-governed-merges.mjsnow guards both of its module-scope triggers oninvokedDirectly = isEntrypoint(import.meta.url)—main()at:1201and its self-test at:1614. On an import path neither can fire, whatever argv says. Measured rather than reasoned: importing the sibling with--self-testplanted inprocess.argvruns no self-test, prints nothing, and returns its 34 exports.The mutation was never free —
process.argvis process-global, so it edited the argv of whatever importedcheck-governed-prose.mjs, for the duration of the import. The dynamicimport()itself stays, becausescripts/pm/dispatch-gates.mjsreads that edge.Two corrections to sitting 1's brief
check-governed-prose.mjsdid not need amain()extraction. Once the dead argv mutation goes, its entire import surface is one tail dispatch — it took the cheap inverted guard. Four of the five needed the extraction, not five.check:parse-guardis no longer unreachable for this diff. Sitting 1 recorded that the derivation cannot see import edges and the family had to be reasoned in. On this diffscripts/pm/dispatch-gates.mjsmatched it directly (scripts/**gate source), and it is in the derived list below. It was run either way.Deliberate non-changes
tenincheck-entry-guard.mjs's scan-surface docblock stays. It reads "The only literals this file carried were the tenKNOWN_IMPORT_UNSAFEentries" — past tense, anchored to the dispatch-gates never names check:entry-guard for a NEW scripts/ file - its declared population is an enumeration of current members #10784 defect it narrates, authored in fix(devx): let a gate declare a scan surface, not just its baseline artifact #10873 when that was the measurement. Rewriting it tofivewould falsify a record of what was measured on that card; the claim it supports (why the scan surface is declared separately from the roster) is independent of the count.scripts/check-changeset-no-major.mjsandscripts/check-empty-changeset.mjsare untouched.Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465's declared file territory was re-read on the card itself (still open, 4/5 sub-issues complete) and names both by full path. Neither was opened for editing.scripts/qa/qa-rollup.mjs,scripts/objectui-range.mjs,scripts/ts-parse.mjs. They leak on the importer's argv or env, so a clean-argv probe cannot see them; they need a probe built for that.ts-parse.mjsadditionally sits atlint.yml:189, andlint.ymlis held by open PR docs(ci): state the gate-invocation idiom once, and scope the #9465 fence correctly #11905.ensureStackHeadroom()was guarded in place, not moved intomain(). Its docblock 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. (No module-scope declaration between it and the tail constructs an ESLint instance; all threenew ESLintsites are inside functions.)checkHeadroomAdoption()still reads the gate as armed: it tests/ensureStackHeadroom\s*\(/over comment-stripped source, which an indented call satisfies.Inbound importers: zero
Swept
from/import()/require()of all five basenames acrossscripts/,packages/,tools/,examples/,apps/,.github/and rootpackage.json. Every hit is a comment, a ledger line,package.jsonscript wiring, or areadFileSyncof the file's text (dispatch-gates.mjsreadscheck-governed-prose.mjsto extract watch hints — it does not import it). So removing the import side effect is a no-op for every existing caller, and the byte-identical CLI legs above cover the only way any of them is actually reached.Gates
Derived live with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(13 families), all run on the final commit2ca16133a, each quoting its own verdict line. Working tree clean at that sha.check:entry-guard✓ … 105 of them inert on import (5 known-unsafe, ⛔ SHRINK-ONLY)check:parse-guard✓ check:parse-guard: 153 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.check:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 364 file(s) …check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared …check:error-status-conformance✓ every derivable runtime status is documented, and every documented status is reachable.check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces …check:pnpm-filter-targets✓ check:pnpm-filter-targets: 135/168 --filter occurrence(s) … resolvecheck:release-page-statuscheck-release-page-status: OK — 2 GA major(s) in scope (v16, v17) …check:query-options-erasure✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) … none newcheck:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none newcheck-ci-filter-parity.mjsOK: all 96 declared cross-package glob(s) (81 unique) are covered …check-cross-package-test-inputs.mjsOK: 16 package(s) read outside themselves, all declared …check-release-section-coverage.mjs2 finding(s) — advisory, this run still exits 0(pre-existing; byte-identical to the base tree)Plus two the derivation did not name, run by reasoning:
check:platform-checklistnode scripts/checklist-select.mjs --self-test✓ checklist-select self-test: 17 cases pass./check-platform-checklist: OK — 15 areas, 207 itemscheck:nul-bytescheck-nul-bytes: OK (scanned 6631 text file(s) … no raw ASCII control bytes)Every heavy run went through
scripts/pm/os-verify-lock.sh. Exit codes were captured before any pipe, never throughtail.skip-changeset: this diff is dev tooling only — sixscripts/**files, nothing published, no user-visible behaviour change, and the CLI output of every one of them is byte-identical.Generated by Claude Code
Generated by Claude Code