From b9d77258d67ffc6687b84ee687f049d8abd16a35 Mon Sep 17 00:00:00 2001 From: ss-dev-01 <11939edb7df583f855dbef923f2358f1184538f88ca452e19e7e35e42ad6d796@buzz.block.builderlab.xyz> Date: Mon, 24 Aug 2026 16:59:11 -0700 Subject: [PATCH] test(scripts): require the ratchet child to run to completion The child-process guard proved the gate *started* reporting, not that it finished. Status 1 plus the report heading was accepted as a completed policy decision, so a child that printed the heading -- or the whole report -- and then crashed still read as the gate having decided. The eval script now writes a fixed sentinel to stdout after the awaited `runFileSizeCheck` returns, and the harness requires it on both the exit-0 and exit-1 paths. The two questions are separated: the sentinel proves normal completion, and only then does the exit status carry the policy result. Proven against the case the old guard missed. Injecting a throw after the full violation report is emitted but before the gate returns: old guard (heading only) 9 passing / 0 failing <- crash read as a pass new guard (sentinel) 7 passing / 2 failing <- "did not run to completion ... crashed rather than gated" A throw injected immediately after the heading also fails with the same assertion rather than being misattributed to a missing violation line. This is the same defect the PR closes, one level up: a check that cannot distinguish the outcome it reports from a failure to reach it. Signed-off-by: ss-dev-01 <11939edb7df583f855dbef923f2358f1184538f88ca452e19e7e35e42ad6d796@buzz.block.builderlab.xyz> Co-authored-by: Bradley Axen Signed-off-by: Bradley Axen --- scripts/check-file-sizes-core.test.mjs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/check-file-sizes-core.test.mjs b/scripts/check-file-sizes-core.test.mjs index 4d200722c6..bd240e1433 100644 --- a/scripts/check-file-sizes-core.test.mjs +++ b/scripts/check-file-sizes-core.test.mjs @@ -152,6 +152,8 @@ test("every script root governs .mjs alongside .ts and .tsx", () => { // Runs the real desktop rule table against a throwaway repository in a child // process. A child keeps `process.exitCode` and `console.error` out of this // test's own process, where a leaked exit code would mark the whole file failed. +const COMPLETION_SENTINEL = "__file_size_gate_completed__"; + function runDesktopGate({ fixtureRoot, baseRef }) { const result = spawnSync( process.execPath, @@ -166,6 +168,9 @@ function runDesktopGate({ fixtureRoot, baseRef }) { rules, label: "Desktop", }); + // Printed only if the awaited call returned normally. On stdout so it + // cannot be confused with any part of the violation report. + process.stdout.write(${JSON.stringify(COMPLETION_SENTINEL)}); `, ], { @@ -181,15 +186,20 @@ function runDesktopGate({ fixtureRoot, baseRef }) { }, ); // Node exits 1 for an uncaught exception too, so status alone cannot tell a - // ratchet violation from a crash. The runner's only output is its violation - // report, so a failing status must carry that report to count as a real - // violation; anything else is the harness breaking, not the gate deciding. + // ratchet violation from a crash. Split the two questions: the sentinel proves + // the gate ran to completion, and only then does the exit status mean a policy + // decision. Asserting on the report heading alone would accept a child that + // started printing violations and then crashed part-way through. + assert.ok( + result.stdout.includes(COMPLETION_SENTINEL), + `the ratchet child did not run to completion (exit ${result.status}, signal ${result.signal}), so it crashed rather than gated:\n${result.stderr}`, + ); const failed = result.status === 1; if (failed) { assert.match( result.stderr, /file size ratchet failed \(base /, - `the ratchet child exited 1 without reporting a violation, so it crashed rather than gated:\n${result.stderr}`, + `the ratchet child exited 1 without reporting a violation:\n${result.stderr}`, ); } else { assert.equal(