milestone 19: hygiene sweep - dead ecs surface, single-source constants, tls classification, frontend state hazards, docker smoke network fix
CI / test (push) Successful in 1m46s
CI / test-aarch64 (push) Successful in 5m30s
CI / frontend (push) Successful in 46s
CI / cross (push) Successful in 8m12s
CI / docker (push) Successful in 3m46s

This commit is contained in:
2026-08-07 20:39:27 +02:00
parent 6f67940995
commit 6c507992e4
59 changed files with 1020 additions and 382 deletions
+101
View File
@@ -431,6 +431,107 @@ Deleted surface: `parseEcs`/`Ecs`/`EcsError`/`ecs_family_*` (edns.zig), the
- [ ] Full suite green: `zig build test -Dintegration`, `npm run test`,
`npm run typecheck`, `npm run lint`.
## Recorded (implementation)
Accepted deviations and findings from the built milestone.
### Ruling 1 (S1)
- The edns.zig test block held six `parseEcs` tests, not nine. A seventh
use lived inside "encodeOpt round-trips through record parse and
parseOpt", which is not a parseEcs test; its last two lines now compare
the found option's bytes directly.
- `extendedRcode` stays, with a nuance the ruling's premise missed: the
BADVERS production path uses the write-side `splitRcode`; extendedRcode
is the read-side verifier whose callers are the two BADVERS wire-format
tests (handler.zig, packet.zig). Deleting it would weaken those tests.
- The write-fault seam for the rollback test has no exported control
surface — the test lives in the same file and needs none.
- The ruling-8 doc comment names `local.zig`'s `publish` function instead
of a line number, so it cannot go stale.
### Rulings 3-7 (S2)
- Ownership amendment: `src/web/metrics.zig` (owned by no session) was
granted to S2 mid-flight. Ruling 5's new counter raises the /metrics
sample count 29 → 30 (`nxdns_log_lines_truncated_total`); the
hard-coded assertion became fully derived
(`1 + dns_stat_fields.len + @typeInfo(LoggerCounters)... +
@typeInfo(logging.Stats)...`), so a new counter in any of those
structs extends the exposition and the assertion together.
- Ruling 6 lands asymmetrically, following the ruling body over the
stale acceptance bullet: doh_client.zig names eleven members (the two
collapsed top-level ones plus the nine `Tls*` members of the unwrapped
read-cause set); fetcher.zig names only the two, because it has no
cause unwrap and no record-layer member can reach it. Both files carry
a comment pointing at the other.
- Addition beyond the ruling: an exact switch does not catch a std
rename — `error.X` in an expression names a member into existence, so
a renamed member leaves the switch compiling and matching nothing.
Comptime guards close this for real: doh_client.zig asserts every
member of `std.crypto.tls.Client.ReadError` maps to `TlsFailed`, and
both files assert the two collapsed names still exist in
`std.http.Client.RequestError`.
- The ruling's "stashed record-layer cause through the unwrap" test is
not constructible: the stub connection is `.plain` on purpose, and a
plain connection's stashed error type has no `Tls*` member. The
record-layer set is instead tested by an `inline for` over the std
error set against `mapError` — all nine members, not one sample.
- Ruling 3 behavioral note: queries.zig's client width dropped 64 → 45
as directed; a `?client=` filter of 46-64 bytes now returns 400
instead of matching nothing. `src/filter/wildcard.zig`'s
`max_pattern_len = 253` is a rule-pattern limit, not a logger role,
and stays.
### Rulings 14-20 (S3)
- Ruling 14: the three `Record<string, unknown>` consumption casts
collapse into one documented cast inside `sectionValues` rather than
zero — iterating the heterogeneous registry erases the per-section
correlation TypeScript needs, and `TlsListenerSettings` (a named
interface, no implicit index signature) blocks the assignment route
with types.ts off-limits. Four casts before, one after.
- Ruling 20: the catch body lives in an exported `swallowMutationError`
helper because jsdom never fires `unhandledrejection`, so a rethrow
from an async submit handler cannot be asserted directly; the helper
is tested directly, mirroring how auth/store.tsx's logout pair is
tested.
- The refresh store notifies synchronously inside `onSuccess`, one flush
before react-query's success dispatch; one pre-existing assertion
moved from `getByText` to `await findByText`. The sub-frame transient
was judged cheaper than batching through notifyManager.
- `lib/queries.ts` imports `features/blocklists/refreshStore.ts` (lib →
features) because the spec directs the mutation factory to write the
store; recorded as sanctioned layering.
- `SourceStatusSection`'s prop is `SourceStatus[] | null` to match the
store; BlocklistsPage tests clear the module-level store in
`beforeEach`.
- Ruling 18: post-m18 there are four other `Promise.all` loaders, not
five.
### Rulings 12, 13 and carry-ins (S4)
- The undecodable-sibling message reports `decompress.err orelse err`,
because `std.Io.Reader` collapses every decode failure into
`error.ReadFailed` and stashes the real one.
- The Dockerfile keeps its `&&` chain instead of the spec sketch's `;`
separators, so any failed step still aborts the layer; the case arms
are untouched.
- The phase-comment sweep leaves `PLAN.md:605`/`:609` alone — they are
the section headings that define Phase 7 and Phase 8, not stale
comments.
- The orchestrator added the host-fallback sentence to
docs/how-to/install-with-docker.md.
### Residual (named, not fixed)
`fetcher.zig` hands the collapsed `ReadFailed`/`WriteFailed` to
`mapError` with no cause unwrap, so a blocklist download canceled at
shutdown returns `error.ReceiveFailed` instead of `error.Canceled` — the
class m18 ruling 4 fixed for DoH. Consequence: a wrong source status and
a spurious warn line at shutdown. Fixing it needs `req`/`resp` plumbing
at four call sites; it deserves its own ruling.
## Anti-requirements
- No deletion of `extendedRcode` (m17 gave it a caller) and no deletion of