43fb76c4
docs: hygiene-kit doctrine — the table is the law, verdicts print, scopes stated
a73x 2026-08-14 09:43
Commit message
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -2623,7 +2623,8 @@ Seven, each stated as the rule it became. | |||
| 2623 | simply vanishes from the run (227 of 251 tests, and no property | 2623 | simply vanishes from the run (227 of 251 tests, and no property |
| 2624 | named) rather than failing loudly. Length-asserts go first and the | 2624 | named) rather than failing loudly. Length-asserts go first and the |
| 2625 | slices get sized at runtime, so a bad mutation fails as a test rather | 2625 | slices get sized at runtime, so a bad mutation fails as a test rather |
| 2626 | than as an absence. | 2626 | than as an absence. This hazard class is named "silent-module-loss" |
| 2627 | elsewhere in this file and in the module roots' refAllDecls comments. | ||
| 2627 | 3. **Flipping a conjunction's operands is not negation.** Shared | 2628 | 3. **Flipping a conjunction's operands is not negation.** Shared |
| 2628 | structure absorbs it — two paths on one filesystem share a `dev`, so | 2629 | structure absorbs it — two paths on one filesystem share a `dev`, so |
| 2629 | swapping the operands of the identity check changed nothing | 2630 | swapping the operands of the identity check changed nothing |
| @@ -3212,3 +3213,59 @@ two dialects. | |||
| 3212 | `sudo: a password is required` (exit 1): this box grants no non-interactive | 3213 | `sudo: a password is required` (exit 1): this box grants no non-interactive |
| 3213 | sudo, so the run half is pending install. valgrind not installable | 3214 | sudo, so the run half is pending install. valgrind not installable |
| 3214 | non-interactively; run `sudo pacman -S valgrind` then the script. | 3215 | non-interactively; run `sudo pacman -S valgrind` then the script. |
| 3216 | - **The layer table is the law.** build.zig's module graph is declared data: | ||
| 3217 | a table row per module (name, root, frozen stratum, production imports, | ||
| 3218 | test-only imports), the wiring loop derives every grant, and a production | ||
| 3219 | import that does not point strictly downward is a comptime error — as is | ||
| 3220 | a table-row rename (comptime idxOf) and a wasm row importing a non-wasm | ||
| 3221 | row (the wasm set is closed under production imports). Layers are | ||
| 3222 | COMPUTED topological strata, frozen 2026-08-14 — hand-assignment was | ||
| 3223 | tried first and misplaced engine on the first draft. Changing the | ||
| 3224 | architecture now means editing the table, which is the point. Proven by | ||
| 3225 | extract-and-diff: 76 edges before and after, empty diff. | ||
| 3226 | - **Two adjudicated edges.** `server -> replica` is test-only (sole use is | ||
| 3227 | the applyFrame test helper) and lives in the test_imports column. | ||
| 3228 | `client -> proxy` is production (ignoreSigpipe in the live attach path) | ||
| 3229 | and is grandfathered with the debt comment at client.zig — relocating | ||
| 3230 | ignoreSigpipe to a leaf is the recorded fix, deliberately not taken here. | ||
| 3231 | - **Test-only imports are a declared column, not an accident.** The testtmp | ||
| 3232 | pattern (production modules importing test scaffolding used only inside | ||
| 3233 | `test` blocks, kept out of release binaries by lazy compilation) is now | ||
| 3234 | stated per-row. The strata computation excludes the column. | ||
| 3235 | - **refAllDecls in every test-loop module root, scope stated honestly.** | ||
| 3236 | Pub decls only — std.meta.declarations sees nothing private. Narrower | ||
| 3237 | still than the original hazard framing: in 0.15.2 a pub decl with an | ||
| 3238 | unresolvable type fails eagerly once the module is touched at all (the | ||
| 3239 | planned probe proof did not reproduce; two isolated repros confirmed), | ||
| 3240 | so the gap this closes is the module that is imported but never | ||
| 3241 | referenced by name — plus forcing analysis where nothing referenced the | ||
| 3242 | decl. quic/quic_server/quic_client use the plain variant (the recursive | ||
| 3243 | walk would analyze the whole wolfSSL/ngtcp2 cImport namespace); engine | ||
| 3244 | was downgraded to plain because recursion reaches pre-existing comptime | ||
| 3245 | errors inside vendored ghostty-vt (re-promote when the dep is bumped); | ||
| 3246 | wasm_core is excluded because it is not in the native test loop and a | ||
| 3247 | block there would never run. | ||
| 3248 | - **Leak verdicts print, never panic.** All three binaries check | ||
| 3249 | `gpa.deinit()` and print `<binary>: LEAK: allocations outlived deinit` | ||
| 3250 | to stderr on `.leak` — never an exit-code change, because muxd's exit | ||
| 3251 | code carries the session shell's. e2e captures every daemon's stderr and | ||
| 3252 | sweeps all captures at the end, behind a canary that fails if the sweep | ||
| 3253 | has nothing to read; agent.sh checks the detached-daemon log after each | ||
| 3254 | clean stop, post process-exit, and fails loudly if the daemon outlives | ||
| 3255 | the stop (a wedged daemon must not read as leak-free). Proven by | ||
| 3256 | deliberate leak: marker appeared, e2e failed at the sweep, agent failed | ||
| 3257 | all three leakcheck sites. kill -9 paths print nothing — no false | ||
| 3258 | positive, no coverage, stated. Known uncovered captures: $OUT.q and the | ||
| 3259 | other mid-suite rm'd files (recorded debt). | ||
| 3260 | - **The persistence soak phase owns the classes 6a cannot see.** One | ||
| 3261 | daemon, N client lifecycles: every cycle must prove its client attached | ||
| 3262 | (a vacuous cycle fails the phase); fd count must return to baseline | ||
| 3263 | exactly, with one settle resample to absorb the unacked-detach race; RSS | ||
| 3264 | gets a 4MB growth bound past a 3-cycle warmup (allocators retain pages; | ||
| 3265 | equality would flake). The zombie /proc pitfall is guarded (`VmRSS` | ||
| 3266 | absent on a dead child reads as empty, not 0). Failure evidence is swept | ||
| 3267 | into the faildir, never rm'd. First real numbers: fds 5->5 exact, RSS | ||
| 3268 | +108 kB over 8 cycles. | ||
| 3269 | - **`zig build check` is the pre-commit gate**: fmt (including | ||
| 3270 | build.zig.zon) + unit tests, seconds. e2e/agent/soak stay separate | ||
| 3271 | steps — minutes-long and process-spawning. | ||