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