a73x

c37bc263

docs: M15 closed — 12 tasks, 37 commits, suite unchanged at 20/33, soak 10/10, regrade 3/3 at the unit layer, refused dial 15001ms→1ms

a73x   2026-08-12 21:06

Commit message
docs: M15 closed — 12 tasks, 37 commits, suite unchanged at 20/33, soak 10/10, regrade 3/3 at the unit layer, refused dial 15001ms→1ms

The refactor milestone, recorded: typed invariants, single owners, module
seams, and exactly two behaviour changes — both in QUIC dialling, both
the same omission seen from two sides (nothing distinguished a refusal
from silence). Deadline 2000 stands; silence is still what sets it.

decisions.md gains the M15 section: the verdict and the size split
(server.zig 4708→4474, client.zig 2406→2506 of which +93 is test code),
both behaviour tables local and on the LAN box against a deliberately
M14-era daemon, the regrade table with its printed failure lines, seven
findings stated as rules, the nine plan amendments made at source, the
survey-accuracy finding, the coverage that got its first pin, the M16
candidates, the process notes and the errata. M14's quic_owned paragraph
gains its superseded annotation.

roadmap.md gains the M15 entry, retires the drain()/ECONNREFUSED and
transport-union candidates as paid, corrects two stale present-tense
claims M15 falsified, and opens the M16 candidate list.

README needed no change: its only QUIC timing claim is the ~2s deadline
for a UDP-blocked attach, which is the silent case and still measures
2002ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

docs/decisions.md
Old New
@@ -2339,7 +2339,9 @@ the load-bearing arm is `serviceObserver` for M13's reason: `muxd
2339 endpoint` never attaches, so it is an observer for its whole life. A 2339 endpoint` never attaches, so it is an observer for its whole life. A
2340 `quic_owned` flag records whether the listener was created by this path, 2340 `quic_owned` flag records whether the listener was created by this path,
2341 so `deinit` hands back exactly what it took and a daemon started with an 2341 so `deinit` hands back exactly what it took and a daemon started with an
2342 explicit `--quic` keeps ownership of its own. 2342 explicit `--quic` keeps ownership of its own. **Became a `union(enum)`
2343 in M15 (Task 9)**, which makes the `(null, owned)` pair untypable rather
2344 than merely unreachable.
2343 2345
2344 `src/handoff.zig` — one grammar, one place. Cache read/write, announce 2346 `src/handoff.zig` — one grammar, one place. Cache read/write, announce
2345 `format`/`parse`, the dial-host strip and the deadline constant all live 2347 `format`/`parse`, the dial-host strip and the deadline constant all live
@@ -2492,6 +2494,273 @@ tempting alternatives all produce a green report that means nothing.
2492 grep and its no-budget bound were never driven red. Cheap candidates 2494 grep and its no-budget bound were never driven red. Cheap candidates
2493 for the next regrade, and cheaper than the audit that found them. 2495 for the next regrade, and cheaper than the audit that found them.
2494 2496
2497 ## 2026-08-12 (M15 — refactor: typed invariants, single owners, module seams)
2498
2499 **Verdict: cleared.** Twelve tasks, each run as implementer + spec
2500 reviewer + quality reviewer, landed in **37 commits without moving the
2501 product**: the e2e suite stayed at **20 scenario checkpoints over 33
2502 convergence points** from the first commit to the last, and `SOAK_N=10
2503 make soak` was **10/10 on the code that ships** (445d80b). The unit layer
2504 grew from **245 to 255 tests**, and exactly **two behaviours changed** —
2505 both in QUIC dialling, both intended, both measured below. Spec:
2506 superpowers/specs/2026-08-12-m15-refactor-design.md.
2507
2508 Four modules were carved out with their tests — `paint.zig` (295),
2509 `quic.zig` (586), `delta.zig` (233), `sockpath.zig` (122) — and the two
2510 files they came out of shrank where it counts: `server.zig` **4708 →
2511 4474**, `quic_server.zig` **2412 → 1972**. `client.zig` went the other
2512 way, **2406 → 2506**, and the split says why: its implementation half is
2513 flat at **1640 → 1647**, and **+93 of the +100 is new test code**. That
2514 is the coverage section below arriving in the line count, and it is the
2515 reason a whole-file `wc -l` is a poor clause to write a refactor against
2516 — it charges a file for the pins the refactor bought it.
2517
2518 ### The two behaviour changes
2519
2520 Both live in QUIC dialling, and both were the same omission seen from
2521 two sides: **nothing distinguished a refusal from silence.**
2522
2523 `drain()` had been discarding the queued ICMP refusal with a bare `catch
2524 return` — M14 recorded this as an incidental — so the `ConnectionRefused`
2525 branch in `readable` never saw it and every failure ran the deadline out.
2526 Task 4 gave both call sites one verdict, `sendRecvFailed`, so a refusal
2527 is acted on wherever it lands. Separately, `quic://` had no attach budget
2528 of its own and gave up only at the connection's 15000ms idle timeout;
2529 `QuicTarget.deadline_ms` split the two, so the attach budget is 2000ms
2530 and `idle_ms` goes back to meaning what its name says.
2531
2532 Local, medians, same box, before and after:
2533
2534 | Dial | Before | After |
2535 | --- | --- | --- |
2536 | refused — an unbound loopback port | 15001ms | **1ms** |
2537 | blackholed address | 15001ms | **2002ms** |
2538 | wrong PSK — a live port answering nothing | 15009ms | **2012ms** |
2539
2540 **The 2000ms deadline stands, and silence is still what sets it.** Only
2541 the refusal got fast; the two silent cases land within 12ms of the
2542 budget, which is the deadline doing its job rather than a bound anyone
2543 tightened.
2544
2545 The named cost: **`--quic-idle-ms` no longer raises the attach budget by
2546 accident.** It used to, because the attach shared the idle timeout, and
2547 that made it an undocumented escape hatch for a slow link. The hatch is
2548 gone. It is irrelevant below roughly 645ms RTT, and no path mux has been
2549 measured on comes close.
2550
2551 ### On the LAN box, against an M14-era daemon (2026-08-12)
2552
2553 The box at `192.168.0.109` was left on its pre-M15 `muxd` **on purpose**.
2554 Both behaviour changes are client-side, so measuring against the old
2555 daemon also measures cross-version compatibility. Version strings match
2556 (`mux 0.0.1-3` local, `muxd 0.0.1-3` remote) because the release was not
2557 cut until M15 closed; the binaries do not — the remote one was built at
2558 **06:27**, eight hours before M15's first commit at 14:31.
2559
2560 | Leg | Reps | Median |
2561 | --- | --- | --- |
2562 | refused — `quic://192.168.0.109:11997`, the box answers ICMP | 1 / 1 / 1 ms | **1ms** |
2563 | blackholed — `quic://192.0.2.1:11997` | 2002 / 2002 / 2003 ms | **2002ms** |
2564 | control — attach `quic://192.168.0.109:4433` | exit 0, marker painted, `\034` detach clean, daemon reported `clients=0` | — |
2565
2566 The control leg is the cross-version one: an M15 client drove a session
2567 on an M14 daemon, painted, detached cleanly and was observed gone from
2568 the daemon's own count. The refusal that cost ~15s before M15 now costs
2569 1ms across a real network, and the blackhole still costs one deadline.
2570
2571 ### The regrade table
2572
2573 One sitting, one tree, one set of binaries. Each mutant was required to
2574 **compile**, `git status --porcelain` was empty and `make test` green
2575 between every row, and every revert was a file copy — never a `git
2576 checkout`, for the reason in the process note.
2577
2578 | Resurrection | Layer | Test that answered | Verdict |
2579 | --- | --- | --- | --- |
2580 | `drain()`'s send-error arm reverted to `catch return` (leaving `sendRecvFailed` itself) | unit | `client: handoff: dead coordinates are a fast no, and the pipe is the fallback` | **CAUGHT** |
2581 | `Transport.open`'s `.sock` and `.via` arm bodies swapped, captures renamed so the bodies moved verbatim | unit | `client: Transport.open: a --via target yields a pipe, a --sock target an fd` | **CAUGHT** |
2582 | `keyRefusalBody`'s `chmod 600` → `chmod 601`, implementation line only | unit, **two** | `quic: keyRefusalBody: the words four binaries print, byte for byte` **and** `client: openFailure: a quic:// target names the key or the address, and only an abort exits 0` | **CAUGHT** |
2583
2584 **All three answered at the unit layer; `make e2e` was never needed.**
2585 That is not the unit-layer mutation sweep the roadmap has banked since
2586 M11 — three aimed mutants are not a sweep — but it is the third data
2587 point arguing for one, and the cheapest yet: seconds per row instead of
2588 the two minutes an e2e run costs.
2589
2590 Each catch named the property rather than the symptom. The refusal
2591 mutant printed its witness line:
2592
2593 ```
2594 error: 'client.test.handoff: dead coordinates are a fast no, and the pipe is the fallback' failed: mux: quic://127.0.0.1:1 unreachable, attaching over ssh
2595 refused dial took 302ms of a 300ms budget: the ICMP refusal was swallowed, not acted on
2596 ```
2597
2598 — a number, a bound, and the diagnosis, in one sentence. (The first line
2599 carries the client's own stderr, which is the fallback working
2600 correctly; the assertion is the second.) The dispatch mutant printed its
2601 named decision:
2602
2603 ```
2604 error: 'client.test.Transport.open: a --via target yields a pipe, a --sock target an fd' failed: a --via target must spawn a command, not connect a socket: open failed with FileNotFound
2605 ```
2606
2607 The literal mutant printed a byte-for-byte diff at **both** layers, the
2608 vocabulary module's and the caller's, with `('\x30')` against `('\x31')`
2609 under the caret — which is the one-owner refactor demonstrating that it
2610 kept two independent pins rather than collapsing them into one.
2611
2612 ### Findings
2613
2614 Seven, each stated as the rule it became.
2615
2616 1. **A panic prints no assertion, so pin ORDERING buys attribution.** A
2617 mutation that aborts is charged to whichever test is running, not to
2618 the test whose property it violated — unless the pin that states the
2619 property runs first. Ordering is not cosmetic in a suite that grades
2620 itself by mutation.
2621 2. **A comptime error is a third prints-nothing route.** The module
2622 simply vanishes from the run (227 of 251 tests, and no property
2623 named) rather than failing loudly. Length-asserts go first and the
2624 slices get sized at runtime, so a bad mutation fails as a test rather
2625 than as an absence.
2626 3. **Flipping a conjunction's operands is not negation.** Shared
2627 structure absorbs it — two paths on one filesystem share a `dev`, so
2628 swapping the operands of the identity check changed nothing
2629 observable. A mutation meant to negate must be written `!`.
2630 4. **When a pin shares the implementation's literal, a sentence-wide
2631 `sed` self-heals.** Mutate the implementation line and nothing else,
2632 or the mutation edits its own detector and grades zero.
2633 5. **A substring pin is satisfiable by incidental prose.** Anchor to the
2634 position that carries the meaning — `"\n muxd " ++ name`, not
2635 `name` — or the pin passes on a mention rather than a definition.
2636 6. **Wire-format mutations wedge the suite silently.** Second data
2637 point; a wedged step prints nothing at all, so it must be watched by
2638 wall clock rather than by output.
2639 7. **An e2e scenario can die UNNAMED under `set -eu`** when the failing
2640 pipeline is the client itself: the script's own `FAIL` line never
2641 prints. Banked as legibility debt.
2642
2643 ### Plan amendments, all made at source
2644
2645 **Nine**, each written into the plan with the evidence that forced it,
2646 rather than worked around in an implementation. The pattern worth
2647 keeping is that the plan was wrong in ways only the code could reveal:
2648
2649 - Task 1's assumed frame `0x86` was `stats_reply`; `exit_status` is
2650 `0x82`.
2651 - `writeFrame`'s "golden pin" was a false doc claim — there was no such
2652 pin until the task wrote one.
2653 - `ensurePtyModeSent` was **reversed**: the name promised a skip the body
2654 did not perform.
2655 - The strike named the wrong target (`waitReady`'s doc, not
2656 `default_idle_ms`).
2657 - Task 5's specified mutation aimed at an unpinned function, so
2658 `renderClipped`'s epilogue got its first pin as a rider.
2659 - Task 6's regrade premise was false — there was **no** unit pin on
2660 sock/via dispatch at all; the rider added it, and it is regrade row 2
2661 above.
2662 - Task 7's null-means-silent premise was false: aborts print.
2663 - Task 10's specified mutation was operand-flipping, not negation
2664 (finding 3).
2665 - Task 11's `sed` note, which became finding 4.
2666
2667 Correction ran in every direction, which is the part that is easy to
2668 lose. Reviews corrected implementers; **implementers corrected
2669 reviewers** (an error-set `||` needs parens on 0.15.2; a sketch that was
2670 described rather than quoted got rewritten from the spec); and
2671 **reviewers corrected the lead** — the `ensurePtyModeSent` reversal,
2672 `egress_cap`'s home, the vestigial stat-before-close ordering, and the
2673 anchored usage pin were all lead decisions overturned with evidence.
2674
2675 ### What a survey is good for
2676
2677 The pre-task surveys **located structure correctly and systematically
2678 undercounted instances**: 17 union edit sites turned out to be 33, 5 path
2679 literals 6, 8 message classes 12. One survey premise was simply false
2680 (the dispatch decision was described as pinned; it was not).
2681
2682 **Surveys locate; they do not count.** A plan that budgets from a survey
2683 count is budgeting from a lower bound.
2684
2685 ### Coverage: what got its first pin
2686
2687 M15 was a refactor, so most of its test growth is coverage that had never
2688 existed rather than coverage that moved. First-ever pins landed for:
2689
2690 - the refusal **wire bytes**, plus the proof that the QUIC and socket
2691 producers emit the same ones;
2692 - `writeFrame`'s golden bytes;
2693 - `renderClipped`'s epilogue **and** its prologue position;
2694 - the transport dispatch decision;
2695 - `openFailure`'s **12 message classes** and its truncation policy;
2696 - `keyRefusalBody`;
2697 - `PathId`, including the historical sockfs bug carried as a live
2698 assertion rather than a comment;
2699 - `oneShotQuery`'s nobody-serving exit;
2700 - usage-names-every-verb, anchored (finding 5).
2701
2702 Two pieces of coverage debt were banked rather than paid: `uses_socket`
2703 is pinned for **1 of its 9 rows** (`dump`; `--version`'s row is
2704 structurally dead), and a session-full refusal **over QUIC** is still
2705 unexercised behaviourally.
2706
2707 ### Banked by M15 (the M16 candidates)
2708
2709 - **The stat-after-close reorder in `deinit`** — narrows the window in
2710 which a successor daemon's socket could be claimed by a departing
2711 one's cleanup. The analysis is already written, in the `sockpath`
2712 comment; only the reorder is owed.
2713 - **`egress_cap` ↔ transport-params, one owner** — `256*1024` is spelled
2714 three times and the tie between the copies is prose only.
2715 - **Fold `Transport`'s `alloc`/`qout` into the `.quic` `Link` payload** —
2716 deletes the struct's one remaining `undefined`.
2717 - **`Pty.write` is dead in production** — adopt or delete.
2718 - **e2e scenario-naming legibility** — finding 7: a scenario that dies
2719 unnamed, because `set -eu` kills the script before its own `FAIL` line.
2720 - **`predict.zig`'s retired channel** — adopt or delete.
2721 - **`keyRefusalBody`'s e2e coverage** — the announce-none path pins it
2722 only loosely; regrade row 3 was caught by unit pins alone.
2723
2724 ### Process note
2725
2726 **The file-copy revert idiom is now standing practice.** It became one
2727 the expensive way: a `git checkout` used as a mutation revert ate an
2728 uncommitted test. Copy the pristine file aside, mutate, copy it back,
2729 and verify with `git status --porcelain` plus a green `make test` before
2730 the next row.
2731
2732 **Relay sketches verbatim, never described.** A described sketch was
2733 reconstructed wrongly and had to be rewritten from the spec — one of the
2734 nine amendments above.
2735
2736 Two permission-classifier blocks were handled correctly: the agent
2737 stopped, reported the step as **unobserved** rather than assumed, and
2738 handed it to an implementer. An unobserved step reported as observed is
2739 the one failure mode this process has no other guard against.
2740
2741 **The soak's pre-flight hygiene check earned its keep at the close.** The
2742 first `SOAK_N=10` invocation refused to start, naming one stray
2743 `mux-e2e-*` file in `/tmp` left by an earlier run. It was real evidence:
2744 a capture of `muxd dump --sock` printing `nothing listening on …` where
2745 the `sun_path` scenario demands `socket path too long`. It was also
2746 **stale** — the current binaries refuse the long path in both binaries,
2747 and the file's timestamp put it inside Task 12's working tree, between
2748 the commit before the subcommand spec table and the commit that landed
2749 it. The check cannot tell a live leak from a fixed one, which is exactly
2750 why it refuses to guess and makes a human look. Inspect, confirm against
2751 the current binaries, then clear — never clear first.
2752
2753 ### Errata
2754
2755 Recorded rather than rewritten, since the commits are published:
2756
2757 - Task 4's fix commit says "eight named cascade failures"; seven are
2758 below the pin.
2759 - Task 9's commit message claims the test edits were three and nothing
2760 else; two comments also changed.
2761 - "12 pins" in Task 7's report was the message-**class** count, not a
2762 count of pins.
2763
2495 ## Open (owed by later milestones) 2764 ## Open (owed by later milestones)
2496 2765
2497 - Scrollback retention *tuning*. The policy itself was decided in M1 and 2766 - Scrollback retention *tuning*. The policy itself was decided in M1 and
docs/roadmap.md
Old New
@@ -5,7 +5,7 @@ The forward view, one item per line, ranked. History and evidence live in
5 this file at each milestone close and whenever the queue reorders; the 5 this file at each milestone close and whenever the queue reorders; the
6 queue's order is set by the user, not by this file. 6 queue's order is set by the user, not by this file.
7 7
8 **Now:** M1–M14 complete; `v0.0.1-2` published as a Linux tarball; in 8 **Now:** M1–M15 complete; `v0.0.1-2` published as a Linux tarball; in
9 field trial on real VMs, and the trial is now producing the queue. 9 field trial on real VMs, and the trial is now producing the queue.
10 Trial feedback outranks everything below — what actually hurts in use is 10 Trial feedback outranks everything below — what actually hurts in use is
11 better data than any of this ranking, and the proof is that the three 11 better data than any of this ranking, and the proof is that the three
@@ -136,26 +136,75 @@ still landing the session over ssh, one deadline and one line later
136 En route: the announce had to become **mandatory in both directions** 136 En route: the announce had to become **mandatory in both directions**
137 because silence and a slow ssh are indistinguishable at the reading end 137 because silence and a slow ssh are indistinguishable at the reading end
138 — `endpoint none` is now an explicit negative — and the deadline was 138 — `endpoint none` is now an explicit negative — and the deadline was
139 pinned from measurement showing there is **no fast-failure case** at 139 pinned from measurement showing there was **no fast-failure case** at
140 all, not even a refused loopback port. 140 all, not even a refused loopback port *(refusal now fails fast as of
141 141 M15: 1ms, local and on the LAN box)*.
142 ## M15 candidates — and still outranked by trial feedback 142
143 143 ## M15 — refactor: typed invariants, single owners, module seams — complete
144 What remains is what was already ranked behind M13 and M14, plus two 144
145 items M14 created. Nothing here is a field finding; the next one that 145 **Verdict: cleared.** Twelve tasks, each run as implementer + spec
146 arrives outranks all of it. 146 reviewer + quality reviewer, landed in **37 commits without moving the
147 147 product**: the suite held at **20 scenario checkpoints over 33
148 - **`drain()` swallows `ECONNREFUSED`** — created by M14 and the 148 convergence points** from first commit to last, `SOAK_N=10 make soak` was
149 cheapest win on the list. On a connected UDP socket the queued ICMP 149 **10/10 on the code that ships**, and the unit layer grew **245 → 255**
150 refusal is consumed by the `sendto` in `Client.drain`, whose `catch 150 tests. Four modules were carved out with their tests — `paint.zig`,
151 return` discards it, so the `ConnectionRefused` branch never fires and 151 `quic.zig`, `delta.zig`, `sockpath.zig` — and `server.zig` fell **4708 →
152 an unreachable port costs the full 2s deadline instead of ~1 RTT. 152 4474**, `quic_server.zig` **2412 → 1972**.
153 Fixing it re-times two existing bounds rather than deleting them 153
154 (decisions.md, M14). 154 Exactly **two behaviours changed**, both in QUIC dialling and both the
155 - **The transport recipe as a `union(enum)`** — also from M14. 155 same omission seen from two sides: nothing distinguished a refusal from
156 `Transport.open` takes four mutually-exclusive nullables, two of them 156 silence. A refused dial went **15001ms → 1ms**, and `quic://` got an
157 sharing a type; `union(enum) { sock, via, quic, hand }` would make the 157 attach budget of its own instead of inheriting the 15000ms idle timeout.
158 invariant unstatable-wrong instead of merely documented. 158 The 2000ms deadline stands and silence is still what sets it — a
159 blackholed address costs 2002ms, a wrong PSK 2012ms. Confirmed on the
160 LAN box against a deliberately **M14-era daemon**, which made the same
161 run a cross-version compatibility check: refusal 1ms, blackhole 2002ms,
162 and an M15 client attaching, painting and detaching cleanly on the old
163 daemon. The named cost is that `--quic-idle-ms` no longer raises the
164 attach budget by accident; that escape hatch is gone, and irrelevant
165 below ~645ms RTT.
166
167 Leg 2 is three for three, and **all three were caught at the unit
168 layer** — `make e2e` was never needed, which is a third argument for the
169 unit-layer mutation sweep still banked below, at seconds a row instead of
170 two minutes. En route, the surveys that fed the plan were found to
171 **locate structure correctly and undercount instances every time** (17
172 union edit sites were 33, 5 path
173 literals were 6, 8 message classes were 12), which forced **nine plan
174 amendments, each made at source**; and most of M15's test growth is
175 coverage that had never existed — the refusal wire bytes, `writeFrame`'s
176 golden bytes, the transport dispatch decision, `openFailure`'s 12 message
177 classes and `keyRefusalBody` all got their **first** pins
178 (decisions.md, M15, which carries the tables and the failure lines).
179
180 ## M16 candidates — and still outranked by trial feedback
181
182 What remains is what was ranked behind M13 and M14 and survived M15,
183 plus the seven items M15 created. Nothing here is a field finding; the
184 next one that arrives outranks all of it.
185
186 - ~~**`drain()` swallows `ECONNREFUSED`**~~ — paid by M15 (Task 4). Both
187 socket paths now route the refusal through one verdict, and an
188 unreachable port fails in **1ms** instead of a full deadline
189 (decisions.md, M15).
190 - ~~**The transport recipe as a `union(enum)`**~~ — paid by M15 (Task 6).
191 `Transport.open` takes `union(enum) { sock, via, quic, hand }`, and the
192 dispatch decision it makes got its first pin in the same task.
193 - **The stat-after-close reorder in `deinit`** — created by M15 (Task
194 10). Narrows the window in which a departing daemon's cleanup could
195 unlink a successor's socket. The analysis is already written, in the
196 `sockpath` comment; only the reorder is owed.
197 - **`egress_cap` ↔ transport-params, one owner** — also M15. `256*1024`
198 is spelled three times and the tie between the copies is prose only.
199 - **Fold `Transport`'s `alloc`/`qout` into the `.quic` `Link` payload** —
200 deletes the struct's one remaining `undefined`.
201 - **`Pty.write` is dead in production** — adopt or delete.
202 - **e2e scenario-naming legibility** — a scenario can die *unnamed*,
203 because under `set -eu` a failing client pipeline kills the script
204 before its own `FAIL` line prints (decisions.md, M15, finding 7).
205 - **`predict.zig`'s retired channel** — adopt or delete.
206 - **`keyRefusalBody`'s e2e coverage** — the announce-none path pins it
207 only loosely; M15's regrade caught that mutant on unit pins alone.
159 - **First-backoff tuning**: a reconnect after a tear can pay a 200ms–2s 208 - **First-backoff tuning**: a reconnect after a tear can pay a 200ms–2s
160 backoff step; measured ~4x win available on fast links, ~2x on slow 209 backoff step; measured ~4x win available on fast links, ~2x on slow
161 (decisions.md, M7). 210 (decisions.md, M7).
@@ -184,7 +233,9 @@ arrives outranks all of it.
184 delta row's leading reset, where the existing unit pin turned out to 233 delta row's leading reset, where the existing unit pin turned out to
185 be the whole guard). Both were discovered by accident, while aiming an 234 be the whole guard). Both were discovered by accident, while aiming an
186 e2e campaign at the product. A sweep aimed at the unit layer would not 235 e2e campaign at the product. A sweep aimed at the unit layer would not
187 need the accident. 236 need the accident. M15 adds the cost argument: all three of its regrade
237 mutants died at the unit layer, in seconds a row, and `make e2e` was
238 never reached (decisions.md, M15).
188 - **ASAN or valgrind over the QUIC tests** — carried from M9 unchanged, 239 - **ASAN or valgrind over the QUIC tests** — carried from M9 unchanged,
189 and unchanged is the finding: an e2e mutation campaign says nothing 240 and unchanged is the finding: an e2e mutation campaign says nothing
190 about use-after-free. M9's two reasoned-not-pinned `deinit` orderings 241 about use-after-free. M9's two reasoned-not-pinned `deinit` orderings