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>
This commit is contained in:
2026-09-18 23:45:43 -07:00
co-authored by Claude Opus 5
parent 69f0a0a218
commit e3f37da232
180 changed files with 17219 additions and 292 deletions
+93
View File
@@ -0,0 +1,93 @@
# M3a task 19: `bxctl audit verify`
**Branch:** `m3a` (run `git switch m3a`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `Add bxctl audit verify`
## Goal
`bxctl audit verify [--home <path>]` reads `<home>/audit/` itself and verifies the whole hash
chain with `proto::ChainVerifier`. It asks no daemon, so it works when `brokerd` refuses to
start, which is exactly when the owner needs it.
## Files
- Copy: `crates/bxctl/tests/verify.rs`
- Modify: `crates/bxctl/src/verify.rs` (task 18 made it with a placeholder body; replace the body),
`docs/implementer-log.md`
Do not touch `lib.rs` or `main.rs`: task 18 already added `pub mod verify;` and the command.
The tests read the fixture logs of task 03 from `crates/proto/tests/fixtures/audit/`.
## Interfaces
```rust
/// Verifies the whole audit log under `home` and prints the report to `out`.
/// `Ok(true)`: the chain verifies. `Ok(false)`: it does not. `Err`: the log cannot be read.
pub fn run(home: &Path, out: &mut dyn Write) -> std::io::Result<bool>;
```
## What `run` does
1. List `<home>/audit/`. A log file is a name of the form `YYYY-MM-DD.jsonl`: ten characters,
digits with `-` at positions 4 and 7, then `.jsonl`. Ignore every other name (`.lock` is
always there). Sort the names.
2. `ChainVerifier::new()`, then `feed(name, &bytes)` for each file in order, then `finish()`.
3. **If the report has a failure**, print exactly two lines and return `Ok(false)`:
```
2026-09-17.jsonl:4: prev is not the hash of the line before
see docs/runbook.md#audit-chain-broken
```
That is `{file}:{line}: {what}` from the failure. Print nothing else.
4. **Otherwise** print the first line below, then one line for each entry of each list, in this
order, and return `Ok(true)`. A list that is empty prints nothing.
| From | Line |
|---|---|
| always | `audit: ok, {records} records, head {hex}` (`head none` when `head` is `None`) |
| `recoveries` | `recovered line: {file}:{line}` |
| `accepted_breaks` | `accepted break: {file}:{line}` |
| `abandoned` | `pending or abandoned: approval {seq}` |
| `unfinished` | `running or unfinished: decision {seq}` |
| `clock_warnings` | `clock went backwards: {file}:{line}` |
| `torn_tail` | `torn final line: {file}:{line} (brokerd recovers it at its next start)` |
The double names are deliberate. `bxctl` reads the files without asking `brokerd`, so an
approval still waiting and a call still running look the same as ones a crash cut off. A torn
tail is not a failure: it is also what a `brokerd` in the middle of a write looks like.
5. **Errors.** If `<home>/audit` cannot be listed, or a log file cannot be read, return the
`io::Error` (use `?`) and print nothing. A missing directory is an error, not an empty log. An
existing directory with no log files is an empty log: `audit: ok, 0 records, head none`.
A `write` to `out` that fails is returned with `?` as well.
## The command (already in `main.rs` from task 18)
`bxctl audit verify [--home <path>]` calls `run`. `Ok(true)` is exit status 0, `Ok(false)` is 1,
and `Err(e)` prints `bxctl: cannot read the audit log under {home}: {e}` and is 1. Check that
`main.rs` does this (`grep -n 'cannot read the audit log' crates/bxctl/src/main.rs`). If it does
not, stop and report; do not edit `main.rs`.
## Steps
- [ ] **1. Copy.** `cp docs/plans/M3a/files/crates/bxctl/tests/verify.rs crates/bxctl/tests/`
- [ ] **2. See the test fail.** `cargo test -p bxctl --test verify`. Expected: it compiles, and
the tests fail against the placeholder.
- [ ] **3. Replace the placeholder body in `verify.rs`.** Run `cargo fmt --all`.
- [ ] **4. See the tests pass.** `cargo test -p bxctl --test verify`. Expected: `6 passed`.
- [ ] **5. Try it.** `cargo run -q -p bxctl -- audit verify --home /nonexistent; echo $?`
Expected: one line on stderr starting `bxctl: cannot read the audit log under /nonexistent`,
then `1`. And `cargo run -q -p bxctl -- audit; echo $?` prints the usage and `2`.
- [ ] **6. Run the gate.** `make gate`. Expected last line: `gate: ok`.
- [ ] **7. Log and commit.** `git add crates/bxctl docs/implementer-log.md && git commit`
## Done when
- `cargo test -p bxctl --test verify` reports 6 passed; step 5 printed what it says; `make gate`
prints `gate: ok`.
## Stop and report if
- `bxctl` would need to depend on `brokerd`. It must not: the directory walk here is a second,
small copy of the one in `brokerd::audit`, on purpose.