1use anyhow::Result;
9use clap::Subcommand;
10
11#[derive(Subcommand)]
12pub enum AuditCommands {
13 Trail {
15 id: String,
17 },
18 Export {
20 #[arg(long, default_value = "json")]
22 format: String,
23 #[arg(long, default_value = "zernel-audit-export")]
25 output: String,
26 },
27 Lineage {
29 model: String,
31 },
32 Provenance {
34 id: String,
36 },
37 Report {
39 #[arg(long, default_value = "soc2")]
41 standard: String,
42 },
43}
44
45pub async fn run(cmd: AuditCommands) -> Result<()> {
46 match cmd {
47 AuditCommands::Trail { id } => {
48 println!("Audit Trail: {id}");
49 println!("{}", "=".repeat(60));
50
51 let exp_db = crate::experiments::tracker::experiments_db_path();
53 if exp_db.exists() {
54 let conn = rusqlite::Connection::open(&exp_db)?;
55
56 #[allow(clippy::type_complexity)]
57 let result: Result<(String, String, String, Option<String>, Option<String>, Option<f64>), _> = conn.query_row(
58 "SELECT name, status, created_at, git_commit, script, duration_secs FROM experiments WHERE id = ?1",
59 [&id],
60 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?, row.get(5)?)),
61 );
62
63 if let Ok((name, status, created, git, script, duration)) = result {
64 println!(" Type: experiment");
65 println!(" Name: {name}");
66 println!(" Status: {status}");
67 println!(" Created: {created}");
68 println!(" Script: {}", script.as_deref().unwrap_or("N/A"));
69 println!(" Git commit: {}", git.as_deref().unwrap_or("N/A"));
70 if let Some(d) = duration {
71 println!(" Duration: {d:.1}s");
72 }
73
74 let sig_path = crate::experiments::tracker::zernel_dir()
76 .join("experiments")
77 .join(&id)
78 .join("output.log.zernel-sig");
79 if sig_path.exists() {
80 println!(" PQC signed: YES");
81 } else {
82 println!(" PQC signed: no (sign with: zernel pqc sign <path>)");
83 }
84
85 let log_path = crate::experiments::tracker::zernel_dir()
87 .join("experiments")
88 .join(&id)
89 .join("output.log");
90 if log_path.exists() {
91 let size = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);
92 println!(" Log: {} bytes", size);
93 }
94
95 return Ok(());
96 }
97 }
98
99 println!(" ID not found in experiments. Try: zernel exp list");
100 }
101
102 AuditCommands::Export { format, output } => {
103 let output_file = format!("{output}.{format}");
104 println!("Exporting audit log to: {output_file}");
105 println!(" Format: {format}");
106
107 let exp_db = crate::experiments::tracker::experiments_db_path();
108 if !exp_db.exists() {
109 println!(" No experiment data to export.");
110 return Ok(());
111 }
112
113 let conn = rusqlite::Connection::open(&exp_db)?;
114 let mut stmt = conn.prepare(
115 "SELECT id, name, status, created_at, git_commit, script, duration_secs FROM experiments ORDER BY created_at",
116 )?;
117
118 match format.as_str() {
119 "json" => {
120 let mut records = Vec::new();
121 let mut rows = stmt.query([])?;
122 while let Some(row) = rows.next()? {
123 records.push(serde_json::json!({
124 "id": row.get::<_, String>(0)?,
125 "name": row.get::<_, String>(1)?,
126 "status": row.get::<_, String>(2)?,
127 "created_at": row.get::<_, String>(3)?,
128 "git_commit": row.get::<_, Option<String>>(4)?,
129 "script": row.get::<_, Option<String>>(5)?,
130 "duration_secs": row.get::<_, Option<f64>>(6)?,
131 }));
132 }
133
134 let export = serde_json::json!({
135 "zernel_audit_export": {
136 "version": "1.0",
137 "exported_at": chrono::Utc::now().to_rfc3339(),
138 "total_records": records.len(),
139 "experiments": records,
140 }
141 });
142
143 std::fs::write(&output_file, serde_json::to_string_pretty(&export)?)?;
144 println!(" Exported {} records to {output_file}", records.len());
145 }
146 "csv" => {
147 let mut csv =
148 String::from("id,name,status,created_at,git_commit,script,duration_secs\n");
149 let mut rows = stmt.query([])?;
150 let mut count = 0;
151 while let Some(row) = rows.next()? {
152 csv.push_str(&format!(
153 "{},{},{},{},{},{},{}\n",
154 row.get::<_, String>(0)?,
155 row.get::<_, String>(1)?,
156 row.get::<_, String>(2)?,
157 row.get::<_, String>(3)?,
158 row.get::<_, Option<String>>(4)?.unwrap_or_default(),
159 row.get::<_, Option<String>>(5)?.unwrap_or_default(),
160 row.get::<_, Option<f64>>(6)?
161 .map(|d| format!("{d:.1}"))
162 .unwrap_or_default(),
163 ));
164 count += 1;
165 }
166 std::fs::write(&output_file, &csv)?;
167 println!(" Exported {count} records to {output_file}");
168 }
169 other => {
170 println!(" Format '{other}' not yet supported. Use: json, csv");
171 }
172 }
173 }
174
175 AuditCommands::Lineage { model } => {
176 println!("Data Lineage: {model}");
177 println!("{}", "=".repeat(60));
178 println!();
179 println!(" Model → Training Script → Dataset → Raw Data");
180 println!();
181
182 let registry_path = crate::experiments::tracker::zernel_dir()
184 .join("models")
185 .join("registry.json");
186
187 if registry_path.exists() {
188 let data = std::fs::read_to_string(®istry_path)?;
189 let entries: Vec<serde_json::Value> =
190 serde_json::from_str(&data).unwrap_or_default();
191
192 let (name, tag) = model.split_once(':').unwrap_or((&model, "latest"));
193 if let Some(entry) = entries
194 .iter()
195 .find(|e| e["name"].as_str() == Some(name) && e["tag"].as_str() == Some(tag))
196 {
197 println!(" Model: {name}:{tag}");
198 println!(
199 " Source: {}",
200 entry["source_path"].as_str().unwrap_or("N/A")
201 );
202 println!(
203 " Git commit: {}",
204 entry["git_commit"].as_str().unwrap_or("N/A")
205 );
206 println!(
207 " Saved at: {}",
208 entry["saved_at"].as_str().unwrap_or("N/A")
209 );
210 println!(
211 " Size: {} bytes",
212 entry["size_bytes"].as_u64().unwrap_or(0)
213 );
214 } else {
215 println!(" Model not found: {model}");
216 }
217 }
218
219 println!();
220 println!(" Full lineage tracking requires:");
221 println!(" 1. zernel run train.py (records script + git commit)");
222 println!(" 2. zernel model save (links model to experiment)");
223 println!(" 3. zernel pqc sign (cryptographic provenance)");
224 }
225
226 AuditCommands::Provenance { id } => {
227 println!("Model Provenance: {id}");
228 println!("{}", "=".repeat(60));
229 println!();
230 println!(" Provenance chain:");
231 println!(" 1. Training data → (hash recorded at zernel run)");
232 println!(" 2. Training script → (git commit recorded)");
233 println!(" 3. Environment → (zernel env snapshot)");
234 println!(" 4. Model checkpoint → (zernel model save)");
235 println!(" 5. PQC signature → (zernel pqc sign)");
236 println!();
237 println!(" Verify: zernel pqc verify <model-path>");
238 }
239
240 AuditCommands::Report { standard } => {
241 println!("Compliance Report: {}", standard.to_uppercase());
242 println!("{}", "=".repeat(60));
243 println!();
244
245 match standard.as_str() {
246 "soc2" => {
247 println!("SOC 2 Type II Compliance Report");
248 println!();
249 println!(" CC6.1 - Logical Access Controls:");
250 println!(" - PQC keypair authentication for model access");
251 println!(" - GPU locking prevents unauthorized GPU usage");
252 println!();
253 println!(" CC6.6 - Encryption:");
254 println!(" - AES-256-GCM encryption for model weights at rest");
255 println!(" - ML-KEM-768 compatible key exchange (post-quantum)");
256 println!();
257 println!(" CC7.2 - Monitoring:");
258 println!(" - eBPF observability (GPU memory, CUDA, NCCL)");
259 println!(" - Prometheus metrics + WebSocket telemetry");
260 println!();
261 println!(" CC8.1 - Change Management:");
262 println!(" - Git commit tracking for all experiments");
263 println!(" - Immutable experiment audit trail in SQLite");
264 println!(" - PQC signatures for model provenance");
265 }
266 "hipaa" => {
267 println!("HIPAA Compliance Controls");
268 println!();
269 println!(" 164.312(a) - Access Control:");
270 println!(" - PQC encrypted model storage");
271 println!(" - GPU reservation with zernel gpu lock");
272 println!();
273 println!(" 164.312(e) - Transmission Security:");
274 println!(" - NCCL traffic priority with DSCP marking");
275 println!(" - PQC-compatible key exchange for data in transit");
276 println!();
277 println!(" 164.312(b) - Audit Controls:");
278 println!(" - Immutable experiment logs");
279 println!(" - Data lineage tracking");
280 println!(" - Export: zernel audit export --format json");
281 }
282 other => {
283 println!(" Standard '{other}' — generate with: zernel audit report --standard {other}");
284 println!(" Supported: soc2, hipaa, iso27001, gdpr");
285 }
286 }
287 }
288 }
289 Ok(())
290}