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>
4.4 KiB
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
/// 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
-
List
<home>/audit/. A log file is a name of the formYYYY-MM-DD.jsonl: ten characters, digits with-at positions 4 and 7, then.jsonl. Ignore every other name (.lockis always there). Sort the names. -
ChainVerifier::new(), thenfeed(name, &bytes)for each file in order, thenfinish(). -
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-brokenThat is
{file}:{line}: {what}from the failure. Print nothing else. -
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 nonewhenheadisNone)recoveriesrecovered line: {file}:{line}accepted_breaksaccepted break: {file}:{line}abandonedpending or abandoned: approval {seq}unfinishedrunning or unfinished: decision {seq}clock_warningsclock went backwards: {file}:{line}torn_tailtorn final line: {file}:{line} (brokerd recovers it at its next start)The double names are deliberate.
bxctlreads the files without askingbrokerd, 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 abrokerdin the middle of a write looks like. -
Errors. If
<home>/auditcannot be listed, or a log file cannot be read, return theio::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. Awritetooutthat 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. Runcargo 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 startingbxctl: cannot read the audit log under /nonexistent, then1. Andcargo run -q -p bxctl -- audit; echo $?prints the usage and2. - 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 verifyreports 6 passed; step 5 printed what it says;make gateprintsgate: ok.
Stop and report if
bxctlwould need to depend onbrokerd. It must not: the directory walk here is a second, small copy of the one inbrokerd::audit, on purpose.