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:
@@ -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.
|
||||
Reference in New Issue
Block a user