proto becomes bxcore, the one shared crate (M5b task 00)

Done the way the owner asked and the spike mapped: the crate and the
manifests renamed, then what the build reported fixed. Layers: 138
"proto unresolved" errors across 169 files; cargo fmt reordering
imports; six fixture paths built as strings in brokerd's and bxctl's
tests, which the compiler cannot see, and comments naming
crates/proto/; the crate-dependency check and its self-test. One more
the spike did not reach: a `\nproto =` key inside a printf format in
the self-test, which a word-boundary replacement skips because `n` and
`p` are both word characters.

make gate: gate: ok, 944 tests; no new entry in /tmp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 01:48:38 -07:00
co-authored by Claude Opus 5.5
parent 727e027fcf
commit 2b3ae080b4
311 changed files with 426 additions and 410 deletions
+112
View File
@@ -0,0 +1,112 @@
//! Audit log record types. One JSON object per line, hash-chained by the exact bytes of each line.
use serde::{Deserialize, Serialize};
use crate::{CallId, DataClass, DenyReason, Hash32, SessionId, Timestamp};
pub use crate::chain::{ChainFailure, ChainReport, ChainVerifier, Location, TornTail};
// JSON: {"outcome":"denied","reason":"no_grant"} ; the tag sits beside the fields
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case", deny_unknown_fields)]
pub enum DecisionRecord {
Allowed {},
Ask {},
Denied { reason: DenyReason },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ApprovalAnswer {
Approved,
Refused,
Expired,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResultStatus {
Result,
Failed,
}
// JSON: {"type":"decision","session":"…",…} ; the tag sits beside the fields
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum AuditEvent {
Decision {
session: SessionId,
call: CallId,
tool: String,
arguments: String,
outcome: DecisionRecord,
grant: Option<String>,
grant_sha256: Option<Hash32>,
taint: DataClass,
untrusted: bool,
},
Approval {
session: SessionId,
call: CallId,
decision: u64,
answer: ApprovalAnswer,
by: Option<String>,
post: Option<String>,
reason: Option<String>,
outcome: DecisionRecord,
grant: Option<String>,
grant_sha256: Option<Hash32>,
taint: DataClass,
untrusted: bool,
},
Result {
session: SessionId,
call: CallId,
decision: u64,
status: ResultStatus,
class: DataClass,
untrusted: bool,
truncated: bool,
bytes: u64,
sha256: Hash32,
taint_after: DataClass,
},
Recovery {
torn_bytes: u64,
torn_sha256: Hash32,
},
AcceptedBreak {
file: String,
line: u64,
last_good: Hash32,
},
}
// JSON: {"seq":0,"time":"…","prev":"…","event":{"type":"decision",…}} ; `event` is a nested object.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuditRecord {
pub seq: u64,
pub time: Timestamp,
pub prev: Hash32,
pub event: AuditEvent,
}
/// True if `name` is an audit log file: `YYYY-MM-DD.jsonl`, ASCII digits with `-` at positions 4
/// and 7, a month from 01 to 12 and a day from 01 to 31. `brokerd` and `bxctl audit verify` both
/// decide with this, so they read the same files.
pub fn is_audit_log_name(name: &str) -> bool {
let Some(date) = name.strip_suffix(".jsonl") else {
return false;
};
let shaped = date.len() == 10
&& date.bytes().enumerate().all(|(i, b)| match i {
4 | 7 => b == b'-',
_ => b.is_ascii_digit(),
});
if !shaped {
return false;
}
let number = |range: std::ops::Range<usize>| date.get(range).and_then(|s| s.parse::<u8>().ok());
matches!(number(5..7), Some(1..=12)) && matches!(number(8..10), Some(1..=31))
}