gates: drain the guest capture before SIGTERM and name it on the FAIL line (failure-capture PR-5) - #883
Merged
Merged
Conversation
…kill Adds docker/qemu/lib/gate-capture-drain.sh and wires it into the four gate scripts that run a guest QEMU process to completion (run-aarch64-boot-test-strict.sh, run-aarch64-prod-profile-boot-test.sh, run-x86-boot-tests.sh, run-x86-prod-profile-boot-test.sh): on a non-PASS outcome, each gate now waits (bounded) for an open [BXCAP:BEGIN ...] capture to reach its matching [BXCAP:END ...] before sending SIGTERM/KILL, and reports capture=complete|partial|absent plus the last trace events on the FAIL line and the per-boot facts sidecar. A PASS outcome adds no latency and reports capture=n/a, unread. tests/gate_capture_drain_structure.rs adds a census of the four converted gates' guest-kill sites, a mutation leg proving the check reddens when a kill is relocated above its drain decision, and a functional oracle over the shell library (no QEMU boot) showing the identical race read two ways: capture=partial with the drain disabled, capture=complete with it enabled. Also fixes two forced-fail knobs added along the way from accidentally duplicating a `reached`/`passed` textual occurrence that tests/teardown_structure.rs's x86 gate verdict-discipline ratchets pin at exactly one -- both x86 knobs now change control flow (an early loop break) rather than reassigning the pinned verdict variable. docs/planning/green-program/failure-capture/serials/pr5/ carries the live serials this round's oracle is built from: a script-default strict-gate PASS (capture=n/a), a capture_selftest + BREENIX_STRICT_FORCE_FAIL boot showing capture=complete with real trace events, and a partial-serial fixture cut from that same boot's own bytes. Part of docs/planning/green-program/failure-capture/PLAN-2026-09-05.md section 6, PR-5.
Round doc for failure-trace-capture PR-5 (docs/planning/green-program/failure-capture/PLAN-2026-09-05.md section 6): the drain contract and its exact latency bound, the vocabulary departure from the plan's own text (complete|partial, matching the landed PR-3 emitter's own verdict= field, not the plan's complete|truncated), the capture_selftest/no-gate-builds-it guard this round stayed inside, the four forced-fail knobs and the two verdict-discipline regressions they caused and how each was fixed, the six-test structural ratchet, the live-boot oracle shown against a real captured serial's own bytes (both the complete reading and a partial reading cut from the identical bytes), each gate run this round with its facts+capture output, the full 42-suite/676-test structural sweep, and what this round does not claim. Part of docs/planning/green-program/failure-capture/PLAN-2026-09-05.md section 6, PR-5.
…time gcd_drain_and_report's four BREENIX_GATE_DRAIN_*_MS/EVENTS_N overrides were assigned once, at top level, when gate-capture-drain.sh was sourced. A caller that prefixes the env var onto the function call itself (`BREENIX_GATE_DRAIN_SETTLE_MS=50 gcd_drain_and_report ...`) -- the exact shape tests/gate_capture_drain_structure.rs's oracle test uses -- had no effect: the top-level GCD_SETTLE_MS etc. were already fixed to their defaults before that prefix could ever be seen, so the function's body read the cached default, not the override. The oracle test's "tightened for test speed" leg still passed, but only because the un-overridden 300ms default settle also happens to exceed the fixture's 150ms race delay, not because the override reached anywhere. Move the four reads inside gcd_drain_and_report's own body, as `local` assignments evaluated at call time, so a per-call env-var prefix reaches them. Verified the oracle test now genuinely exercises the tightened 50/100/2000ms leg (scripts/run-structure-tests.sh gate_capture_drain_structure, 6/6 passed, ~0.57s -- fast enough that the tightened bounds, not the defaults, ran). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ained file Both aarch64 gates (strict, prod-profile) decided their pass/fail verdict from the live serial file after gcd_drain_and_report's own wait had already run. That wait deliberately keeps QEMU alive up to BREENIX_GATE_DRAIN_SETTLE_MS + BREENIX_GATE_DRAIN_MAX_MS longer so an open BXCAP capture can finish writing, which is real added wall-clock life QEMU did not have before this PR's drain step existed. Scoring the live file after that wait let it move which side of the loop's own deadline a boot landed on in both directions: a poll_exhausted boot could read scored_pass on content that would not have existed under the old, near-zero-gap timing, and -- separately -- a boot the loop's provisional check saw as early_pass kept gcd_pass_report's 0-cost capture=n/a even when the post-kill rescore later disagreed (scored_fail), since the code path as it stood did not re-read the file after that choice was made. Freeze a byte-for-byte snapshot of the serial file the instant the poll loop stops, before the drain-or-pass decision runs, and score the final verdict off that snapshot (DEADLINE_SERIAL / VERDICT_SERIAL_FILE) instead of the live file. gcd_drain_and_report itself still reads the live file, so the capture evidence it reports keeps benefiting from the extra wait; only the pass/fail verdict is pinned to the pre-drain snapshot, so the drain step can no longer change which side of the deadline a boot lands on. The snapshot is removed again once the rescore is done, so a boot's evidence directory keeps the same files it did before this fix. For the early_pass-then-scored_fail case, add gcd_classify_report -- a zero-wait entry point that classifies the (by then dead, so unchanging) live file honestly instead of leaving gcd_pass_report's capture=n/a on a boot that turned out not to pass -- and call it from both gates when the post-kill rescore disagrees with the loop's provisional early_pass read. Verified: scripts/run-structure-tests.sh gate_capture_drain_structure, 6/6 passed; `bash -n` on all three changed files; shellcheck -x on all three (only pre-existing, unrelated warnings, none touching the changed lines). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…late fails report_gate_failure() only drained/classified the guest's BXCAP capture when QEMU_PID was still non-empty, but the main flow clears QEMU_PID to "" right after its own kill -- which runs after the steady-state check succeeds and before ~40 more assertions (the liveness/wedge check, then the teardown census) still have to pass for a real PASS. Any of those later assertions tripping the ERR trap left capture_lines at its initial empty string, so capture_drain.txt was written out blank for a confirmed FAIL -- not even an explicit n/a marker -- defeating the drain feature for exactly the failure shape (a wedge/hang caught by the liveness check) it is most useful for. Add an elif branch: when QEMU_PID is empty but the serial files exist (only reachable once QEMU has launched and the main flow's own kill has already run), classify the frozen files with gcd_classify_report instead of leaving capture_lines blank. QEMU is already dead by then, so no further wait is useful, but the files' real capture state is genuine evidence for a non-PASS outcome. Verified with a same-fixture before/after comparison, not a live boot: a fixture serial file carrying a real open ([BXCAP:BEGIN] with no matching END) capture, driving the extracted, unmodified report_gate_failure() body from both HEAD~1 and this commit against that one fixture. HEAD~1's body writes an empty capture_drain.txt; this commit's body writes capture=partial:seq=9:edge=FAULT:cpu=0 -- the same before/after contrast gate-capture-drain.sh's own oracle test uses for its mutation. `bash -n` and `shellcheck -x` pass on the changed file (only pre-existing, unrelated warnings from prior rounds). A live x86 prod-profile boot was not run for this change (~8min on beast per CLAUDE.md). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
run-x86-boot-tests.sh preserves a failed boot's serial_kernel.txt/ serial_user.txt pair plus capture_drain.txt under breenix_x86_boot_tests_failures/<timestamp>_<pid>/, but the importer only recognized three aarch64-only failure containers (breenix_aarch64_strict_failures, breenix_prod_profile_failures, breenix_testing_profile_failures) -- this new directory was invisible to `breenix-runs import`. The directory name also carries a trailing `_<pid>` that parseTimestamp's exact-match date format cannot parse on its own. Add isX86BootTestsFailureRunDirectory / x86BootTestsFailureRunEntry alongside the existing isProdFailureRunDirectory / prodFailureRunEntry pair, reusing the generic serialSources/captureSources helpers importGateIteration already uses (this directory's shape -- an arbitrary set of serial_*.txt files plus sidecar captures -- already matches what those scan for, unlike the aarch64 prod entry's single hard-coded serial.txt). Add timestampFromPidSuffixedName to strip and validate the `_<pid>` suffix before handing the remainder to parseTimestamp, and wire the new container name into isPreservedFailureContainer / profileForFailureContainer / preservedFailureEntries / a standalone importPath entry, mirroring the existing prod-profile wiring at each site. run-x86-prod-profile-boot-test.sh's own breenix_x86_prod_profile_failures directory uses the same `_<pid>`-suffixed naming and remains unrecognized by the importer -- that gap pre-dates this PR (the review finding this commit closes explicitly scopes to the directory this PR itself introduces) and is not fixed here. Verified with two new ImporterTests cases -- a pid-suffixed run directory importing both serials with the correct SerialStream (com1/com2) plus the capture_drain.txt sidecar, and a malformed (non-numeric) pid suffix being correctly unrecognized rather than misparsed -- plus the full existing suite: `swift test` in tools/breenix-runs, 90/90 passed, 0 failures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Section 5's quoted transcript read "claim-lint: clean (11 file(s) checked, changed hunks vs 2be24e3)". Re-running python3 scripts/claim-lint.py directly at this round's own HEAD (9f3d710), via a detached worktree, reports 12 files, not 11 -- both runs say clean, so this is not a live gate failure, but the doc's own quoted reproduction did not reproduce byte-for-byte at the commit it is committed alongside. Most likely explanation: the doc's own file is the 13th changed path (12 checkable after the one extension-skipped .gitattributes), captured before that file had landed in the tree. Corrected the quoted line to 12 and added a note explaining the discrepancy, so the transcript matches what the tool reports at the commit the doc documents. claim-lint: python3 scripts/claim-lint.py --files docs/planning/green-program/failure-capture/PR-5-2026-09-06.md -> exit 0 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The deadline-snapshot fix for aarch64-gates-drain-decision-uses-provisional-
not-final-verdict introduced a new VERDICT_SERIAL_FILE variable and
renamed the ~35 assertion/echo reads of $SERIAL_FILE to it. Running the full
structure suite (not just gate_capture_drain_structure) surfaced the
consequence: current_teardown_bypass_surface_is_exact
(tests/teardown_structure.rs) pins four of those read sites as exact
literal text containing "$SERIAL_FILE" (PROD_SEAM_ABSENT_COUNT,
KERNEL_ORACLE_COUNT, INIT_EXIT_COUNT, BSSHD_COUNT), so the rename read as
those four markers no longer being asserted at all.
Same effect, no rename: freeze the PROD_DEADLINE_SERIAL snapshot at the
same point as before, but instead of introducing a second variable name,
reassign $SERIAL_FILE itself to that snapshot once the drain-or-pass
decision (the only step that still needs the live file) has run. Each of
the ~35 existing assertion/echo call sites keeps its original, unmodified
text, so
the four literal-text ratchets above pass again, and so would any other
ratchet checking one of the ~30 other ${SERIAL_FILE} reads in this
block, without needing to be found and updated individually.
Verified: `bash -n` and `shellcheck -x` on the changed file (only
pre-existing, unrelated warnings); scripts/run-structure-tests.sh
teardown_structure current_teardown_bypass_surface_is_exact and
x86_production_profile_gate_verdict_discipline_holds both pass; the full
structure suite (`for f in tests/*_structure.rs; do bash
scripts/run-structure-tests.sh "$(basename "$f" .rs)"; done`) reports
676/676 passed, 0 failed, across the same 42 suites the round doc's own
section 5 counted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The explanatory comment added for x86-prod-profile-capture-lines-locked-before-liveness-and-teardown-assertions quoted the verdict check verbatim (`` `test "$reached" = true` ``) as a cross-reference. tests/teardown_structure.rs's x86_production_profile_gate_verdict_discipline_holds ratchet counts occurrences of the literal $reached substring and the VERDICT constant across the whole script and requires exactly one of each (the real verdict machinery's own false/true/spent ordering check); the quoted comment text was a second, earlier match, which broke both the count and the ordering the ratchet derives from it. Reworded the cross-reference to name the check in prose instead of quoting its shell text, so the literal count returns to one. Verified: `bash -n` on the changed file; scripts/run-structure-tests.sh teardown_structure x86_production_profile_gate_verdict_discipline_holds passes; the full structure suite reports 676/676 passed, 0 failed (same run this commit shares with the sibling aarch64-prod-gate fixup). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`python3 scripts/claim-lint.py` on the tree (required before push, not just on commit messages) flagged six instances of the tool's own unquantified-absolute vocabulary inside review-finding explanatory comments this round added across gate-capture-drain.sh and the three gate scripts -- claim-lint:ok: this commit's own `python3 scripts/claim-lint.py` run, quoted below, is the resolving evidence. Reworded each flagged paragraph to state the same fact with a quantified or narrower phrase in its place. One reword needed a second pass: an initial fix for the SCORE_ONLY paragraph in run-aarch64-prod-profile-boot-test.sh claimed the replay mode "takes an early `return` out of this function" -- inspected the actual script and that file has no such function or return; it is a top-level `if` guard. Corrected to describe the real control-flow shape (the two top-level `if` branches on $SCORE_ONLY_SERIAL) rather than ship a wrong claim to clear a lint warning. Comment-only changes; verified `bash -n` and `shellcheck -x` on all four touched files (only pre-existing, unrelated warnings) and the full structure suite (`for f in tests/*_structure.rs; do bash scripts/run-structure-tests.sh "$(basename "$f" .rs)"; done`) still reports 676/676 passed, 0 failed. claim-lint: python3 scripts/claim-lint.py -> exit 0 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merges 26 commits from main (the #865 x86 host QEMU lock arch-aware extension, the #821 TTY-IRQ-under-PM-lock fix and its own landing re-smoke, and the #866 breenix-runs release-build fix plus a Swift-compiler-crash-workaround refactor of RunShow.renderSubsystems) into this branch's PR-5 failure-capture-drain work. 3 conflicts, 0 of them touching kernel/: - .gitattributes: both sides appended an independent -text entry block (this branch's pr5 serials, main's #821 serials); resolved by keeping both blocks. - docker/qemu/run-x86-boot-tests.sh and docker/qemu/run-x86-prod-profile-boot-test.sh: both sides added an independent `source lib/*.sh` line at the same spot (this branch's lib/gate-capture-drain.sh, main's lib/qemu-host-lock.sh and lib/gate-boot-facts.sh) and, in run-x86-boot-tests.sh, an independent per-boot state variable at two more sites (CAPTURE_LINES vs POLL_BREAK_REASON); resolved by keeping both sides' hunks. Both scripts still drain the capture and sample the host-side facts before their own kill line, per each side's own comment on why that ordering matters; `bash -n` confirms both files parse. No kernel/ file is part of any conflict; main's kernel/ changes (process/mod.rs, syscall/handlers.rs, tty/driver.rs, line_discipline.rs, main.rs, test_framework/registry.rs) applied cleanly with no overlap against this branch's non-kernel diff. No scorer contract this branch's replayed fixtures depend on changed on main's side: main did not touch tools/breenix-runs/Sources/BreenixRuns/Store/Importer.swift, docker/qemu/lib/gate-capture-drain.sh, or the BXCAP/GATE_BOOT_FACTS line formats those fixtures were recorded against (the RunShow.swift change is a compiler-crash-workaround refactor whose own comment states the per-state text is unchanged, and the gate-boot-facts.sh/ qemu-host-lock.sh edits are `|| true` and arch-name-portability robustness fixes, not format changes) -- no fixture re-recording is needed at this merged head. 43 of 43 tests/*_structure.rs suites and scripts/test_claim_lint.py pass at this merged tree; scripts/claim-lint.py is clean over the changed hunks.
Merges 6 further commits from main that landed after this branch's first landing merge (815bdb8): a new structure-test ratchet on gate-boot-facts.sh's own set -e/pipefail-fallback shape (the file itself unchanged, only the test asserting on it is new) plus the chk-debt PR-0 critical-path-logging census round (a new denylist entry for serial_print!/log_serial_print!/log::log! in check-critical-path-violations.sh and its own structure-test census). 0 conflicts across 6 changed paths: 5 of 6 (three new docs, two new tests/*_structure.rs files) are additions; the sixth, scripts/check-critical-path-violations.sh, auto-merged clean. `git log --oneline 0481960..815bdb8 -- scripts/check-critical-path-violations.sh` returns 0 of the 11 pre-merge PR-5 commits, so main's PR-0 widening of that file's existing denylist array applied with no overlapping hunk from this branch. `git diff 783a6a5 origin/main --stat` names the same 6 paths above, and neither tools/breenix-runs/Sources/BreenixRuns/Store/ Importer.swift, docker/qemu/lib/gate-capture-drain.sh, nor docker/qemu/lib/gate-boot-facts.sh is among them -- the scorer contracts this branch's replayed fixtures depend on carry 0 lines of change here, so no fixture re-recording is needed at this merged head.
Records the full re-smoke run at this branch's merged-to-main head: the two-stage `git merge --no-ff origin/main` (815bdb8 then b0ea446, 0 conflicts either stage, no kernel/ file touched, no fixture re-recording needed); 45/45 tests/*_structure.rs suites (700 tests, 0 failed); python3 scripts/claim-lint.py and scripts/test_claim_lint.py both clean; a from-scratch aarch64 rebuild (0 own-code warnings, no-neon PASS) with a clean 20/20 strict-gate pass and a PASS prod-profile gate on the first attempt each; and beast x86 (0 build warnings/errors, a clean run-x86-boot-tests.sh 1 PASS) where run-x86-prod-profile-boot-test.sh needed two attempts -- the first FAILED on the console-prompt liveness check under 4-5 concurrent agents' QEMU workloads on the shared beast container (serial evidence preserved under scratchpad/ftc5-serials/ land-resmoke/), the second PASSED cleanly once host load measurably dropped, consistent with #766's wake-dispatch-latency mechanism rather than a defect this branch introduced -- recorded attempt-by-attempt per the project's testing-integrity rule, not silently retried away. claim-lint:ok: python3 scripts/claim-lint.py -> exit 0 (12 files checked, this commit's own changed hunks)
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements PR-5 of
docs/planning/green-program/failure-capture/PLAN-2026-09-05.md(section 6): gates drain an open BXCAP capture before their own kill and name
the result on the FAIL line. A new shared library,
docker/qemu/lib/gate-capture-drain.sh, wired into the four gate scriptsthat run a guest QEMU process through to a scored outcome and then kill it:
docker/qemu/run-aarch64-boot-test-strict.shdocker/qemu/run-aarch64-prod-profile-boot-test.shdocker/qemu/run-x86-boot-tests.shdocker/qemu/run-x86-prod-profile-boot-test.sh(The plan's own file list additionally named
run-aarch64-service-sequence-gate.sh; this round's task scope named thetwo production-profile gates in its place. Not converted, not touched.)
The drain contract
On any outcome that is not already a confirmed pass, immediately before a
gate's own kill line:
Settle, unconditionally:
GCD_SETTLE_MS(default 300ms), a flatsleep, not a stability poll — catches a kill landing mid-write on any
serial line, not only inside a BXCAP block.
Re-check. If the last
[BXCAP:BEGIN ...]has no matching[BXCAP:END ...](paired byseq=, not by which file a line landedin), wait for the file(s) to go byte-stable for
GCD_QUIET_MS(default250ms), bounded by
GCD_MAX_MS(default 3000ms) total.Classify and print two bracket lines:
On a confirmed-pass outcome the drain step does not run at all:
capture=n/a,drain_ms=0, no re-read — the same predicate each gate's ownpoll loop already uses to decide it is done polling. Latency bound: a PASS
boot pays 0ms; a non-PASS boot pays at most
GCD_SETTLE_MS + GCD_MAX_MS(≤3300ms at shipped defaults), added only on the failure path, once per
boot, after the outcome is known and before QEMU is killed.
Wire vocabulary is
complete|partial, not the plan text'scomplete|truncated— matches the landed PR-3 emitter's ownverdict=complete|partialfield on its in-capture[BXCAP:END ...]line,so the two vocabularies agree about the same fact.
absentis this round'sown addition, for "no
BEGINwas ever written," a case the in-captureemitter has no line for.
capture_selftestand the gates stay disjoint throughout: eachcapture_selftestboot in this round was built by hand outside the gatescript and run against the unmodified gate; no gate script's own text
gained the string
capture_selftestanywhere (the existing structuralguard for that stayed green throughout).
Ratchet:
tests/gate_capture_drain_structure.rsSix tests below (0 of 6 boot a kernel):
converted_gates_exist_and_call_both_drain_functions— census overdocker/qemu/**/*.shfiltered to scripts sourcing the drain library;each must call both
gcd_drain_and_reportandgcd_pass_report.every_guest_kill_site_is_preceded_by_a_drain_decision— the actualratchet: each guest-kill line in the converted set must have a drain
call within the preceding 30 lines. Anti-vacuity floor: at least 6 kill
sites (this round wired exactly six across the four gates).
moving_a_kill_above_its_drain_decision_reddens_the_check— mutationleg: confirms the real, unmutated gate passes first, then relocates a
real kill line above its drain decision and asserts the check now
fails. Caught its own vacuity bug live on the first run (the relocated
kill's new lookback window still matched this test file's own prose
comments naming the drain functions) — fixed by excluding comment
lines from what counts as coverage.
classify_distinguishes_complete_partial_and_absent_with_no_boot—three static fixtures, no boot.
classify_pairs_a_begin_and_end_split_across_two_serial_files_by_seq—the x86 shape (BEGIN in one file, END in the other), paired by
seq=.drain_disabled_reads_partial_drain_enabled_reads_complete_same_race—the oracle the task asked for, no QEMU boot: a background process
appends the closing half of a real BXCAP capture 150ms after the check
starts;
BREENIX_GATE_DRAIN_DISABLE=1readscapture=partial, thedrain enabled reads
capture=complete— same underlying race, onlywhether draining happened differs.
Live-boot oracle: the identical, real capture read two ways
Section 2 item 6 establishes the mechanism with a synthetic fixture. Section
3 of the round doc reruns the same claim against a real QEMU boot's own
bytes:
Full serial:
docs/planning/green-program/failure-capture/serials/pr5/aarch64-strict-forcefail-complete-serial.txt(
[BXCAP:BEGINat line 674,[BXCAP:ENDat line 750).The reverse leg is derived from the identical boot's own bytes, not
hand-authored: a straight byte-range cut ending strictly between BEGIN and
END, committed at
docs/planning/green-program/failure-capture/serials/pr5/aarch64-strict-forcefail-partial-serial.txt,classifies as
partial. The "kill before draining" race replayed on thisexact real content, both directions: drain-disabled reads
partialatdrain_ms=0; drain-enabled readscompleteatdrain_ms=300. Red togreen, both directions, from one identical real capture.
Gates run at this branch's head
PASS: 1/1,capture=n/aFAIL(forced),capture=complete:...:records=76PASS,capture=n/acapture=absent:...:drain_ms=300PASS,capture=n/aPASS,capture=n/aFull structural suite at review-passed HEAD: 42/42
tests/*_structure.rsfiles, 676 tests, 0 failed;
claim-lintclean.What this round found in its own work (both by-catch, both fixed this round)
$reachedduplicate. The first forced-fail knob setreached=falsea second time after the poll loop, trippingx86_production_profile_gate_verdict_discipline_holds's pin on exactlyone
reached=falseassignment (and, on a second pass, a prose commentthat happened to quote the literal variable expansion). Fixed by moving
the knob into the poll loop as an early
break.$passedduplicate. Reading$passeda second timeafter the loop tripped
validate_x86_frame_custody_harness'spassed_reads == 1pin. Fixed with aCAPTURE_LINES=""sentinel setalongside
passed=true/passed=falserather than read from either.Neither ratchet was weakened to pass; both were doing exactly the job they
were built for, on new code unconnected to why either was originally
written. Caught by running the full structural suite, not by reading the
ratchet source first. A later pass over this round's own prose (docs and
gate-script comments) found and fixed six unquantified-absolute
claim-lintfindings against the tool's own rules, plus one stale filecount in this doc's own claim-lint citation — corrected in place, not
carried forward.
Landing merge and re-smoke
git merge --no-ff origin/main(two stages,origin/mainhaving movedbetween them): 0 conflicts in either stage, no
kernel/file touched byeither, no file this branch's replayed fixtures depend on changed by
either — no fixture re-recording needed. At the merged head: 45/45
tests/*_structure.rssuites (700 tests, 0 failed),claim-lintand itsself-test both clean, a from-scratch aarch64 rebuild (no-neon PASS, clean
20/20 strict gate, PASS prod-profile gate) and beast x86 (0 build
warnings/errors, PASS
run-x86-boot-tests.sh 1).run-x86-prod-profile-boot-test.shneeded two attempts: the first FAILED on the console-prompt liveness check
under 4-5 concurrent agents' QEMU workloads on the shared beast container;
the second PASSED cleanly once host load measurably dropped, consistent
with #766's wake-dispatch-latency mechanism rather than a defect this
branch introduced. Recorded attempt-by-attempt, not silently retried away
— full detail and serial paths in the round doc's own landing section.
What is NOT claimed
used PR-3's
capture_selftestself-test edge. On a genuine panic or faulttoday, on any of these four gates,
capture=absentis the honest andcorrect reading — no capture emitter is wired to those edges yet. This
round's drain mechanism is shown working end to end; its production value
on a real crash starts the day PR-4 lands.
gcd_classify/gcd_last_eventsmatches byseq=only, not a stricter check that the two halves are the samecapture beyond that number colliding — a by-construction argument (the
counter is a monotonic atomic
fetch_add), not a race exercised by anytest here.
condition, unchanged by this round: each red in this round's gates was a
forced-fail knob's own deliberate FAIL, not a red carrying
capture=absenton a non-PASS boot that should have shown a capture.-smp 1; themulti-CPU interleaving hazard PR-3's own round doc measured on aarch64's
-smp 4is not exercised by anything in this round's x86 legs.run-aarch64-service-sequence-gate.shis not converted — the plannamed it, this round's task scope named the two production-profile gates
instead; no line of it changed.
Round doc, full detail:
docs/planning/green-program/failure-capture/PR-5-2026-09-06.md.Related: #826, #827.