e032ef40
docs: the split, the first full coverage map, and the race it found
a73x 2026-08-26 18:04
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -23,10 +23,11 @@ First build after a clean checkout fetches ~30MB of QUIC deps (minutes, once). | |||
| 23 | Files are large and comment-dense (~44% of Zig bytes are `//`). Reading the repo | 23 | Files are large and comment-dense (~44% of Zig bytes are `//`). Reading the repo |
| 24 | costs ~800k tokens; every token stays in context and is re-billed each turn. | 24 | costs ~800k tokens; every token stays in context and is re-billed each turn. |
| 25 | 25 | ||
| 26 | - **Never `cat` these:** `src/server.zig` (4.0k lines, ~40k tok; its tests are | 26 | - **Never `cat` these:** `src/server.zig` (3.5k lines, ~35k tok; its tests are |
| 27 | siblings, `src/server_test_*.zig`, none over 1.6k), | 27 | siblings, `src/server_test_*.zig`, none over 1.6k; the agent relay and the |
| 28 | `test/e2e.sh` (7.0k), `docs/decisions.md` (4.9k), `src/wallview.zig` (3.0k), | 28 | session table are `src/server_agent.zig` and `src/server_sessions.zig`), |
| 29 | `src/interact.zig` (2.9k). | 29 | `test/e2e.sh` (9.7k), `docs/decisions.md` (7.0k), `src/wallview.zig` (5.5k), |
| 30 | `src/interact.zig` (4.5k). | ||
| 30 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. | 31 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. |
| 31 | `src/client.zig` is 1.9k now — cheap enough to read. | 32 | `src/client.zig` is 1.9k now — cheap enough to read. |
| 32 | - Every module has a `//!` header stating its contract. `head -12 src/X.zig` | 33 | - Every module has a `//!` header stating its contract. `head -12 src/X.zig` |
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -7015,3 +7015,33 @@ already carries this feature, so the first release with it is the floor. Until | |||
| 7015 | then the same-binary leg (`--allow-same-version`, a flag whose help text says | 7015 | then the same-binary leg (`--allow-same-version`, a flag whose help text says |
| 7016 | it exists for the e2e) is the v1 gate, and the cross-version leg is a documented | 7016 | it exists for the e2e) is the v1 gate, and the cross-version leg is a documented |
| 7017 | gap rather than a test that quietly does not run. | 7017 | gap rather than a test that quietly does not run. |
| 7018 | |||
| 7019 | ## 2026-08-26 — hygiene: server.zig split, coverage that finishes | ||
| 7020 | |||
| 7021 | **server.zig is three files and a test tree.** 12,003 lines → 3,464: the 132 | ||
| 7022 | tests moved to nine `src/server_test_*.zig` siblings (bodies byte-identical, | ||
| 7023 | 821 unit tests before and after, a build.zig gate on the import list because | ||
| 7024 | a dropped `_ = @import` is a domain of tests that passes by not running); the | ||
| 7025 | agent relay (seven fields touched by thirteen functions and almost nothing | ||
| 7026 | else) and the session table became types in `server_agent.zig` (360) and | ||
| 7027 | `server_sessions.zig` (298), each reaching the daemon through named things | ||
| 7028 | only; `handleFrame`'s 16 arms became methods. The pump stayed: `pumpOnce`, | ||
| 7029 | `serviceObserver`, `drainPending`, `checkAwaits` are the product. | ||
| 7030 | |||
| 7031 | **`make coverage` had never finished the suite.** Six stops, one disease: a | ||
| 7032 | pid the suite holds that under kcov is the tracer's. `endpoint` now runs bare | ||
| 7033 | like `start` (the handoff client kill()s and waits on an ssh child that is | ||
| 7034 | kcov); `softkill` is hardkill's TERM twin (ptrace drops a signal sent to the | ||
| 7035 | tracer); the upgrade candidate is the ELF behind the shim (`MUXD_ELF`); | ||
| 7036 | `real_pid` reads /proc off the tracee; the stop-gone leg names the pid `stop` | ||
| 7037 | waited on. First full map, ReleaseSafe so lines merge: TOTAL 77% of 3,197 | ||
| 7038 | instrumented lines, server.zig 80% of 931, quic_server 92%, webhub 88%. | ||
| 7039 | `initFromManifest` reads 0/58 because kcov does not instrument the post-exec | ||
| 7040 | image; its unit tests own it. The map is a worklist, not a score. | ||
| 7041 | |||
| 7042 | **The tracer found a product race.** `muxa send` on a refused attach wrote | ||
| 7043 | input blind after the attach; when the daemon's close beat the write, EPIPE | ||
| 7044 | was reported instead of the refusal. Rare untraced, certain under ptrace | ||
| 7045 | (scenario 75 stopped there every run). Fixed at one owner: a peer-closed | ||
| 7046 | send drains the read side through `awaitFrame`, so the snapshot rule that | ||
| 7047 | tells a refusal from an ending is not copied. | ||
src/server_test_upgrade.zig
| Old | New | ||
|---|---|---|---|
| @@ -18,8 +18,8 @@ test "writeManifestTo: every session's own name crosses, not the last one's" { | |||
| 18 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); | 18 | var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); |
| 19 | defer srv.deinit(); | 19 | defer srv.deinit(); |
| 20 | 20 | ||
| 21 | // Three sessions of DIFFERENT name lengths. One session cannot catch a | 21 | // Three sessions, each with a name of its own. One session cannot catch |
| 22 | // name that aliases its neighbour's buffer, which is how a by-value | 22 | // a name that aliases its neighbour's buffer, which is how a by-value |
| 23 | // `Session` copy in the gather loop shipped every record pointing at | 23 | // `Session` copy in the gather loop shipped every record pointing at |
| 24 | // the last name: right length, wrong bytes. | 24 | // the last name: right length, wrong bytes. |
| 25 | srv.sessions[1] = try Server.createSession(alloc, srv.spawn_plan, "work", 80, 24, null); | 25 | srv.sessions[1] = try Server.createSession(alloc, srv.spawn_plan, "work", 80, 24, null); |