Replace the audit record with chained audit events

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-19 00:22:25 -07:00
parent cb5ecad4da
commit c6395b16f4
6 changed files with 270 additions and 71 deletions
+71 -17
View File
@@ -4,34 +4,88 @@ use serde::{Deserialize, Serialize};
use crate::{CallId, DataClass, DenyReason, Hash32, SessionId, Timestamp};
// JSON: {"outcome":"allowed","grant":"…"} ; the tag sits beside the fields; outcomes are snake_case
// 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 {
grant: String,
},
Approved {
grant: String,
approver: String,
post: Option<String>,
},
Denied {
reason: DenyReason,
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 session: SessionId,
pub call: CallId,
pub tool: String,
pub arguments: String,
pub session_taint: DataClass,
pub decision: DecisionRecord,
pub event: AuditEvent,
}
+1 -1
View File
@@ -9,7 +9,7 @@ pub mod ids;
pub mod log;
pub mod wire;
pub use audit::{AuditRecord, DecisionRecord};
pub use audit::{ApprovalAnswer, AuditEvent, AuditRecord, DecisionRecord, ResultStatus};
pub use class::DataClass;
pub use frame::{FrameError, MAX_FRAME, read_frame, write_frame};
pub use grant::{Constraints, Grant, Mode};