fix(test): decide base-path rejection from the server startup line (#93) #94

Merged
rockachopa merged 2 commits from timmy/93-deterministic-basepath-gate into main 2026-11-05 12:07:22 +00:00
Member

Closes #93

What changed

tests/staging-health.test.js decided whether a bad TIMMY_BASE_PATH was accepted by racing the child's exit against an 800 ms timer. On a loaded host the child is still importing modules at 800 ms, gets killed, and the killed child is reported as an accepted base path.

The gate now waits for the child's own startup line (Timmy is listening on http://). That line is the only evidence of acceptance:

outcome meaning
startup line accepted → fail immediately, with that wording
exit before the line rejected → assert non-zero exit and TIMMY_BASE_PATH in stderr
neither within 20 s indeterminate → explicit failure, never "accepted"

RED (observed on main 58c6d86, not staged)

not ok 230 - malformed and escaping base paths are rejected at startup
error: '"timmy-staging" was accepted'
# pass 299
# fail 1

Twice in a row in the full suite on this 4-core host (load ~11.9). The same file alone: 6/6, three runs. The server itself was verified rejecting directly (exit=1, Error: TIMMY_BASE_PATH must be a canonical absolute URL path…), so the failure was the harness, not the validator.

GREEN

  • node --test tests/staging-health.test.js → 6/6
  • npm test → 300/300, twice after the fix (20.7 s and 28.2 s on the loaded host)

Hold-out / mutation check

With the leading-slash branch of resolveBasePath disabled, the repaired test fails in 398 ms:

not ok 6 - malformed and escaping base paths are rejected at startup
    "timmy-staging" was accepted: the server announced it was listening

Validator restored; git status shows only the test file.

Demonstration path

No user-visible feature change, so scripts/record_release_demo.mjs is untouched. Nearest honest effect: the release gate that the morning video/archive handoff depends on now fails only for real reasons and reports the real reason. No UI, inference, medical boundary, or production service change.

Diff / hygiene

Test-only, 34 insertions / 10 deletions in one file. bash scripts/check_diff.sh exits 0; no secrets, generated artifacts, model weights, or medical images.

Closes #93 ## What changed `tests/staging-health.test.js` decided whether a bad `TIMMY_BASE_PATH` was accepted by racing the child's exit against an **800 ms timer**. On a loaded host the child is still importing modules at 800 ms, gets killed, and the killed child is reported as an accepted base path. The gate now waits for the child's own startup line (`Timmy is listening on http://`). That line is the only evidence of acceptance: | outcome | meaning | |---|---| | startup line | accepted → fail immediately, with that wording | | exit before the line | rejected → assert non-zero exit and `TIMMY_BASE_PATH` in stderr | | neither within 20 s | indeterminate → explicit failure, never "accepted" | ## RED (observed on `main` 58c6d86, not staged) ``` not ok 230 - malformed and escaping base paths are rejected at startup error: '"timmy-staging" was accepted' # pass 299 # fail 1 ``` Twice in a row in the full suite on this 4-core host (load ~11.9). The same file alone: 6/6, three runs. The server itself was verified rejecting directly (`exit=1`, `Error: TIMMY_BASE_PATH must be a canonical absolute URL path…`), so the failure was the harness, not the validator. ## GREEN - `node --test tests/staging-health.test.js` → 6/6 - `npm test` → **300/300**, twice after the fix (20.7 s and 28.2 s on the loaded host) ## Hold-out / mutation check With the leading-slash branch of `resolveBasePath` disabled, the repaired test fails in **398 ms**: ``` not ok 6 - malformed and escaping base paths are rejected at startup "timmy-staging" was accepted: the server announced it was listening ``` Validator restored; `git status` shows only the test file. ## Demonstration path No user-visible feature change, so `scripts/record_release_demo.mjs` is untouched. Nearest honest effect: the release gate that the morning video/archive handoff depends on now fails only for real reasons and reports the real reason. No UI, inference, medical boundary, or production service change. ## Diff / hygiene Test-only, 34 insertions / 10 deletions in one file. `bash scripts/check_diff.sh` exits 0; no secrets, generated artifacts, model weights, or medical images.
rockachopa added 1 commit 2026-11-05 10:59:36 +00:00
The old gate raced child exit against a fixed 800 ms timer. Under full-suite
CPU contention the child was still importing modules, so it was killed and
the timeout was asserted as acceptance: 2/2 full-suite runs on main failed
with '"timmy-staging" was accepted' while the file alone passed 3/3 and the
server rejected the value directly.

Acceptance is now decided by the child printing 'Timmy is listening on
http://'; a child that neither exits nor listens inside 20 s is reported as
indeterminate. Rejection assertions (non-zero exit, TIMMY_BASE_PATH in
stderr) still run.

Mutation check: with the leading-slash validation disabled the repaired
test fails in 398 ms with the acceptance message.
rockachopa added 1 commit 2026-11-05 12:07:04 +00:00
The harness derived every server port from a fixed `nextPort = 43100`, which
lives inside this host's ephemeral range (32768-60999). The kernel can already
have handed one of those numbers to an unrelated outbound socket — a Caddy ->
Gitea connection held 43102 — so the spawn died with EADDRINUSE before a single
assertion ran, and the suite reported a failure for a reason that is not the
code under test. Reproduced 3/3 on main; 6/6 with the block moved out of range.

Ports now come from a bind on :0, readiness is the child's own "Timmy is
listening on http://" line rather than a poll deadline, and a port stolen in the
probe-then-spawn gap is retried (4 attempts) instead of failing the release.

Hold-out: with the leading-slash branch of resolveBasePath disabled, the gate
still fails in ~336 ms with "the server announced it was listening" — the
retry cannot make a broken validator pass.
rockachopa merged commit bd4420b4d6 into main 2026-11-05 12:07:22 +00:00
Sign in to join this conversation.