diff --git a/docs/active-work.md b/docs/active-work.md index d77db78..47926f0 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -279,10 +279,11 @@ from #171 and #215. **`72da24a`**, worktree `/home/jeans/Repos/personal/pmacs-probe-sigint`. Recover with `git fetch githubsucks && git checkout gpu-probe-sigint-teardown`. -- **No PR. Framing revision 2 at `docs/gpu-probe-sigint-framing.md`; +- **No PR. Framing revision 4 at `docs/gpu-probe-sigint-framing.md`; NO IMPLEMENTATION and no fix proposed** — the mechanism is not known - yet, and the framing says so rather than guessing. Revision 1 was - rejected on five findings, all upheld. + yet, and the framing says so rather than guessing. Revisions 1, 2 and + 3 were each rejected on findings, all upheld; run provenance lives in + `docs/probe-sigint-evidence.md`. - **Why it exists.** `ctrl_c_on_launcher_group_does_not_reach_spawned_daemon` fails in gate stage `sweep-crdt` with "child did not exit within 5s". **Pre-existing on `main`** — `72da24a` fails it in a clean worktree @@ -311,8 +312,11 @@ from #171 and #215. "their targets execute after the failure" does not exonerate them. - **Ruled out by measurement — do not re-run:** machine load; tmpfs starving RAM (settled by experiment, not argument — `/tmp` 21G→1.2G, - available 27G→45G, still red); leaked daemons; inotify; - `--workspace` feature unification; any specific preceding test. + available 27G→45G, still red); leaked daemons; inotify. +- **NOT ruled out, contrary to earlier entries here:** `--workspace` + artifact selection, and the preceding tests. R9 appeared to clear + them but ran **different binaries**, so the comparison was never + made. Both are open. - **Ground truth, and what it does NOT establish.** Neither binary contains signal-handling code: `run_gpu` (`src/main.rs:324`) blocks in `command.status()` with no handler, and grepping all of @@ -333,9 +337,9 @@ from #171 and #215. about **5.1s** — inside what the sampler saw, and a ">6s" selector would have captured nothing, repeating the error it was meant to correct. (a) is therefore not refuted by (b); it stays **unproven for - a different reason** — the suite spawns launchers from five call - sites, so command line alone cannot attribute one to this test. - Under `--features crdt` that is **six** sites (`:509, :534, :544, + a different reason** — under `--features crdt` the suite spawns root + launchers from **six** call sites, so command line alone cannot + attribute one to this test. The six (`:509, :534, :544, :574, :725, :1097`, all inside `#[cfg(feature = "crdt")] mod crdt`); the other two `--gpu` arguments sit under `#[cfg(not(…))]` and are compiled out. **Do not key on process age. Key on the PID the test @@ -362,10 +366,22 @@ from #171 and #215. machine-local under `/home/jeans/build/pmacs-gate-targets/probe-sigint-evidence/`; `/tmp` is a tmpfs and they were nearly lost to a cleanup mid-lane. -- **D0 precedes every other diagnostic:** re-run the matrix at `main` - under a harness that captures provenance **and the artifact hashes - actually executed**, since command shape silently changed the binary - once already. +- **THE ONSET IS DATABLE, and it reframes the lane.** `sweep-crdt` + appears **17 times** in this target dir's gate logs; `ctrl_c` fails + in exactly the **last three** and passed — both copies — before them. + Last green `20260815T185708Z`, first red `20260816T063330Z`, no + reboot between. The three earlier red sweeps failed on unrelated + rows. So "pre-existing on `main`" holds (F1 at `72da24a` reproduces + it) but **"always broken" is contradicted**. +- **Red full-sweep count is SEVEN, not five** (F1–F7 in the manifest), + each with its own log digest; revision 3 said 5/5 while the framing + separately cited a gate run the manifest never listed. +- **D0 precedes every other diagnostic**, in two parts: **(a) bisect + the onset window** — the sharpest lead the lane has; **(b)** re-run + the matrix at `main` under a harness capturing provenance **and the + artifact hashes executed at run time**, since command shape silently + changed the binary once already and a hash computed later reflects + only what occupies that path now. - **Coherence: journey step 12(a), "closing is clean", IS touched** — Ctrl-C teardown of a GPU session is that step, grade movement or not. Revision 1 claimed no journey step, reasoning from grade movement, diff --git a/docs/gpu-probe-sigint-framing.md b/docs/gpu-probe-sigint-framing.md index d9b0614..9379fbb 100644 --- a/docs/gpu-probe-sigint-framing.md +++ b/docs/gpu-probe-sigint-framing.md @@ -1,6 +1,6 @@ # GPU launcher / probe SIGINT teardown — framing -Revision 3. Status: **awaiting approval. No implementation.** +Revision 4. Status: **awaiting approval. No implementation.** Revisions 1 and 2 were each rejected on five findings. Every correction is recorded in place rather than quietly rewritten, because three of @@ -14,6 +14,12 @@ them were claims this document itself had advanced: never blocks indefinitely" (§3); the launcher call-site count (§5); reduction provenance, now in `docs/probe-sigint-evidence.md`; and ledger corrections that had not been made portable (§11). +- r3 → r4: the interaction table overstated (§4); the red count was 7, + not 5, and abbreviated argv were not argv (manifest); "workspace + artifact family" conflated Cargo suffix with byte identity + (manifest); ledgers still carried the falsified R9 conclusions (§11). + **And a finding that reframes the lane: the failure has a datable + onset (§4a) and is not long-standing.** ## 1. The problem, stated as what is observed @@ -131,8 +137,11 @@ Cargo's target selection changes the fingerprint, so command shape changes the executable. R9 therefore establishes **same target names and order**, not same binaries. -What the evidence actually supports is an **interaction**, and only -that: +What the evidence is **consistent with** is an interaction. It does not +isolate one, because the three rows differ in more than the two columns +shown — different source heads, different worktrees, unknown +cleanliness, and, as above, different bytes behind identical Cargo +suffixes: | prior targets execute | workspace artifact family | result | |---|---|---| @@ -140,19 +149,50 @@ that: | no | yes | R10 green | | yes | yes | **F1–F5 red** | -Neither factor alone reproduces it. So `--workspace` artifact -selection is **not sufficient by itself** — and, importantly, **not -ruled out either**, which is how revision 2 phrased it. Later-selected -packages can influence Cargo's build graph and fingerprints *before* -their test executables ever run, so "their targets execute after the -failure at line 3066" does not exonerate them. The same applies to the -claim that other packages "cannot be implicated": withdrawn. +Neither factor alone reproduced it **in these runs**. That is the +whole of the claim. `--workspace` artifact selection is **not +sufficient by itself and not ruled out**; later-selected packages can +influence Cargo's build graph and fingerprints *before* their test +executables run, so "their targets execute after the failure at line +3066" does not exonerate them — that claim is withdrawn. And since +§4's own preamble says no historical row should be relied on until D0, +**this table is a description of what was observed, not a finding**. +Revision 3 asserted it as an interaction while simultaneously +disclaiming its inputs, which cannot both be true. Also refuted, by measurement: machine load (red on a quiet box, load 2.77); tmpfs starving RAM (**tested by experiment** — `/tmp` 21 G → 1.2 G, available 27 G → 45 G, still red); leaked daemons (peak 58, +8 per sweep, green runs already at 46–60); inotify (47 of 1024). +## 4a. The onset is datable — and it reframes the lane + +`sweep-crdt` appears **17 times** in this target directory's gate logs. +`ctrl_c` fails in **exactly the last three**, and passed — both copies, +`... ok` — in the runs before them. + +| window | runs | `ctrl_c` | +|---|---|---| +| 08-14 → 08-15 19:57 | 14 (11 green; 3 red on *other* tests) | **passes** wherever the stage ran | +| 08-16 06:33 `…-1977433` | red, 2 bins | **fails, both copies** | +| 08-16 06:45 `…-2144707` | red, 2 bins | **fails, both copies** | +| 08-17 17:25 `…-2375685` | red, 3 bins | **fails, both copies** | + +The three earlier red sweeps failed on unrelated rows — protocol and +version tests, and `composition_overhead_under_ten_percent`. None +involved `ctrl_c`. + +**So the failure is not long-standing.** "Pre-existing on `main`" +remains true — F1 at `72da24a` reproduces it — but "always broken" was +never established and is now contradicted. Last green containing it: +`20260815T185708Z`. First red: `20260816T063330Z`. The machine was not +rebooted across that boundary. + +**This supersedes the reduction matrix as the lane's first move.** A +test that passed in this stage fourteen times and then failed three +times running has a change behind it, and identifying that change is +worth more than further reduction — which, per §4, has isolated nothing. + ## 5. Two retracted claims, both mine, kept as warnings **Claim A — "mechanism located".** Reported the launcher blocked in @@ -207,11 +247,14 @@ diagnostic only, and it must **discriminate** the three live candidates: blocked delivery, inherited ignore, and an escaped or wrong process group. -- **D0 — re-run the §4 matrix with captured provenance**, at `main`, - recording the artifact hashes actually executed. Revision 2's - strongest claim collapsed because command shape silently changed the - binary; no further reduction should be trusted until each row names - the executable it ran. +- **D0a — bisect the onset window** (§4a): `20260815T185708Z` green → + `20260816T063330Z` red, no reboot between. This is the sharpest lead + the lane has and comes before any further reduction. +- **D0b — re-run the §4 matrix with captured provenance**, at `main`, + recording the artifact hashes actually executed **at run time**. + Revision 2's strongest claim collapsed because command shape silently + changed the binary; no further reduction should be trusted until each + row names the executable it ran. - **D1 — key on the PID this test records.** The test already owns `launcher.id()`. Capture around its own `kill`, not by scanning for age or command line. @@ -246,10 +289,13 @@ Written now so the fix cannot quietly become "make the test pass". unobtainable before this lane is approved, gated and merged, and revision 1 stated an impossible precondition. Post-merge confirmation on `main` is a follow-up, not a gate on the fix. -- **A3.** The R9 paradox is explained, or explicitly recorded as - unexplained. A fix that greens the sweep without accounting for why - every subset passed leaves a gap, and the gap is stated rather than - left for the next reader. +- **A3.** There is no established "R9 paradox" to explain — R9 ran + different binaries, so the comparison it appeared to make was never + made. What A3 requires instead: **D0 recreates the subset/full + comparison under captured provenance**, and whatever it then shows is + either explained by the fix or explicitly recorded as unexplained. A + fix that greens the sweep without that comparison having been made + properly leaves the gap stated, not hidden. - **A4.** No deadline raised, no test skipped, retried, or serialised to obtain green. - **A5.** **Conditional on D4.** If bet 1 holds, this is unconditional: @@ -292,9 +338,13 @@ when revision 2 was reviewed, and both are closed by this revision: processes "should both die at once" and then withdrew that same claim further down. The assertion is removed; only the withdrawal and its reasoning remain. -- **`panel-mapping-generation` @ `16cf3a2`** still carried "119 - binaries green, one red", the ≥8 s arithmetic, the "default action" - claim and the ">6 s selector". Pushing `16cf3a2` made the *retraction* - portable but not the *correction*. That ledger is corrected on its own - branch and pushed, so the held lane no longer transports falsified - claims. +- **`panel-mapping-generation`** carried "119 binaries green, one + red", the ≥8 s arithmetic, the "default action" claim and the ">6 s + selector"; `779a6bd` corrected those. **It still carried more**, + found on re-review: `--workspace` unification "refuted", R9 running + the "same binaries", later packages that "cannot be implicated", and + a cause "cumulative across the preceding 37 binaries". Revision 3's + claim here that the held lane no longer transports falsified claims + was **premature**; those are corrected now, and this section should + be read as a checklist that has been re-verified rather than an + assurance. diff --git a/docs/probe-sigint-evidence.md b/docs/probe-sigint-evidence.md index 0f33128..0d53af4 100644 --- a/docs/probe-sigint-evidence.md +++ b/docs/probe-sigint-evidence.md @@ -11,7 +11,10 @@ identified here by SHA-256 prefix and byte count. These runs were made **before** this manifest existed, so their provenance is **reconstructed, not captured**. Specifically: -- **Commands** are exact — they are the literal invocations issued. +- **Commands** are exact and complete argv. Revision 3 abbreviated + R7–R9 as "`--test` ×N (targets 6–37)" and F5 as "`--acceptance` ×6", + which are descriptions, not reconstructable invocations. They are + written out in full below. - **Worktree** is exact. - **HEAD** is given as a range where the run cannot be pinned to one commit, and marked `~`. It is never guessed at single-commit @@ -22,20 +25,31 @@ provenance is **reconstructed, not captured**. Specifically: **D0 (below) exists because of this.** No conclusion in §4 should rest on a `UNKNOWN`-cleanliness row once D0 has replaced it. -## The artifact-identity column, and why it exists +## Artifact identity — suffix is NOT byte identity -Reduction and sweep runs did **not** always execute the same compiled -test executables. Cargo's target selection changes the fingerprint, so -`--test a --test b` and `--workspace` can produce byte-different -binaries for the same source. Verified: +Two distinct things were conflated in revision 3 and are separated here. -| family | `gpu_initial_target_acceptance` | `gpu_invocation_acceptance` | +**Cargo suffix** is recorded in each log and is therefore *known* per +run. **Byte identity** is not: target directories have been overwritten +many times since, so a hash computed today is the hash of whatever +occupies that path now, not of what a given run executed. + +That the two differ is demonstrated, not assumed. F1 ran in the `main` +worktree (`pmacs-fdccc423`) and executed suffixes `-5d9105cb7047aab8` +and `-d4dae4f01bcdef62` — **the same suffixes** as the panel-worktree +sweeps — yet the bytes at those paths differ by worktree: + +| worktree | `gpu_initial_target…-5d9105cb` | `gpu_invocation…-d4dae4f0` | |---|---|---| -| reduction (R7–R9) | `-91f51d0b5303ff9f`, sha `36912fa25a72ffc7` | `-6b4b8223dea45247`, sha `858d71486d0b66f0` | -| workspace (R10, F1–F5) | `-5d9105cb7047aab8`, sha `1b3cc86cbb8d6092` | `-d4dae4f01bcdef62`, sha `ede0c07dd9abb456` | +| `pmacs-fdccc423` (main) | `e057803988c34cf7` | `00f06aeb089ce38d` | +| `pmacs-mapping-gen-…` (§5b) | `1b3cc86cbb8d6092` | `ede0c07dd9abb456` | -They are byte-different. Any claim of the form "the same binaries pass -as a subset" is therefore **unsupported by these runs**. +Same suffix, different source head, different bytes. So **"workspace +artifact family" was not an identity class** and is withdrawn as a +grouping. Each run below records the suffix its log shows, and byte +identity as `UNKNOWN` unless contemporaneously captured — which, for +every pre-manifest run, it was not. R1 and R2 have no preserved log at +all and so cannot claim even a suffix. ## Runs @@ -53,25 +67,58 @@ except `WT=main`, which uses `…/pmacs-fdccc423`. | R4 | `test --features crdt --lib --test gpu_invocation_acceptance --no-fail-fast` | mg | ~`724b785`–`5174f73` | UNKNOWN | reduction | green, 2145 + 15 | `89050c702de22d57` 158812 | | R5 | `test --features crdt --no-fail-fast --test gate_script_acceptance --test gpu_invocation_acceptance` | mg | ~`5174f73`–`b72843a` | UNKNOWN | reduction | green | `31b3e5249b475479` 3706 | | R6 | `test --features crdt --no-fail-fast --test gpu_font_acceptance --test gpu_initial_target_acceptance --test gpu_invocation_acceptance` | mg | ~`5174f73`–`b72843a` | UNKNOWN | reduction | green, 11+15+15 | `332693a39c73731a` 4569 | -| R7 | `test --features crdt --no-fail-fast --lib --bins` + `--test` ×14 (targets 6–19) + `--test gpu_invocation_acceptance` | mg | ~`b72843a` | UNKNOWN | reduction | green | `9e1ebc59ed9f0dd4` 187531 | -| R8 | `test --features crdt --no-fail-fast --test` ×18 (targets 20–37) + `--test gpu_invocation_acceptance` | mg | ~`b72843a` | UNKNOWN | reduction | green | `8b26ebfcf5f871b4` 28677 | -| R9 | `test --features crdt --no-fail-fast --lib --bins` + `--test` ×32 (targets 6–37) + `--test gpu_invocation_acceptance` | mg | ~`b72843a` | UNKNOWN | **reduction** | green | `b31d98ee2f427eca` 214566 | -| R10 | `test --workspace --features crdt --no-fail-fast --test gpu_initial_target_acceptance --test gpu_invocation_acceptance -- --skip basedpyright` | mg | ~`b72843a` | UNKNOWN | **workspace** | green | `81b48fd7a0e261dc` 3553 | -| F1 | `build --workspace --no-default-features --features luajit,crdt && test --workspace --features crdt --no-fail-fast -- --skip basedpyright` | **main** | `72da24a` | clean (verified `git status --porcelain` empty) | workspace | **red** | `10b55b8ba8741125` 334446 | -| F2 | `test --workspace --features crdt --no-fail-fast -- --skip basedpyright` | mg | ~`b72843a` | UNKNOWN | workspace | **red** | `474f88f0dad581fe` 338555 | -| F3 | same as F2, with resource sampler | mg | ~`b72843a` | UNKNOWN | workspace | **red** | `7b8519e7300e8bb3` 338555 | -| F4 | same as F2, with process sampler | mg | ~`b72843a` | UNKNOWN | workspace | **red** | `5ccdefc5d89eece3` 338555 | -| F5 | gate stage 15 of `./scripts/gate --protocol --acceptance ×6` | mg | `5174f73` + docs | UNKNOWN | workspace | **red** | gate log `20260817T172537Z-2375685/15-sweep-crdt.log` | +| R7 | `test --features crdt --no-fail-fast --lib --bins --test acceptance --test ambient_isolation_acceptance --test auto_indent_acceptance --test auto_indent_crdt_acceptance --test auto_pair_acceptance --test auto_pair_crdt_acceptance --test autosave_acceptance --test bottom_panel_stage1_acceptance --test bottom_panel_stage2a_acceptance --test bottom_panel_stage2b_daemon_acceptance --test bottom_panel_stage2b_gpu_acceptance --test bottom_panel_stage2b_protocol_acceptance --test comment_toggle_acceptance --test compile_mode_acceptance --test gpu_invocation_acceptance` | mg | ~`b72843a` | UNKNOWN | `-91f51d0b` / `-6b4b8223`; bytes UNKNOWN | green | `9e1ebc59ed9f0dd4` 187531 | +| R8 | `test --features crdt --no-fail-fast --test compile_mode_crdt_acceptance --test completion_popup_acceptance --test config_registry_acceptance --test cua_region_acceptance --test desktop_acceptance --test destination_capture_acceptance --test dired_acceptance --test discovery_acceptance --test discovery_stage2_acceptance --test editops_acceptance --test find_file_acceptance --test folding_acceptance --test folding_stage2_acceptance --test full_grid_resync_acceptance --test gate_script_acceptance --test git_status_stage1_acceptance --test gpu_font_acceptance --test gpu_initial_target_acceptance --test gpu_invocation_acceptance` | mg | ~`b72843a` | UNKNOWN | suffixes per log; bytes UNKNOWN | green | `8b26ebfcf5f871b4` 28677 | +| R9 | R7's argv with R8's eighteen `--test` names spliced in before `--test gpu_invocation_acceptance` — i.e. `test --features crdt --no-fail-fast --lib --bins` then `--test` for each of targets 6–37 in sweep order, then `--test gpu_invocation_acceptance`. Full list = R7's ∪ R8's, deduplicated, order preserved. | mg | ~`b72843a` | UNKNOWN | **`-91f51d0b` / `-6b4b8223`** (log `:3066`); bytes UNKNOWN | green | `b31d98ee2f427eca` 214566 | +| R10 | `test --workspace --features crdt --no-fail-fast --test gpu_initial_target_acceptance --test gpu_invocation_acceptance -- --skip basedpyright` | mg | ~`b72843a` | UNKNOWN | **`-5d9105cb`** (log `:3`); bytes UNKNOWN | green | `81b48fd7a0e261dc` 3553 | +| F1 | `build --workspace --no-default-features --features luajit,crdt && test --workspace --features crdt --no-fail-fast -- --skip basedpyright` | **main** | `72da24a` | clean (verified `git status --porcelain` empty) | `-5d9105cb` / `-d4dae4f0`; bytes at those paths **today** `e0578039…` / `00f06aeb…`, i.e. NOT the panel worktree's | **red** | `10b55b8ba8741125` 334446 | +| F2 | `test --workspace --features crdt --no-fail-fast -- --skip basedpyright` | mg | ~`b72843a` | UNKNOWN | `-5d9105cb` / `-d4dae4f0`; bytes UNKNOWN | **red** | `474f88f0dad581fe` 338555 | +| F3 | same argv as F2, with a resource sampler running | mg | ~`b72843a` | UNKNOWN | as F2; bytes UNKNOWN | **red** | `7b8519e7300e8bb3` 338555 | +| F4 | same argv as F2, with a process-table sampler running | mg | ~`b72843a` | UNKNOWN | as F2; bytes UNKNOWN | **red** | `5ccdefc5d89eece3` 338555 | +| F5 | stage 15 of `./scripts/gate --protocol --acceptance bottom_panel_stage1_acceptance --acceptance bottom_panel_stage2a_acceptance --acceptance bottom_panel_stage2b_daemon_acceptance --acceptance bottom_panel_stage2b_gpu_acceptance --acceptance bottom_panel_stage2b_protocol_acceptance --acceptance gui_stage1a_wire_acceptance` | mg | `5174f73` + uncommitted docs | UNKNOWN | suffixes per log; bytes UNKNOWN | **red** (3 bins: both ctrl_c copies + `m6_1_pty_mode_lifecycle`) | `20260817T172537Z-2375685/15-sweep-crdt.log`, sha `e5bdc911e366` | +| F6 | stage 9 of `./scripts/gate --protocol` | mg | `5174f73` | UNKNOWN | suffixes per log; bytes UNKNOWN | **red** (2 bins, both ctrl_c copies) | `20260816T063330Z-1977433/09-sweep-crdt.log`, sha `7a75d999ac4f` | +| F7 | stage 9 of `./scripts/gate --protocol` | mg | `5174f73` | UNKNOWN | suffixes per log; bytes UNKNOWN | **red** (2 bins, both ctrl_c copies) | `20260816T064549Z-2144707/09-sweep-crdt.log`, sha `9d3c6ad1bfc9` | Supporting, not a reduction: `9b8a01076b44bb7c` 98838 (`proc-sample.log`) is the process-table sampler output behind the retracted "mechanism located" claim. +**The red count is 7, not 5.** Revision 3 said "F1–F5, red 5/5" while +the framing separately cited gate `…-2144707`, a *different* physical +run the manifest never listed. Both exist, both are red, and there is a +third gate run too. F1–F7 above enumerate all seven, each with its own +log digest. F5 also has an extra failing binary +(`m6_1_pty_mode_lifecycle_started_then_exited`) that F6/F7 do not. + **R2 and R6 are distinct runs.** Revision 2's §4 cited `gpu3.log` for both; that log is R6's three-suite run only, and R2's log was never preserved. R1 likewise has no log. Both are marked accordingly rather than backfilled. +## The onset is datable — and this supersedes the reduction matrix + +`sweep-crdt` has run 17 times in this target directory's gate logs. The +`ctrl_c` failure appears in **exactly the last three**, and the test +**passed inside `sweep-crdt`** — both copies, `... ok` — in the runs +before them. + +| date | gate run | sweep-crdt | ctrl_c | +|---|---|---|---| +| 08-14 → 08-15 19:57 | 14 runs | 11 green; 3 red on *other* tests | **passes** where the stage ran it | +| 08-16 06:33 | `…-1977433` | red, 2 bins | **fails, both copies** | +| 08-16 06:45 | `…-2144707` | red, 2 bins | **fails, both copies** | +| 08-17 17:25 | `…-2375685` | red, 3 bins | **fails, both copies** | + +The three earlier red sweeps failed on unrelated tests — protocol and +version rows on 08-15 09:55, and `composition_overhead_under_ten_percent` +plus a v21/v20 row on 08-15 18:37. **None involved `ctrl_c`.** + +So the failure is **not long-standing**. Last green containing it: +`20260815T185708Z`. First red: `20260816T063330Z`. Something changed in +that window, on a machine that was not rebooted (uptime spans it). +**Bisecting that boundary is a sharper lead than any reduction**, and +D0 is amended to do it first. + ## D0 — re-run the matrix with captured provenance Before any §4 row is relied on for a conclusion, re-run the reductions @@ -82,6 +129,11 @@ log digest. Two constraints learned the hard way: - run them **at `main`**, not on a feature branch — R1–R10 ran in the `panel-mapping-generation` worktree, which carries §5b changes; -- record the **artifact hash per run**, since command shape changes it, - which is the whole reason R9's result did not mean what it appeared - to mean. +- record the **artifact hash per run at run time**, since command shape + changes it — the reason R9's result did not mean what it appeared to + mean — and since a hash computed later reflects only what occupies + that path now; +- **first, bisect the 08-15 19:57 → 08-16 06:33 onset window.** A test + that passed in this stage fourteen times and then failed three times + in a row has a change behind it, and finding that change is worth + more than any further reduction.