clawft_kernel/workload_runtime/
evidence.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone)]
11pub struct Capture {
12 buf: Vec<u8>,
13 total: u64,
14 limit: usize,
15}
16
17impl Capture {
18 pub fn new(limit: usize) -> Self {
20 Self {
21 buf: Vec::new(),
22 total: 0,
23 limit,
24 }
25 }
26
27 pub fn push(&mut self, chunk: &[u8]) {
29 self.total += chunk.len() as u64;
30 let room = self.limit.saturating_sub(self.buf.len());
31 self.buf.extend_from_slice(&chunk[..chunk.len().min(room)]);
32 }
33
34 pub fn text(&self) -> String {
36 String::from_utf8_lossy(&self.buf).into_owned()
37 }
38
39 pub fn total(&self) -> u64 {
41 self.total
42 }
43
44 pub fn truncated(&self) -> bool {
46 self.total > self.buf.len() as u64
47 }
48}
49
50#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
52pub struct RunEvidence {
53 pub runtime: String,
55 pub instance_id: String,
57 pub args: Vec<String>,
59 pub exit_code: Option<i32>,
61 pub signal: Option<i32>,
63 pub killed_for_timeout: bool,
65 pub elapsed_ms: u64,
67 pub stdout: String,
69 pub stderr: String,
71 pub stdout_bytes: u64,
73 pub stderr_bytes: u64,
75 pub truncated: bool,
77 #[serde(default, skip_serializing_if = "Vec::is_empty")]
79 pub stopped_for_console: Vec<String>,
80}
81
82impl RunEvidence {
83 pub fn with_output(mut self, out: &Capture, err: &Capture) -> Self {
85 self.stdout = out.text();
86 self.stderr = err.text();
87 self.stdout_bytes = out.total();
88 self.stderr_bytes = err.total();
89 self.truncated = out.truncated() || err.truncated();
90 self
91 }
92
93 pub fn json_lines(&self) -> Vec<serde_json::Value> {
95 self.stdout
96 .lines()
97 .filter_map(|l| serde_json::from_str::<serde_json::Value>(l.trim()).ok())
98 .filter(|v| v.is_object())
99 .collect()
100 }
101
102 pub fn succeeded(&self) -> bool {
104 self.exit_code == Some(0) && !self.killed_for_timeout
105 }
106
107 pub fn audit(&self) -> serde_json::Value {
109 serde_json::json!({
110 "runtime": self.runtime,
111 "instance_id": self.instance_id,
112 "exit_code": self.exit_code,
113 "signal": self.signal,
114 "killed_for_timeout": self.killed_for_timeout,
115 "elapsed_ms": self.elapsed_ms,
116 "stdout_bytes": self.stdout_bytes,
117 "stderr_bytes": self.stderr_bytes,
118 "truncated": self.truncated,
119 "stopped_for_console": self.stopped_for_console,
120 "json_lines": self.json_lines().len(),
121 "stdout_blake3": blake3::hash(self.stdout.as_bytes()).to_hex().to_string(),
122 })
123 }
124}