Files
boxmaker/docs/plans/M3a/checks-a.md
T
kyleandClaude Opus 5 e3f37da232 Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on
m3a-ref), each area's check record, and a README with the per-task
table of what each check exposed. The handoff note is done with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:45:43 -07:00

4.6 KiB

M3a checks, fork A: the audit chain (tasks 01, 03, 09, 19)

What each task's tests were checked against before hand-over, and what writing that check exposed. The reference lives on branch m3a-ref-a (worktree ~/src/boxmaker-ref-a).

The fixture logs in files/crates/proto/tests/fixtures/audit/ were made by crates/proto/tests/gen_audit_fixtures.rs in the reference tree (cargo test -p proto --test gen_audit_fixtures -- --ignored). It builds a good two-day log with real proto::sha256 hashes and then damages it one way per directory. The generator is not handed over: the implementer gets the directories and tests/chain.rs, which says what each must verify as.

Mutation check: eight deliberate defects were put into the reference one at a time (recovered line must fail to parse; writer goes back to an older file; --accept-break uses the short check; break seq read from the record; last_good unchecked; break prev unchecked; sticky failure removed; an unparseable last line with a newline is a failure). Each made at least one given test fail.

Task Check What the check exposed
01 proto audit types Reference (the types), plus the existing walk-every-object test strict.rs Contract defect, security-relevant. DecisionRecord::Allowed and Ask were unit variants. serde does not apply deny_unknown_fields to a unit variant of an internally tagged enum, so {"outcome":"allowed","zz":true} decoded. strict.rs (tip T2) caught it on the first run. Fixed by making them empty struct variants, Allowed {} and Ask {}; the JSON is unchanged, but every construction and match site writes the braces. strict.rs had to change: a decision or approval line now holds three objects, not two.
03 proto chain verifier Reference, 30 generated fixture logs, mutation check Spec gap 1. The spec gives abandoned and unfinished as "seq of …" but Result.decision and Approval.decision both name the decision's seq, so an approved call can only be tracked under the decision's seq. The reference reports the decision's seq in both lists (it is also the approval id the owner saw). Spec gap 2. The resumed-verifier exception was specified only outside a failed region. If the latest file also has damage before the break record, the resumed verifier is already in a failed region when it meets the break naming an older file, its four checks cannot match, and brokerd would refuse to start after a correctly accepted break while --accept-break says "nothing to accept". The exception must apply inside a region too. Spec gap 3. A chain-valid Recovery record that describes nothing (hash and length of no neighbouring line) was not addressed; the reference makes it a failure. Spec gap 4. ChainReport as specified could not tell the writer what a Recovery or AcceptedBreak must carry, so each consumer would recompute seq and prev; ChainFailure gained last_good, break_prev, break_seq, tail_torn, and torn_tail became a TornTail with recovery_prev and recovery_seq. Test defect found by running: the generator's day-2 approval named a fixed decision seq, wrong once a recovery shifted the numbering.
09 brokerd audit writer Reference, mutation check Spec defect 1. "Each tampering fixture must fail in brokerd's startup" cannot hold as written: an ordinary start reads only the latest file, so damage in an older file is, by design, not seen. The startup tests copy the damaged file alone so that it is the latest. Spec defect 2. "Resumed from the last line of the file before; that line must parse, or it is the failure" deadlocks: accept a break whose failure is that last line, and every later ordinary start fails on it while --accept-break says "nothing to accept". The reference falls back to verifying the whole log when there is no record to resume from. Spec gap. Whether --accept-break with a torn tail and no failure recovers the tail before refusing: the reference writes nothing and returns "nothing to accept". Task defect. AuditError::Broken(ChainFailure) fails clippy's result_large_err; it is boxed. Also: a fallback "if the short check fails, run the full check" was written and then removed, because the region exception (gap 2 above) makes it unreachable and an untested rule is one the implementer will get wrong silently.
19 bxctl audit verify Reference Spec gap: the exact lines were not given, and a torn tail had no line at all, although a live brokerd mid-write produces one. The task fixes the wording. main.rs was not touched here (fork D owns it); the task describes the command's behaviour for the implementer to add in task 18's style.