Commit b22db51
fix(scripts): stop the bump self-test reporting a crashed digest probe as a range verdict (#19034)
Fixes #18354
Clause-②: no
## What was wrong
`scripts/bump-objectui.selftest.sh` ran the digest's `--check-walkable`
probe with both
streams discarded, and then hard-coded the failure wording as a
**verdict about the
objectui range**:
```
node "$DIGEST_SCRIPT" ... --check-walkable >/dev/null 2>&1 || walk_rc=$?
...
bad "fixture: the range does not walk BEFORE breaking the blob (rc=${walk_rc}) — not this card's state"
```
`--check-walkable` derives nothing and prints nothing on stdout — the
digest's own
self-test asserts that emptiness — so **stderr was the entire
diagnostic**, and it was
thrown away before anyone could read it. A probe that *crashed* (missing
module, syntax
error, killed process) was therefore reported as "the range does not
walk": a confident,
wrong diagnosis pointing at another subsystem, with the only clue
already gone.
Not hypothetical: in the #16421 round a new import made the digest die,
that wording sent
the dev to investigate shallow clones and `fetch --unshallow`, and the
real cause was a
copy manifest three directories away. Their report calls it the longest
part of the round.
## What changed
One file, both probe sites in `case_5`:
- `check_walkable` runs the probe keeping the child's **stderr** in a
file under the
existing `TMPROOT` (so the EXIT trap still cleans it up). stdout stays
discarded — the
probe writes none.
- `walk_answered` forks on **exit 2 or 3 = a verdict, anything else = it
never answered**.
That criterion is **taken from `bump-objectui.sh`**, which already forks
on exactly it,
rather than invented a second time — so the two cannot drift into two
different ideas of
what "the range does not walk" means.
- `walk_replay_stderr` replays the child's stderr, prefixed, **before**
the `bad` line.
- The no-answer wording says the probe never answered, names the exit
and the two
verdicts, and states that this says nothing about the objectui range.
The header comment's pointer at this card was replaced with a pointer at
the fork that now
exists in the file, so it does not outlive the card as a dangling
reference.
`scripts/bump-objectui.sh` is **not touched**, per the card.
## Scope: why two of the eight `dev/null` occurrences
`dev/null` appears 8 times in the file. Two of them — the
`--check-walkable` probes — are
the carded shape: stderr swallowed **and** a hard-coded failure sentence
that is a verdict
about another subsystem. The other six are not, and are left alone:
| line (pre-change) | what it is | why out of scope |
|---|---|---|
| 242 `git cat-file -e "$blob" 2>/dev/null` | existence probe | the exit
code IS the answer; the message it produces is about the fixture it just
built, not a verdict about another subsystem |
| 261 `git cat-file -e "${sha}^{commit}" 2>/dev/null` | existence probe
| same |
| 265 `git log -1 --format=%s "$sha" >/dev/null 2>&1` | existence probe
| same |
| 273 `git rev-parse HEAD 2>/dev/null` | existence probe, `rc` already
captured and printed | same |
| 374 | the pin file read with `tr`, stderr discarded, falling back with
a `true` on absence | tolerated-absence read; no verdict sentence at all
|
| 431 `break_changeset_blob … >/dev/null` | stdout only | stderr already
flows through, and the function calls `bad` itself with its own evidence
|
Deliberately **not** measured here: whether this shape exists elsewhere
in the repo. The
card claims this one file's two sites, not a population, and a
population would be a new
finding rather than a licence to widen this PR.
## Both directions, measured
Acceptance item 3. Each leg mutates the tree, proves the mutation
reached disk by an
occurrence count, runs the real self-test, and restores under a trap;
every leg ends with
both touched files hashing byte-identical to `HEAD` (`git hash-object`
vs the `HEAD` blob),
and the final `git status --porcelain` and `git diff HEAD --stat` are
both empty.
**Direction A — a real "range does not walk"** (the fixture deletes the
`from` endpoint's
commit object, so the probe returns its verdict **2**):
- before: `✗ fixture: the range does not walk BEFORE breaking the blob
(rc=2) — not this card's state`
- after: the same sentence, now preceded by the probe's own stderr
(`PREREQUISITE NOT MET — an endpoint of … is not present as a commit
object …`,
plus the `fetch --unshallow` / `fetch origin` remedies and git's own
`fatal:` line).
So a real verdict still reads as a verdict about the range. No
regression.
**Direction B — a crash** (the digest imports a name `./invoked-as.mjs`
does not export;
exit **1**, which is neither verdict):
- before: `✗ fixture: the range does not walk BEFORE breaking the blob
(rc=1) — not this card's state` — the wrong sentence, and nothing else.
- after:
```
↳ the walkability probe's own stderr:
| file:///…/scripts/objectui-changeset-digest.mjs:213
| import { isEntrypointTHISEXPORTDOESNOTEXIST as isEntrypoint } from './invoked-as.mjs';
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| SyntaxError: The requested module './invoked-as.mjs' does not provide an export named 'isEntrypointTHISEXPORTDOESNOTEXIST'
| at ModuleJob._instantiate (node:internal/modules/esm/module_job:226:21)
…
✗ fixture: the walkability probe never ANSWERED before the blob was broken (exit 1; its verdicts are 2 and 3) — this says nothing about the objectui range; read the probe's stderr above
```
Both directions were also driven through the **second** probe site
(breaking the walk, and
breaking the digest, between the two probes):
- A at site 2, before and after: `✗ fixture: --check-walkable now exits
2 — the blob deletion broke the WALK, not just the blob read` (after:
with the probe's stderr above it).
- B at site 2, before: the same sentence with `exits 1` — wrong. After:
`✗ fixture: the walkability probe never ANSWERED after the blob deletion
(exit 1; its verdicts are 2 and 3) — the WALK is UNMEASURED here, not
broken; read the probe's stderr above`, with the SyntaxError replayed.
Note on the vehicle: removing an import *specifier* outright cannot
reach these probes —
`firstPartyModuleClosure` throws on a first-party module that does not
exist, so
`new_framework_with_digest` dies first (the #16421 hardening working). A
missing *named
export* from an existing module is the same ERR-class load failure and
does reach them.
## Verification
| run | exit |
|---|---|
| `pnpm check:objectui-bump` on the pre-change file (restored from the
merge base, restore proven byte-identical afterwards) | 0 — 20
assertions across 5 cases |
| `pnpm check:objectui-bump` after the change | 0 — 20 assertions across
5 cases |
| `bash scripts/bump-objectui.selftest.sh` directly, after the change |
0 (same run; the gate is exactly this command) |
Gate families derived in-worktree with
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`
(1 changed path vs merge base `72c164050`): **26 commands**. 25 ran
green here (exit 0),
including `check:objectui-bump`, `check:nul-bytes`, `check:parse-guard`,
`check:entry-guard`, `check:self-test-wired`,
`check:scripts-symbol-anchors`,
`check:bash32-floor` and `check:agent-test-spelling`. The 26th,
`check:pm-dispatch-gates`, was run detached and waited on to completion:
**exit 0**, and it
printed `the battery took 999.5s on this box` (a contended box; the
prescription quotes
430-450s). The tool's own warning applies: that list is **not** a
complete account of what
CI runs on this PR.
`pnpm lint` is not a reading for this diff and is not claimed as one:
eslint's universe is
JS/TS, and the only changed file is a `.sh` file it does not lint.
`skip-changeset` was **measured, not asserted**: the root manifest is
`private: true` with
no `files[]`, and no package's `files[]` names anything under the
repo-root `scripts/`
directory (positive control: the same loop prints `dist` for 5+
packages). Nothing this
diff touches is published.
## Acceptance notes
- Observation, not filed: the same "swallow both streams, then assert a
hard-coded
sentence" shape may exist elsewhere in this repo. This PR deliberately
did not sweep for
it, because the card claims two sites and not a population.
- Observation, not filed: the two probe sites both re-run the identical
`--check-walkable` invocation, differing only in which side of the blob
deletion they
sit on. They are now one helper; collapsing the surrounding assertions
further would
change what case 5 pins and is not in scope here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent abd63ed commit b22db51
1 file changed
Lines changed: 63 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | | - | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
88 | 91 | | |
89 | 92 | | |
90 | 93 | | |
| |||
291 | 294 | | |
292 | 295 | | |
293 | 296 | | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
294 | 342 | | |
295 | 343 | | |
296 | 344 | | |
| |||
421 | 469 | | |
422 | 470 | | |
423 | 471 | | |
424 | | - | |
425 | | - | |
| 472 | + | |
426 | 473 | | |
427 | | - | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
428 | 480 | | |
429 | 481 | | |
430 | 482 | | |
| |||
433 | 485 | | |
434 | 486 | | |
435 | 487 | | |
436 | | - | |
437 | | - | |
| 488 | + | |
438 | 489 | | |
439 | 490 | | |
440 | 491 | | |
441 | | - | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
442 | 498 | | |
443 | 499 | | |
444 | 500 | | |
| |||
0 commit comments