From fc8befaf5b3601433c3f62c28e0fa9c4da112c39 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Tue, 22 Sep 2026 21:52:54 -0700 Subject: [PATCH] bxctl audit verify: name the log-like files it did not check A .jsonl file whose name is not a date is ignored by brokerd and by verify; it is now listed, so "ok" does not seem to cover it. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/bxctl/src/verify.rs | 25 +++++++++++++++++- crates/bxctl/tests/verify_skipped.rs | 38 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 crates/bxctl/tests/verify_skipped.rs diff --git a/crates/bxctl/src/verify.rs b/crates/bxctl/src/verify.rs index 1596df2..f652d02 100644 --- a/crates/bxctl/src/verify.rs +++ b/crates/bxctl/src/verify.rs @@ -11,14 +11,22 @@ use proto::ChainVerifier; pub fn run(home: &Path, out: &mut dyn Write) -> std::io::Result { let audit_dir = home.join("audit"); let mut names: Vec = Vec::new(); + let mut skipped: Vec = Vec::new(); for entry in std::fs::read_dir(&audit_dir)? { let entry = entry?; let name = match entry.file_name().into_string() { Ok(name) => name, - Err(_) => continue, + Err(raw) => { + skipped.push(raw.to_string_lossy().into_owned()); + continue; + } }; if proto::is_audit_log_name(&name) { names.push(name); + } else if name.ends_with(".jsonl") { + // Looks like a log file but is not named as one: brokerd ignores it too. Say so, so + // that nobody takes "ok" to cover it. + skipped.push(name); } } names.sort(); @@ -33,6 +41,7 @@ pub fn run(home: &Path, out: &mut dyn Write) -> std::io::Result { if let Some(failure) = &report.failure { writeln!(out, "{}:{}: {}", failure.file, failure.line, failure.what)?; writeln!(out, "see docs/runbook.md#audit-chain-broken")?; + write_skipped(out, &skipped)?; return Ok(false); } @@ -63,5 +72,19 @@ pub fn run(home: &Path, out: &mut dyn Write) -> std::io::Result { torn.at.file, torn.at.line )?; } + write_skipped(out, &skipped)?; Ok(true) } + +/// One line per file in `audit/` that looks like a log but is not named as one; neither `brokerd` +/// nor this check reads it. Names are escaped: a file name can hold anything. +fn write_skipped(out: &mut dyn Write, skipped: &[String]) -> std::io::Result<()> { + for name in skipped { + writeln!( + out, + "not an audit log file, not checked: {}", + crate::escape::escape_json_text(name) + )?; + } + Ok(()) +} diff --git a/crates/bxctl/tests/verify_skipped.rs b/crates/bxctl/tests/verify_skipped.rs new file mode 100644 index 0000000..8706928 --- /dev/null +++ b/crates/bxctl/tests/verify_skipped.rs @@ -0,0 +1,38 @@ +//! `bxctl audit verify` says which files in `audit/` it did not read: a file that looks like a +//! log but is not named as one is ignored by `brokerd` too, and "ok" must not seem to cover it. + +use std::path::PathBuf; + +fn home(tag: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!("bx-verify-skip-{tag}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(dir.join("audit")).unwrap(); + let fixture = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto/tests/fixtures/audit/good"); + for entry in std::fs::read_dir(fixture).unwrap() { + let entry = entry.unwrap(); + std::fs::copy(entry.path(), dir.join("audit").join(entry.file_name())).unwrap(); + } + dir +} + +#[test] +fn a_stray_jsonl_file_is_named_and_the_log_still_verifies() { + let dir = home("stray"); + std::fs::write(dir.join("audit/2026-0x-19.jsonl"), "x\n").unwrap(); + std::fs::write(dir.join("audit/notes.txt"), "not a log\n").unwrap(); + let mut out = Vec::new(); + let ok = bxctl::verify::run(&dir, &mut out).unwrap(); + let text = String::from_utf8(out).unwrap(); + assert!(ok, "{text}"); + assert!(text.starts_with("audit: ok, 10 records"), "{text}"); + assert!( + text.contains("not an audit log file, not checked: 2026-0x-19.jsonl"), + "{text}" + ); + assert!( + !text.contains("notes.txt"), + "only files that look like logs: {text}" + ); + let _ = std::fs::remove_dir_all(&dir); +}