Skip to content

fix(devx): the self-test floor probe writes its mutated copy outside the walked tree, imports rewritten to the originals - #15613

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-15515-floor-probe-copy-outside-tree
Sep 5, 2026
Merged

fix(devx): the self-test floor probe writes its mutated copy outside the walked tree, imports rewritten to the originals#15613
baozhoutao merged 2 commits into
mainfrom
claude/issue-15515-floor-probe-copy-outside-tree

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15515

measure-self-test-floor's probe wrote its mutated copy beside the original, inside scripts/. That answered "do relative imports and repo-root resolution still resolve?" by construction and broke a question nobody had asked it: a near-duplicate of a gate, sitting under scripts/, is a finding for any gate whose own work is to walk that tree. That gate's own BASELINE then exits non-zero, the precondition (#15391) ends the probe, and the row reads NOT MEASURED · baseline run failed (exit 1) — a limit of the INSTRUMENT recorded as a property of the FILE.

Reproduced by hand on this branch's base, scripts/check-pnpm-filter-targets.mjs:

node scripts/check-pnpm-filter-targets.mjs --self-test                      # exit 0
cp scripts/check-pnpm-filter-targets.mjs scripts/.self-test-floor-probe-check-pnpm-filter-targets.mjs
node scripts/check-pnpm-filter-targets.mjs --self-test                      # exit 1
#   "and the checked-in tree is clean (dead: @objectstack/adapter-hono, ...)"

The placement, and the three things it now answers explicitly

The copy goes into a fresh mkdtemp directory under the OS temp dir — outside every walked tree — and each thing the old placement answered implicitly is answered by a rewrite. Every rewrite is applied only where scanSource says the text is real CODE: several gates in this tree feed themselves fixture STRINGS containing import statements and the word import.meta.url, and rewriting one of those would change what the gate SCANS rather than where the copy resolves.

  1. Relative specifiersfrom './x.mjs', import('../y.mjs') and the bare import './z.mjs' — become absolute file:// URLs of the ORIGINAL's neighbours. Measured over the census: 320 from and 5 dynamic in real-code positions.
  2. import.meta.url (and .dirname / .filename) becomes a literal naming the ORIGINAL, so new URL('..', import.meta.url) ROOT resolution (43 sites), createRequire (8) and the importerUrl that requireDependency turns into its fromDir (51) all answer as they did. The one exception is the argument of isEntrypoint(...) — 145 of the census's dispatches sit behind that call, and answering it about the original would leave every one of those copies parsing, running nothing, printing nothing and exiting 0: a whole-census false DEFEATED.
  3. Bare specifiers resolve by walking up from the file, so the temp directory gets a node_modules symlink to the nearest one above the ORIGINAL. Without it import 'typescript' (4 members) and the dynamic import('yaml') inside requireDependency die in module resolution — non-zero AND speaking, which this file scores HELD. That is the flattering direction, so it is closed rather than accepted.

cwd stays ROOT, so nothing resolved from process.cwd() moves. ⛔ No walking gate was weakened to ignore the copy, and there is no roster of "affected gates".

Relocation census

count
census members 179
relocate cleanly 178
keep the OLD placement, reason recorded per row 1

The one exception is scripts/ts-parse.mjs, whose row now publishes it rather than hiding it:

"placement": "beside",
"placementWhy": "import.meta.resolve resolves from the running file's own url, which is not a literal to rewrite"

Its verdict is unchanged (HELD) — the old placement never hurt it. A template-literal relative specifier is refused the same way; no member carries one today.

Rows that moved

Probed 40 rows — every census member identifiable as walking scripts/, plus scripts/ts-parse.mjs — before and after, through main()'s own entry selection. One moved, and only in the direction this card is about:

row before after
scripts/check-pnpm-filter-targets.mjs NOT MEASUREDbaseline run failed (exit 1), head ✗ check-pnpm-filter-targets --self-test — 1 failure(s) DEFEATED — baseline exit 0, mutated exit 0, 0 bytes printed
the other 39 HELD / DEFEATED / NOT MEASURED(by-hand) identical verdict, identical mutatedHead and mutatedBytes

The 39 unchanged rows are the load-bearing half: they include isEntrypoint-guarded members (check-nul-bytes, check-entry-guard, …) and requireDependency users (check-doc-frontmatter, check-merge-queue-triage-outcome, …), so the rewrites did not move a single existing reading.

The verdict the moved row now yields is a real defect in that gate, not an artefact: check-pnpm-filter-targets --self-test prints NOTHING and exits 0 when its selfTest() leaves early. It is the same class as #15153, and is filed separately as #15614 rather than folded in here.

--json (the static census) is byte-identical to base. Probe rows gain placement / placementWhy only on the exceptional row; a relocated row's field set is unchanged.

#15573 — the extended-timeout reading (that card remains open)

scripts/pm/dispatch-gates.mjs read NOT MEASURED · killed by SIGTERM at the default 120 s budget. Measured ONCE at 900 s, narrowed to that row, after this change:

HELD  scripts/pm/dispatch-gates.mjs  entry=selfTest
      baselineExit=0  mutatedExit=1  mutatedBytes=200
      mutatedHead="✗ dispatch-gates self-test: selfTest() returned without reaching its verdict,"

So yes on both counts: with the copy out of scripts/ the baseline now passes, and the mutated run reaches a verdict. 425 s wall clock on a shared box (other unlocked work running alongside), so read it as "does not fit 120 s", not as a calibrated figure. Its ENTRY_BY_HAND note is corrected to say the row is a budget away, not a property of the file. #15573 is not addressed here.

Controls

Both new behavioural controls run in runControls(), on every invocation, fixtures only.

  • (a) A fixture gate whose self-test walks its own directory and refuses anything unexpected there. Relocated, it reads HELD — a verdict is available at all — and its module-level line is asserted to be exactly fixture: dir=walked help=helped dep=linked: dir= reads the import.meta.url rewrite, help= the relative-specifier rewrite, dep= the node_modules link, in one spawn.
  • (b) The SAME fixture with the copy forced beside the original (placement: 'beside') reads NOT MEASURED · baseline run failed (exit 1), and a third assertion pins that the two placements separate the fixture's own baseline (relocated exit 0, beside exit 1) — so the pair says something about WHERE the copy was written.
  • Eleven pure-function controls read relocateSource directly: each rewritten shape, the node: builtin left alone, the isEntrypoint argument left alone, an import statement inside a fixture STRING and the word import.meta.url in prose both left alone, and the two refusal shapes (import.meta.resolve, a template-literal specifier).

Verification

Exit codes captured before any pipe; verdict lines quoted as the gate printed them.

check result
node scripts/measure-self-test-floor.mjs exit 0 · measure-self-test-floor: 179 file(s) under scripts/ dispatch on --self-test. · no CONTROL FAILED / CONTROL FIXTURE INVALID line
--json vs base 17ec4b1e1 byte-identical (diff clean)
runControls() failures 0
narrowed probe, 40 rows, before/after 1 moved row, 39 identical (table above)
#15573 row at 900 s HELD (quoted above)
22 derived gate commands all exit 0 (list below)
pnpm check:pm-dispatch-gates exit 0 · ✓ dispatch-gates self-test: 1445 cases pass. (no case edited)
node scripts/check-published-list-mirrors.mjs (+ --self-test) exit 0 · OK: 1 published list mirror(s) match their constants line for line. · All 37 self-test cases passed.
eslint, narrowed + proven exit 0, 0 errors / 0 warnings

Gate family derived with node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack at fd79ecc76 (1 path: scripts/measure-self-test-floor.mjs) — 22 commands, run individually, every one exit 0:
check-reference-carrier-shape (+--self-test), check-ci-filter-parity, check-closing-keyword-parity (+--self-test), check-comment-mask-corpus, check-self-test-wired (+--self-test), check-whole-set-label-write (+--self-test), check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs, check:driver-memory-census, check:entry-guard, check:nul-bytes, check:parse-guard, check:pnpm-filter-targets, check:ratchet-remedy-authority, check:refd-timer-probe, check:watch-hint-literal.

One reading was NOT MEASURED and re-taken rather than reported: pnpm -s check:published-list-mirrors exited 254 printing nothing — there is no such script; the roster spells it node scripts/check-published-list-mirrors.mjs. Run that way it is exit 0, quoted above. (It is one of the six roster families whose list sits under scripts/, where a silent verdict is evidence in neither direction — so it was read, not assumed.)

NOT MEASURED (named, not counted as passes): the three families whose argv takes a value only a CI run supplies (check-cross-package-test-inputs --union-into ..., check-shard-attestation --emit ..., check-test-completeness ...), the 16 families derived against a changeset path that does not exist (none is owed — scripts/** publishes nothing), and the always-runs workflow tail.

Whole-repo pnpm lint was narrowed, and the narrowing is proven, not assumed: (1) the file is in ESLint's own linted population — eslint --print-config resolves a config for it, exit 0; (2) --format json reports exactly 1 file linted, 0 errors, 0 warnings; (3) this repo runs one eslint.config.mjs which never enables type-aware linting for ANY file (no parserOptions.project--print-config returns null for it; stated and independently measured at eslint.config.mjs around line 327), so a one-file diff cannot move the verdict on any untouched file.

Ablations

Both mutate a COMMITTED tree, prove the mutation landed on disk by counting the anchor and the injected text, restore with git checkout HEAD -- ABSOLUTE_PATH under an EXIT INT TERM trap using absolute paths, and prove the restore by blob hash and an empty git diff HEAD.

ablation anchor→injected on disk result
default placement forced back beside the original 1→0 / 0→1, hash e59375d0fe596bba29 exit 1 — control (a) reds with the card's exact reading: read NOT MEASURED (baseline run failed (exit 1)); the printed-line control reds; the placement-separation control reds
the isEntrypoint(import.meta.url) exception removed 1→0 / 0→1 exit 1 — the static control reds (the isEntrypoint(import.meta.url) argument was rewritten) and the walking fixture reads DEFEATED instead of HELD — the predicted whole-census false direction, caught

Both restores verified: hash=e59375d0f007e75cd430fd3d5a01dd3486ac33bc matching HEAD, git diff HEAD empty.

No changeset: scripts/** publishes nothing from any package (skip-changeset).

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…the walked tree, imports rewritten to the originals (#15515)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…get, not the placement (#15515)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff origin/main...claude/issue-15515-floor-probe-copy-outside-tree (head fd79ecc76, two commits; one file, scripts/measure-self-test-floor.mjs, +337/−17), not the self-report. git merge-tree against the queued PR #15601 (which touches the same instrument's ledger note in dispatch-gates.mjs, not this file): 0 conflicts.

What I checked in the diff:

  • relocateSource(source, absFile): rewrites, only where scanSource says the byte is CODE, relative from / bare import / dynamic import() specifiers to absolute file:// URLs of the ORIGINAL's neighbours, and import.meta.url / .dirname / .filename to literals naming the original — with the one measured exception, the argument of isEntrypoint(…) (145 of 179 dispatches sit behind it; rewriting it would make every copy run nothing and score a false DEFEATED). Template-literal specifiers and import.meta.resolve are refused (blocked), never guessed.
  • probeEarlyReturn(…, { placement = 'relocated' }): the copy goes to an mkdtemp dir outside the repo with a node_modules symlink to the nearest one above the original (so import 'typescript' and requireDependency's dynamic import('yaml') resolve); a refused or failed relocation falls back to the beside placement and the row PUBLISHES placement: 'beside' + placementWhy — never silent. cwd stays ROOT; the temp dir is removed in finally.
  • Controls: a RELOCATION_SPECIMEN pins every rewrite shape and both non-rewrites (fixture string, prose, node: builtin, the isEntrypoint argument); a WALKING_GATE fixture (walks its own dir, refuses a stray, imports a helper by relative path and a dep through node_modules, guarded by isEntrypoint) reads a verdict under relocation and baseline run failed under the old placement — both directions; no walking gate weakened, no roster.
  • ENTRY_BY_HAND: scripts/ts-parse.mjs keeps the beside placement (import.meta.resolve) with the reason on the row; the dispatch-gates.mjs note now records the budget (measure-self-test-floor's probe spawns with a 120s timeout, shorter than the self-tests it measures — dispatch-gates.mjs reads NOT MEASURED, killed by SIGTERM #15573), not the placement.

Dev's verification quoted (on fd79ecc76): census 179 file(s), exit 0, no control failure; --json byte-identical to the base capture; narrowed probe over 40 rows — exactly ONE moved: check-pnpm-filter-targets.mjs NOT MEASURED / baseline run failed (exit 1)DEFEATED (the card's specimen), 39 identical; #15573 reading at 900 s: HELD … mutatedHead "✗ dispatch-gates self-test: selfTest() returned without reaching its verdict" (425 s wall) — the baseline now passes and the mutated run reaches a verdict; ablation A (relocation removed) reds RELOCATION CONTROL FAILED: … read NOT MEASURED (baseline run failed (exit 1)), ablation B (isEntrypoint exception removed) reds the predicted false DEFEATED, both restored by blob e59375d0f + empty git diff HEAD; derived family 22 commands all exit 0 + check:pm-dispatch-gates 1445 cases pass. Three CI-only families NOT MEASURED, named. Full --probe not run (container cap) — stated.

Finding uncovered by the repair, filed by the dev: #15614 (check-pnpm-filter-targets self-test has no verdict handshake — the DEFEATED the relocation exposed).

Flipping ready + enabling auto-merge. Fixes #15515. #15573 keeps its own card with this reading attached.


Generated by Claude Code

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