byteor_replay/
audit.rs

1//! Replay audit artifact types.
2
3use serde::{Deserialize, Serialize};
4
5/// Replay audit artifact (v1).
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
7pub struct ReplayAudit {
8    /// Audit schema version.
9    pub audit_v: u32,
10    /// Unix timestamp in milliseconds.
11    pub created_unix_ms: u128,
12    /// Bundle directory path (as provided).
13    pub bundle_dir: String,
14    /// Spec SHA-256 digest of canonical `spec.kv` bytes.
15    pub spec_sha256: String,
16    /// Replay inputs summary.
17    pub input: ReplayAuditInput,
18    /// Policy context and derived decisions.
19    pub policy: ReplayAuditPolicy,
20    /// Side-effecting actions discovered in the spec.
21    pub actions: Vec<ReplayAuditAction>,
22}
23
24/// Replay input summary.
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub struct ReplayAuditInput {
27    /// Journal lane name selected.
28    pub journal_lane: String,
29    /// Journal backing file path used.
30    pub journal_path: String,
31    /// Requested number of records (last N).
32    pub last_n: u64,
33    /// Total records scanned from the journal.
34    pub scanned_records: u64,
35    /// Total bytes across the selected records.
36    pub selected_bytes: u64,
37    /// SHA-256 hashes of the first few selected records (for operator sanity checks).
38    pub sample_sha256: Vec<String>,
39}
40
41/// Policy audit section.
42#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub struct ReplayAuditPolicy {
44    /// Environment (dev/staging/prod).
45    pub env: String,
46    /// Whether approval token was provided.
47    pub approval_provided: bool,
48    /// Replay mode.
49    pub mode: String,
50}
51
52/// Audit record for a discovered side-effect action in the spec.
53#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub struct ReplayAuditAction {
55    /// Action name (e.g. `http_post`, `exec`).
56    pub action: String,
57    /// Role name in the spec, if applicable.
58    pub role: String,
59    /// Input lane name (rx), if applicable.
60    pub input_lane: String,
61    /// Output lane name (tx), if applicable.
62    pub output_lane: String,
63    /// Full stage identity string from the spec.
64    pub stage: String,
65    /// Parsed target fields for greppable audit.
66    pub target: String,
67    /// Policy decision string (stable-ish).
68    pub decision: String,
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn replay_audit_json_roundtrips() {
77        let audit = ReplayAudit {
78            audit_v: 1,
79            created_unix_ms: 42,
80            bundle_dir: "/tmp/bundle".to_string(),
81            spec_sha256: "sha256:abc123".to_string(),
82            input: ReplayAuditInput {
83                journal_lane: "journal.main".to_string(),
84                journal_path: "/tmp/journal.mmap".to_string(),
85                last_n: 10,
86                scanned_records: 12,
87                selected_bytes: 512,
88                sample_sha256: vec!["sha256:def456".to_string()],
89            },
90            policy: ReplayAuditPolicy {
91                env: "prod".to_string(),
92                approval_provided: true,
93                mode: "dry_run".to_string(),
94            },
95            actions: vec![ReplayAuditAction {
96                action: "http_post".to_string(),
97                role: "role_stage_notify".to_string(),
98                input_lane: "rx".to_string(),
99                output_lane: "tx".to_string(),
100                stage: "http_post:https://example.test".to_string(),
101                target: "https://example.test".to_string(),
102                decision: "Allow".to_string(),
103            }],
104        };
105
106        let json = serde_json::to_string_pretty(&audit).unwrap();
107        let back: ReplayAudit = serde_json::from_str(&json).unwrap();
108        assert_eq!(back, audit);
109    }
110}