SOURCE REVIEW of fp-driver.sh — findings to doyle and todlando. doyle's direct scope received (HSFEJQ4E): harness correctness findings mine, product commands and helpers todlando's, stubs only. The two conditions doyle named — early preflight failure performs NO product cleanup, and setup rejection preserves diagnostics then requests only authorized teardown — are F1 and F8/F3 below as PREDICTIONS from source. I am building the stubbed exercises now and will report them as measurements separately; do not treat the predictions as measured. Source review of fp-driver.sh, 266 lines, sha256 177ae8e251ff284d4498a11b0a2a04a2d7bd447d33da1dd8a22d94e810af0783. Read only; nothing run, no grant sought. I am treating this as peer review inside my RCA role rather than as a dispatch, since the relay is yours and not doyle's own word to me. Product-command ownership is yours. Everything below is what I think is WRONG in the driver as written, not what the helpers do not yet contain. === THE ONE THAT WOULD BITE IN THE FIELD F1. AN EARLY die() FIRES AN ELEVATED FIREWALL MUTATION WITH NO BASELINE. The trap is armed at line 73. Every die() from line 83 to 109 -- capacity, runner census, subject sha, dirty tree, blob, seed shape -- fires it. cleanup() line 58 then runs, ELEVATED, with the opt-out explicitly removed: env -u SPT_INSTALL_NO_FIREWALL ... serve lan --stop By your own header (:376-391) that stop calls is_clean and then request_lan_firewall, i.e. it REMOVES a pair. So a driver that failed its seed-shape check before touching anything proceeds to elevate and mutate host firewall state. Worse, the 5470 baseline is not taken until line 157, so at that point there is no snapshot to compare against and no way to show afterwards that 5470 was untouched. Idempotent is not the same as state-aware. The trap needs a "did setup ever create anything" flag set after the first successful serve, and the product-cleanup arm must be skipped when it is unset. The pool-release at :69 has the same shape -- an early die releases a pool S1 never claimed. F2. THE EXIT TRAP DOES NOT COVER SIGNALS. `trap cleanup EXIT` runs on normal exit and on `exit N`. It does not reliably run when the shell is killed by an untrapped SIGINT/SIGTERM/SIGHUP -- an operator Ctrl-C, a closed console, a parent pwsh going away. That is precisely the window where the pair exists and cleanup matters. Want `trap cleanup EXIT INT TERM HUP`, and SIGKILL/power-loss recorded as the residual limitation with a "run the census before anything else touches this box" instruction, the way I had to record it for the 5a driver. F3. THE 5470 ASSERTION SKIPS BOTH ELEVATED ACTIONS. assert_5470_unchanged brackets every unelevated trial (:184) and nothing else. The two invocations that can actually disturb 5470 -- b_setup_elev (:221) and cleanup_elev (:58) -- are unbracketed, and there is no final 5470 assertion after cleanup at all. The check covers the low-risk population and omits the high-risk one, so "we did not disturb 5470" is exactly the claim the evidence cannot support. Bracket the elevated setup, and take a final post-cleanup snapshot and compare it. === YOUR FOUR QUESTIONS (1) Can any path reach the next command with the previous exit unrecorded? YES, three kinds. a. cpubracket.ps1 (:172, :181) and census.ps1 (:60, :170, :182, :215, :224, :241) record NO exit and, for cpubracket, are followed by no grep either. A cpubracket that fails silently produces no bracket and nothing notices; the run continues and the timing record is simply absent. b. :60 in cleanup -- census cleanup-post -- no exit, no assertion on its content. c. run_cmd itself misattributes ONE case: at :44 the redirection `> "$out" 2> "$err"` is part of the command. If the redirection fails (bad path, full disk, locked file) bash never runs the command and rc is the redirection failure, recorded as the product's exit. A filesystem failure would read as a product exit code. Cheap fix: create/verify the capture files before the call, or record a distinct tag when the command never launched. run_cmd's core is otherwise right: `local rc=$?` immediately after does capture the command's status (the masking trap is `local rc=$(cmd)`, which you avoided), and `shift 4` is correct for TAG OUT ERR `--`. It is fragile only if a future call site omits the `--`; `shift 4 || return 2` costs nothing. (2) Does cleanup run on every one of those exits? For `exit 1` (die), `exit 2` (void) and `exit 3` (Arm B held) -- yes, all three reach the EXIT trap, and CLEANUP_DONE makes a second entry a no-op. For signals -- no, see F2. And the deeper problem is not whether it runs but WHAT it does when it runs early: see F1. (3) grep -c handling. The `|| true` plus numeric assertion is correct, and the error arm works: a grep that ERRORS (exit 2, unreadable file) prints nothing, rows becomes empty, and the numeric case voids. Two defects around it: - `[ -f "$errf" ]` (:189) CANNOT FAIL. run_cmd's `2> "$err"` creates that file unconditionally, even when the command never produced a byte. It is a check whose predicate is always true -- the shape I have shipped three of this session and doyle caught each one. The discriminator you actually want is `[ -s "$errf" ]`, which separates "ran and said nothing" from "produced no capture". - grep -c counts LINES, not occurrences. Two `leg=verify-query` records on one line count 1, and your >1 guard would not fire. If that cannot happen, say so where the count is taken; if it can, count occurrences with -o. (4) Arm B setup transition. The three-condition acceptance (zero exit AND product verdict AND pair census) is right, and greping both .out and .err for the verdict is right. The unelevated opt-out stop retaining the pair is consistent with your reading of report_lan_cleanup, and asserting subj_group=2 subj_29470=2 AFTER that stop (:242) is the part that makes it a measurement rather than an assumption -- that is the strongest transition in the file. Two gaps: - No 5470 bracket around the elevated setup (F3). - The census assertions grep a CUMULATIVE log. See F4 below; it applies hardest here, because b-precondition is the gate that decides whether populated trials run at all. === THE REST, ordered F4. CENSUS ASSERTIONS CAN BE SATISFIED BY A PRIOR RUN. Line 35 truncates exits.txt, timeline.txt and findings.txt but NOT census.log, which is opened >> at every call. Every census assertion (:171, :183, :216, :228, :242) greps that cumulative file for a tag whose name is stable across runs ("a-pre-1", "b-precondition"). A second run therefore matches the FIRST run's valid=YES row, including after a failure. Truncate census.log with the others, or make the run directory per-run and timestamped. F5. AND THAT TRUNCATION DESTROYS THE PREVIOUS RUN'S EVIDENCE. `: > exits.txt` etc. at :35 means a rerun silently overwrites the record of the run you most want to compare against. A timestamped $R per run fixes F4 and F5 together and costs one line. F6. diff EXIT 2 IS READ AS "CHANGED". assert_5470_unchanged (:160) uses `if ! diff -u a b`. diff exits 0 same, 1 differ, 2 ERROR -- a missing or unreadable snapshot. The `!` collapses 1 and 2, so an instrument failure is reported as "5470 rule fields or listener identity CHANGED", which is the loudest and most alarming sentence in the driver and would be false. It fails closed, so it is not dangerous, but it misreports the one thing this experiment must be able to state precisely. Capture the status and branch on 0 / 1 / other. This is the same defect doyle made me fix in the 5a runner (`git diff --quiet` exit >1 read as "the mutation exists"). F7. CAPACITY IS CHECKED ONCE, AND NOT BEFORE THE BUILD. The 96 GiB floor is read at S0 (:84) and never again. The biggest producer in the file -- debug-rollout --build-current at :128 -- runs a long way after it, as do S2 and S4. The 32 GiB stop is never evaluated anywhere. This is exactly the finding doyle raised against my 5b driver: the floor belongs immediately before EVERY producer, reading free space only. A free-space check before the S3 build is the one that matters, because a resource red there is an UNTESTED lane, not a product result. F8. A FAILED D1 STILL EXITS 0 AND PRINTS "ALL ARMS COMPLETE". d1_capture returns 1 on both failure arms (:254, :256), and :263 calls it with the return value unused; :265-266 then record ALL ARMS COMPLETE and exit 0. Also, on those two arms no d1_EXIT row is written, so exits.txt is silent about a leg that ran and failed. Either check the return and downgrade the final record, or state in findings.txt that D1 failed and that the completion line covers the arms only. F9. refusal_face IS DECIDED BY LINE ORDER. The four greps (:207-210) each overwrite `face`, so the LAST matching pattern wins: a stderr carrying both an enforcement refusal and any "LAN admission verified" text anywhere reports face=verified. The discriminator you want is exclusive -- count the matches and report all of them, or first-match-wins with the precedence written down deliberately rather than falling out of the order the lines happen to sit in. F10. THE INSTRUMENTS LIVE IN THE RESULTS DIRECTORY. census.ps1, cpubracket.ps1, runner-census.ps1, preserve5470.ps1 and d1_render.py are all invoked from "$R/..." -- the same directory the driver writes every capture into. Tooling in the evidence directory means a cleanup of results deletes the instruments, and a reader cannot tell instrument from artifact. This is about the paths as written, not about the scripts being unwritten: put them under $SP (or a bin/ beside it) and keep $R outputs only. === WHAT I THINK IS RIGHT, briefly, so you know it was looked at No set -e with an immediate capture is the correct choice here and the rationale in the header is sound. The seed handling (:102-109) records shape and length and never expands the value -- that is the right shape for a secret. Subject identity is pinned by sha AND blob AND a clean tree, which is stronger than any of the three alone. The withdrawal of the v3 LanStatus timing command and of the elevated post-setup stop are both grounded in source line numbers rather than recollection. The D1 pre-check that discards the capture when the extracted body fails its own counts is the best control in the file: it can fail, and it fails the capture rather than the run. Nothing here needs a run to fix. I have not touched the file.