diff --git a/docs/active-work.md b/docs/active-work.md index bc5d877..21ca140 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -279,7 +279,7 @@ 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 6 at `docs/gpu-probe-sigint-framing.md`; +- **No PR. Framing revision 7 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. Revisions 1, 2 and 3 were each rejected on findings, all upheld; run provenance lives in @@ -320,10 +320,17 @@ from #171 and #215. artifact selection, and the preceding tests. R9 appeared to clear them but ran **different Cargo compilations**, 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 +- **Ground truth, and what it does NOT establish.** `run_gpu`'s own + path installs no handler: `run_gpu` (`src/main.rs:324`) blocks in `command.status()` with no handler, and grepping all of - `pmacs-gpu/src` for signal machinery returns nothing. The probe's + `pmacs-gpu/src` for signal machinery returns nothing. **But the + `pmacs` binary DOES contain signal machinery** — + `install_signal_handlers` (`src/daemon.rs:628`) registers `SIGINT` + and `SIGTERM`; it is simply not on `run_gpu`'s path. A source grep + also cannot exclude a runtime or dependency installing a disposition. + So the established fact is only: **no explicit installation on + `run_gpu`'s path**, and "whatever disposition they hold was + inherited" stays a **hypothesis** until D2 measures it. The probe's **event loop** wakes at least every 50ms (`pmacs-gpu/src/main.rs:1065`) — but the process is **not** bounded: its stdin reader blocks in `read_to_end` (`:1109`) and, once ready, @@ -380,15 +387,22 @@ from #171 and #215. at `7599661` during the last green (`3c06176` landed 40s after it finished) and `724b785` during the first red (`5174f73` landed 08:45:41, after that run ended 08:42:01). **Cleanliness captured for - neither.** And `72da24a` is an **ancestor** of `7599661` yet fails - today while `7599661` passed — no source-monotonic cause does that. - The ancestry shows only that **outcome is not determined by commit - alone**; it does NOT discriminate an environmental change, a - source/environment interaction, or a fix before `7599661` with a - regression before `724b785`, and an ancestor outside the interval is - irrelevant to whether the interval regressed. D0a is a **decision - procedure with no predicted outcome**: endpoints differ → bisect - `7599661..724b785`; endpoints agree → ask what else changed. + neither.** `72da24a` is an **ancestor** of `7599661` yet fails today + while `7599661` passed on 08-15 — but those two observations differ + in commit AND environment AND time, so they are **non-comparable and + support no causal conclusion of any kind**. Earlier wordings here + ("no source-monotonic cause does that", "outcome is not determined by + commit alone") are both **withdrawn**: different commits can + deterministically produce different outcomes, so the pair says + nothing about determinism either. +- **D0a is a decision procedure with no predicted outcome**, and one + run per endpoint decides nothing for a context-sensitive failure. + **N = 5 full `sweep-crdt` runs per endpoint, interleaved A/B/A/B**, + under the same captured conditions as D0b plus `uptime`, `free`, + `/tmp` usage and leaked-daemon count. A bisect of + `7599661..724b785` is permitted **only on a clean split** — all N red + one side, all N green the other. A mixed result means the failure is + intermittent under fixed source and **no bisect is justified**. - **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. diff --git a/docs/gpu-probe-sigint-framing.md b/docs/gpu-probe-sigint-framing.md index 864785e..a057fc7 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 6. Status: **awaiting approval. No implementation.** +Revision 7. 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 @@ -20,6 +20,13 @@ them were claims this document itself had advanced: (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.** +- r6 → r7: the ancestry supports **no** causal statement at all — the + observations are non-comparable, and even "outcome is not determined + by commit alone" is withdrawn (§4a); D0a was not yet a valid decision + procedure + (§7); "neither binary contains signal-handling code" is false — the + `pmacs` binary registers SIGINT in daemon mode (§3); residual + artifact wording (§4, manifest). - r5 → r6: the ancestry argument overreached (§4a) — it shows outcome is not determined by commit alone, and nothing more; residual byte-identity and "artifact family" wording in both ledgers (§11); @@ -69,11 +76,17 @@ stages green and is held behind this lane by explicit instruction. ## 3. Ground truth (cited), and what it does *not* establish -- **Neither binary contains signal-handling code.** `run_gpu` +- **`run_gpu`'s own path installs no handler.** It (`src/main.rs:324`) blocks in `command.status()` (`:363`) — a plain - `waitpid` — with no handler installed. Grepping + `waitpid` — with nothing installed along the way. Grepping `SIGINT|signal_hook|sigaction|ctrlc|set_handler|pthread_sigmask|sigprocmask` across `pmacs-gpu/src` returns nothing. + **Revision 6 said "neither binary contains signal-handling code"; + that is false.** The `pmacs` binary *does* — `install_signal_handlers` + (`src/daemon.rs:628`) registers `SIGINT` and `SIGTERM` — it simply is + not on `run_gpu`'s path. And a grep of project sources cannot exclude + a runtime or dependency installing a disposition. So the established + fact is narrow: **no explicit installation on `run_gpu`'s path**. - **The probe's event loop wakes at least every 50 ms.** `run_headless_managed_probe` (`pmacs-gpu/src/main.rs:1065`) loops on `event_rx.recv_timeout(Duration::from_millis(50))`. **Revision 2 said @@ -95,16 +108,18 @@ nothing about runtime *disposition*: `SIG_IGN` is inherited across `fork` **and** survives `exec`, so either process can hold a non-default disposition it never installed — from the test harness, from `cargo`, or from the invoking shell. Inherited ignore is a live -candidate precisely because the source is silent. What the source -establishes is narrower: **neither binary sets a disposition itself**, -so whatever disposition they hold at runtime was inherited, and that is -measurable rather than arguable. +candidate precisely because `run_gpu`'s path is silent. But **"whatever +disposition they hold was inherited" is a hypothesis, not a finding** — +revision 6 stated it as established, which it is not, since neither a +source grep nor an absent call proves what the runtime disposition is. +D2 measures it. Until then it is one candidate among the three D1/D2 +are built to separate. ## 4. Reductions attempted **Full provenance lives in `docs/probe-sigint-evidence.md`**, which is pushed with this branch: exact command, worktree, HEAD, cleanliness, -the artifact family actually executed, result, and log digest for every +the Cargo suffixes actually executed, result, and log digest for every physical run. Log bodies stay 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. @@ -119,7 +134,7 @@ worktree**, not at `main`. `D0` re-runs the matrix under a harness that captures all of it, at `main`, before any row here is relied on. All rows carry `--features crdt`. Full argv, worktree, HEAD, -cleanliness and artifact family per run: `docs/probe-sigint-evidence.md`. +cleanliness and Cargo suffixes per run: `docs/probe-sigint-evidence.md`. | # | reduction (after `cargo test`) | runs | result | log | |---|---|---|---|---| @@ -131,8 +146,8 @@ cleanliness and artifact family per run: `docs/probe-sigint-evidence.md`. | R6 | `--test gpu_font_acceptance --test gpu_initial_target_acceptance --test gpu_invocation_acceptance` | 1 | green | `gpu3.log` | | R7 | `--lib --bins` + `--test`×14 (targets 6–19) + the suite | 1 | green | `half1.log` | | R8 | `--test`×18 (targets 20–37) + the suite | 1 | green | `half2.log` | -| R9 | `--lib --bins` + `--test`×32 (targets 6–37) + the suite | 1 | green (**reduction artifacts**) | `prefix.log` | -| R10 | `--workspace ... --test gpu_initial_target_acceptance --test gpu_invocation_acceptance` | 1 | green (**workspace artifacts**) | `wsonly.log` | +| R9 | `--lib --bins` + `--test`×32 (targets 6–37) + the suite | 1 | green (`-91f51d0b`, `-6b4b8223`) | `prefix.log` | +| R10 | `--workspace ... --test gpu_initial_target_acceptance --test gpu_invocation_acceptance` | 1 | green (`-5d9105cb`, `-d4dae4f0`) | `wsonly.log` | | F1–F7 | full `--workspace --no-fail-fast -- --skip basedpyright`, plus three gate `sweep-crdt` stages | **7** | **red, 7/7** | `base-sweep.log` (at `72da24a`), `postclean.log`, `sweep-inst.log`, `sweep-diag.log`, gates `…-1977433`, `…-2144707`, `…-2375685` | **Correction to revision 2: R9 did not run the same compilations.** It @@ -153,9 +168,9 @@ different Cargo compilations: | prior targets execute | compilation set | result | |---|---|---| -| yes | `-91f51d0b` + `-6b4b8223` (subset selection; `prefix.log:3066`, `:3087`) | R9 green | -| no | `-5d9105cb` + `-d4dae4f0` (workspace selection; `wsonly.log:3`, `:24`) | R10 green | -| yes | workspace selection | **F1–F7 red (7)** | +| yes | `-91f51d0b` + `-6b4b8223` (`prefix.log:3066`, `:3087`) | R9 green | +| no | `-5d9105cb` + `-d4dae4f0` (`wsonly.log:3`, `:24`) | R10 green | +| yes | `-5d9105cb` + `-d4dae4f0`, all seven | **F1–F7 red (7)** | Neither factor alone reproduced it **in these runs**. That is the whole of the claim. `--workspace` artifact selection is **not @@ -207,18 +222,25 @@ after that run ended at 08:42:01. **Cleanliness was captured for neither**, and the tree was under active edit throughout. So the window dates a **machine/worktree-state transition**, not two clean revisions. -One further relationship is worth stating **only for what it shows**: -`72da24a` is an **ancestor** of `7599661` (verified by -`git merge-base --is-ancestor`), yet `72da24a` fails today while -`7599661` passed on 08-15. That establishes exactly one thing — -**outcome is not determined by commit alone** — because the two -observations are from different environments at different times. +One further relationship is worth recording **only to say what it +cannot support**: `72da24a` is an **ancestor** of `7599661` (verified +by `git merge-base --is-ancestor`), yet `72da24a` fails today while +`7599661` passed on 08-15. **These two observations are +non-comparable** — they differ in commit *and* in environment *and* in +time — so **no causal conclusion of any kind may be drawn from the +pair**. -**Revision 5 drew more from it than it carries, and that is -withdrawn.** It said a source cause was "positively discouraged", that -the ancestry "says to expect" equal endpoints, and that "whatever -changed is environmental, cached, or uncommitted". None of that -follows. The observation cannot distinguish: +Revision 6 read it as "outcome is not determined by commit alone". +**That is withdrawn too**: different commits can deterministically +produce different outcomes, and this document's own fix-then-regression +scenario is an example. The pair supports nothing about determinism +either way. + +**Revision 5 drew still more from it.** It said a source cause was +"positively discouraged", that the ancestry "says to expect" equal +endpoints, and that "whatever changed is environmental, cached, or +uncommitted". None of that follows either. Nothing in the pair +distinguishes: - an environmental change; - a source/environment interaction; or @@ -291,12 +313,26 @@ group. - **D0a — reproduce the onset endpoints CLEANLY** (§4a): `7599661` (last observed green) and `724b785` (first observed red), each checked out clean, each in its own isolated target directory. This is - a **decision procedure with no predicted outcome**: - - **endpoints differ** → a regression lives in `7599661..724b785` - and a Git bisect over that interval is justified; - - **endpoints agree** → the difference is not captured by those two - commits under current conditions, and the next question is what - else changed across the window. + a **decision procedure with no predicted outcome**. One run per + endpoint decides nothing — this failure is context-sensitive by + construction, appearing only in the full sweep — so the procedure is + specified rather than left to judgement: + - **N = 5 full `sweep-crdt` runs per endpoint**, since the observed + failure rate in the reproducing configuration is 7/7 and the + passing configuration 13/13; anything less cannot separate a real + difference from the intermittency that has not yet been excluded. + - **Interleaved**, alternating endpoints A/B/A/B…, so any drift in + machine state across the session hits both arms equally instead of + landing entirely on whichever ran second. + - **Identical captured conditions per run**: same harness as D0b — + argv, worktree, `git rev-parse HEAD`, `git status --porcelain` + emptiness, the Cargo suffixes executed, result, log digest — plus + the machine facts that have already misled this lane once + (`uptime`, `free`, `/tmp` usage, leaked-daemon count). + - **Permitting a bisect requires a clean split**: all N of one + endpoint red and all N of the other green. A mixed result means the + failure is intermittent under fixed source, and **no bisect is + justified at all** — that outcome sends the lane back to D1/D2. - **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 @@ -337,8 +373,8 @@ Written now so the fix cannot quietly become "make the test pass". revision 1 stated an impossible precondition. Post-merge confirmation on `main` is a follow-up, not a gate on the fix. - **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 + different Cargo compilations, 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 diff --git a/docs/probe-sigint-evidence.md b/docs/probe-sigint-evidence.md index 3fcb0c6..d8620a6 100644 --- a/docs/probe-sigint-evidence.md +++ b/docs/probe-sigint-evidence.md @@ -50,6 +50,10 @@ withdrawn as a grouping. Three levels of knowledge, kept apart: +0. **Portability caveat.** Suffixes below are read from logs that are + **machine-local**; this manifest is the portable record of them, and + a reader elsewhere is trusting this transcription, not verifying it. + Rows that were never logged say `UNKNOWN` and are not guessed. 1. **Suffix — known**, from each log. A differing suffix means Cargo computed a different metadata hash, i.e. it treated the two as distinct compilations. @@ -81,16 +85,16 @@ except `WT=main`, which uses `…/pmacs-fdccc423`. | 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 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 | `gpu_invocation…-6b4b8223` **only** — R7 does not select `gpu_initial_target` | 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 | `-91f51d0b` / `-6b4b8223` per log | green | `8b26ebfcf5f871b4` 28677 | +| 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 | `-91f51d0b` (`half2.log:1`) / `-6b4b8223` | green | `8b26ebfcf5f871b4` 28677 | | R9 | `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 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 | **`-91f51d0b` / `-6b4b8223`** (log `:3066`, `:3087`) | 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` AND `-d4dae4f0`** (log `:3`, `:24`) | 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`; today's occupants `e0578039…` / `00f06aeb…` | **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 | **`724b785`** (see below) | UNKNOWN | suffixes per log | **red** (2 bins, both ctrl_c copies) | `20260816T063330Z-1977433/09-sweep-crdt.log`, sha `7a75d999ac4f` | -| F7 | stage 9 of `./scripts/gate --protocol` | mg | `5174f73` (committed 08:45:41, run began 08:45:49) | UNKNOWN | suffixes per log | **red** (2 bins, both ctrl_c copies) | `20260816T064549Z-2144707/09-sweep-crdt.log`, sha `9d3c6ad1bfc9` | +| F2 | `test --workspace --features crdt --no-fail-fast -- --skip basedpyright` | mg | ~`b72843a` | UNKNOWN | `-5d9105cb` / `-d4dae4f0` (`postclean.log`) | **red** | `474f88f0dad581fe` 338555 | +| F3 | same argv as F2, with a resource sampler running | mg | ~`b72843a` | UNKNOWN | `-5d9105cb` / `-d4dae4f0` (`sweep-inst.log`) | **red** | `7b8519e7300e8bb3` 338555 | +| F4 | same argv as F2, with a process-table sampler running | mg | ~`b72843a` | UNKNOWN | `-5d9105cb` / `-d4dae4f0` (`sweep-diag.log`) | **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 | `-5d9105cb` / `-d4dae4f0` | **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 | **`724b785`** (see below) | UNKNOWN | `-5d9105cb` / `-d4dae4f0` | **red** (2 bins, both ctrl_c copies) | `20260816T063330Z-1977433/09-sweep-crdt.log`, sha `7a75d999ac4f` | +| F7 | stage 9 of `./scripts/gate --protocol` | mg | `5174f73` (committed 08:45:41, run began 08:45:49) | UNKNOWN | `-5d9105cb` / `-d4dae4f0` | **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 @@ -144,11 +148,13 @@ clean source revisions. One relationship is worth recording **for exactly what it shows**: `72da24a` is an **ancestor** of `7599661` (`git merge-base --is-ancestor`), yet fails today (F1) while `7599661` passed on 08-15. -That shows **outcome is not determined by commit alone** — the two -observations come from different environments at different times — and -nothing further. It does **not** discriminate an environmental change, -a source/environment interaction, or a fix before `7599661` followed by -a regression before `724b785`. An ancestor outside the interval is +The two observations differ in commit **and** environment **and** +time, so they are **non-comparable and support no causal conclusion of +any kind** — not even "outcome is not determined by commit alone", +since different commits can deterministically produce different +outcomes. The pair does not discriminate an environmental change, a +source/environment interaction, or a fix before `7599661` followed by a +regression before `724b785`. And an ancestor outside the interval is irrelevant to whether the interval contains a regression. ## D0 — re-run the matrix with captured provenance