From d8479f1d11f2683f032c1d38551e87d61d36d2a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:16:54 +0000 Subject: [PATCH 1/3] Initial plan From 55e49726df18d3704e33d6849c02c96bad29d85c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:47:53 +0000 Subject: [PATCH 2/3] Fix cargo fmt and clippy failures: format http_preflight files and add missing postflight_etl_path field in test_state Co-authored-by: jverdicc <23726212+jverdicc@users.noreply.github.com> --- .../artifacts/postflight-unit.etl.ndjson | 4 ++ .../proptest-regressions/policy_oracle.txt | 7 ++ .../evidenceos-daemon/src/http_preflight.rs | 65 +++++++++++++------ .../src/http_preflight_tests.rs | 52 ++++++++++----- 4 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson create mode 100644 crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt diff --git a/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson b/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson new file mode 100644 index 0000000..07f5ff7 --- /dev/null +++ b/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson @@ -0,0 +1,4 @@ +{"agentId":null,"decision":"ALLOW","outputHash":"2bfd14f43d17fc7cea24e0917a8879b4b2f880b8baeec1b9d90fbaad655e71bd","paramsHash":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","receiptHash":"3a0263b5bfa79871fd477b2aa69f942158f9db1168d8424b5a6ac6955b44fa3e","schema":"evidenceos.v2.postflight","sessionId":"op1","toolName":"tool.a"} +{"agentId":null,"decision":"REDACT","outputHash":"363379742f80b51bdb9206579af7754911543079b9399cb3fc315fb199f476e8","paramsHash":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","receiptHash":"bef3735fe7e2806caeec6847d26020c32d0dd7b9b4ad7ca26690e70e51762702","schema":"evidenceos.v2.postflight","sessionId":"op1","toolName":"tool.a"} +{"agentId":null,"decision":"REDACT","outputHash":"4931605f92478cebee22ccab5535a07e8661d4c2896fe478d3bbf62ceb158172","paramsHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","receiptHash":"d80d7cfb82f004604e777bf76951f7301f2cc88c6a9919970191071855c5ecf3","schema":"evidenceos.v2.postflight","sessionId":"s1","toolName":"exec"} +{"agentId":null,"decision":"REDACT","outputHash":"4931605f92478cebee22ccab5535a07e8661d4c2896fe478d3bbf62ceb158172","paramsHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","receiptHash":"d80d7cfb82f004604e777bf76951f7301f2cc88c6a9919970191071855c5ecf3","schema":"evidenceos.v2.postflight","sessionId":"s1","toolName":"exec"} diff --git a/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt b/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt new file mode 100644 index 0000000..491069d --- /dev/null +++ b/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc b6175de6f47ba312561527a3742c6aee3ae91a2b7bd26c7aa49f3fb528b807b7 # shrinks to input = [] diff --git a/crates/evidenceos-daemon/src/http_preflight.rs b/crates/evidenceos-daemon/src/http_preflight.rs index 786afe6..073692b 100644 --- a/crates/evidenceos-daemon/src/http_preflight.rs +++ b/crates/evidenceos-daemon/src/http_preflight.rs @@ -55,7 +55,6 @@ pub struct PreflightToolCallResponse { pub paramsHash: Option, } - #[derive(Debug, Clone, Deserialize)] #[allow(non_snake_case)] pub struct PostflightToolCallRequest { @@ -172,7 +171,6 @@ async fn preflight_tool_call( } } - async fn postflight_tool_call( State(state): State, headers: HeaderMap, @@ -322,7 +320,10 @@ pub async fn preflight_tool_call_impl( validate_hex_64(client_hash) .map_err(|_| HttpErr::invalid_argument("invalid paramsHash", "invalid_params_hash"))?; if client_hash != params_hash { - return Err(HttpErr::invalid_argument("paramsHash mismatch", "params_hash_mismatch")); + return Err(HttpErr::invalid_argument( + "paramsHash mismatch", + "params_hash_mismatch", + )); } } @@ -411,7 +412,6 @@ pub async fn preflight_tool_call_impl( Ok(response) } - #[derive(Debug)] pub struct PostflightHttpErr { pub(crate) status: StatusCode, @@ -443,38 +443,57 @@ pub async fn postflight_tool_call_impl( ) -> Result { validate_authorization(headers, &state.cfg) .map_err(|_| PostflightHttpErr::invalid("unauthorized"))?; - let _ = validate_request_id(headers).map_err(|_| PostflightHttpErr::invalid("missing_request_id"))?; - let req: PostflightToolCallRequest = serde_json::from_slice(body) - .map_err(|_| PostflightHttpErr::invalid("invalid_json"))?; + let _ = validate_request_id(headers) + .map_err(|_| PostflightHttpErr::invalid("missing_request_id"))?; + let req: PostflightToolCallRequest = + serde_json::from_slice(body).map_err(|_| PostflightHttpErr::invalid("invalid_json"))?; validate_ascii_printable_len(&req.toolName, 1, 128, "toolName") .map_err(|_| PostflightHttpErr::invalid("invalid_tool_name"))?; - validate_hex_64(&req.paramsHash).map_err(|_| PostflightHttpErr::invalid("invalid_params_hash"))?; + validate_hex_64(&req.paramsHash) + .map_err(|_| PostflightHttpErr::invalid("invalid_params_hash"))?; if let Some(h) = req.outputHash.as_deref() { validate_hex_64(h).map_err(|_| PostflightHttpErr::invalid("invalid_output_hash"))?; } if let Some(h) = req.preflightReceiptHash.as_deref() { - validate_hex_64(h).map_err(|_| PostflightHttpErr::invalid("invalid_preflight_receipt_hash"))?; + validate_hex_64(h) + .map_err(|_| PostflightHttpErr::invalid("invalid_preflight_receipt_hash"))?; } if let Some(session) = req.sessionId.as_deref() { validate_ascii_printable_len(session, 0, 128, "sessionId") .map_err(|_| PostflightHttpErr::invalid("invalid_session_id"))?; } if state.high_risk_tools.contains(&req.toolName) - && req.sessionId.as_deref().map(|s| s.is_empty()).unwrap_or(true) + && req + .sessionId + .as_deref() + .map(|s| s.is_empty()) + .unwrap_or(true) { return Err(PostflightHttpErr::invalid("session_required")); } - let output_json = req.output.as_ref().map(|v| serde_json::to_vec(v).unwrap_or_default()); - let output_len = output_json.as_ref().map(|v| v.len() as u64).or(req.outputBytes).unwrap_or(0); + let output_json = req + .output + .as_ref() + .map(|v| serde_json::to_vec(v).unwrap_or_default()); + let output_len = output_json + .as_ref() + .map(|v| v.len() as u64) + .or(req.outputBytes) + .unwrap_or(0); let output_hash = req .outputHash .clone() .or_else(|| output_json.as_ref().map(|v| hex::encode(sha256_bytes(v)))); - let operation = req.sessionId.clone().unwrap_or_else(|| "no-session".to_string()); + let operation = req + .sessionId + .clone() + .unwrap_or_else(|| "no-session".to_string()); let principal = principal_id_from_auth(headers); - let semantic_hash = output_hash.clone().unwrap_or_else(|| req.paramsHash.clone()); + let semantic_hash = output_hash + .clone() + .unwrap_or_else(|| req.paramsHash.clone()); let now_ms = state.clock.now_ms(); let (probe_verdict, snapshot) = { let mut guard = state.probe.lock(); @@ -505,7 +524,10 @@ pub async fn postflight_tool_call_impl( if output_len > max_bytes { decision = "REDACT".to_string(); let preview = serde_json::to_string(raw).unwrap_or_default(); - let preview: String = preview.chars().take(state.cfg.postflight_preview_chars).collect(); + let preview: String = preview + .chars() + .take(state.cfg.postflight_preview_chars) + .collect(); output_rewrite = Some(json!({ "truncated": true, "len": output_len, @@ -519,7 +541,8 @@ pub async fn postflight_tool_call_impl( let budget_delta = 1.0; let budget_remaining = state .hard_freeze_ops - .saturating_sub(snapshot.unique_semantic_hashes_operation) as f64; + .saturating_sub(snapshot.unique_semantic_hashes_operation) + as f64; let mut response = PostflightToolCallResponse { decision, @@ -540,7 +563,8 @@ pub async fn postflight_tool_call_impl( }, "response": response.clone(), }); - let canonical = canonical_json(&receipt_payload).map_err(|_| PostflightHttpErr::invalid("receipt_canonical"))?; + let canonical = canonical_json(&receipt_payload) + .map_err(|_| PostflightHttpErr::invalid("receipt_canonical"))?; response.receiptHash = hex::encode(sha256_bytes(&canonical)); let etl_record = json!({ @@ -564,8 +588,10 @@ fn append_postflight_etl(path: &PathBuf, record: &Value) -> Result<(), std::io:: } let mut file = OpenOptions::new().create(true).append(true).open(path)?; serde_json::to_writer(&mut file, record)?; - file.write_all(b" -")?; + file.write_all( + b" +", + )?; Ok(()) } @@ -910,6 +936,7 @@ mod tests { clock, rate_state: Arc::new(Mutex::new(RateLimitState::default())), high_risk_tools: Arc::new(HashSet::from(["shell.exec".to_string()])), + postflight_etl_path: PathBuf::from("/dev/null"), } } diff --git a/crates/evidenceos-daemon/src/http_preflight_tests.rs b/crates/evidenceos-daemon/src/http_preflight_tests.rs index 2693971..a632167 100644 --- a/crates/evidenceos-daemon/src/http_preflight_tests.rs +++ b/crates/evidenceos-daemon/src/http_preflight_tests.rs @@ -8,7 +8,8 @@ use serde_json::{json, Map, Value}; use crate::config::DaemonConfig; use crate::http_preflight::{ - postflight_tool_call_impl, preflight_tool_call_impl, stable_params_hash, HttpPreflightState, RateLimitState, + postflight_tool_call_impl, preflight_tool_call_impl, stable_params_hash, HttpPreflightState, + RateLimitState, }; use crate::probe::{ProbeClock, ProbeConfig, ProbeDetector}; use crate::telemetry::Telemetry; @@ -231,10 +232,12 @@ async fn invalid_inputs_use_constant_public_error_shape() { assert_eq!(lengths.len(), 1); } - #[tokio::test] async fn postflight_redacts_large_output_deterministically() { - let st = state(DaemonConfig { postflight_default_max_output_bytes: 8, ..DaemonConfig::default() }); + let st = state(DaemonConfig { + postflight_default_max_output_bytes: 8, + ..DaemonConfig::default() + }); let headers = request_headers("req-post-1"); let body = json!({ "toolName":"exec", @@ -242,9 +245,14 @@ async fn postflight_redacts_large_output_deterministically() { "paramsHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "status":"ok", "output": {"x":"abcdefghijklmnopqrstuvwxyz"} - }).to_string(); - let r1 = postflight_tool_call_impl(&st, &headers, body.as_bytes()).await.expect("ok"); - let r2 = postflight_tool_call_impl(&st, &headers, body.as_bytes()).await.expect("ok"); + }) + .to_string(); + let r1 = postflight_tool_call_impl(&st, &headers, body.as_bytes()) + .await + .expect("ok"); + let r2 = postflight_tool_call_impl(&st, &headers, body.as_bytes()) + .await + .expect("ok"); assert_eq!(r1.outputRewrite, r2.outputRewrite); assert_eq!(r1.decision, "REDACT"); } @@ -253,15 +261,22 @@ async fn postflight_redacts_large_output_deterministically() { async fn postflight_budget_decreases_monotonically_per_operation_for_unique_outputs() { let st = state(DaemonConfig::default()); let headers = request_headers("req-post-2"); - let mk = |idx| json!({ - "toolName":"tool.a", - "sessionId":"op1", - "paramsHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "status":"ok", - "output": {"n": idx} - }).to_string(); - let r1 = postflight_tool_call_impl(&st, &headers, mk(1).as_bytes()).await.expect("r1"); - let r2 = postflight_tool_call_impl(&st, &headers, mk(2).as_bytes()).await.expect("r2"); + let mk = |idx| { + json!({ + "toolName":"tool.a", + "sessionId":"op1", + "paramsHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "status":"ok", + "output": {"n": idx} + }) + .to_string() + }; + let r1 = postflight_tool_call_impl(&st, &headers, mk(1).as_bytes()) + .await + .expect("r1"); + let r2 = postflight_tool_call_impl(&st, &headers, mk(2).as_bytes()) + .await + .expect("r2"); assert!(r2.budgetRemainingBits.unwrap_or(0.0) <= r1.budgetRemainingBits.unwrap_or(0.0)); } @@ -274,7 +289,10 @@ async fn postflight_requires_session_id_for_high_risk_tools() { "paramsHash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "status":"ok", "output": {"ok":true} - }).to_string(); - let err = postflight_tool_call_impl(&st, &headers, body.as_bytes()).await.expect_err("must fail"); + }) + .to_string(); + let err = postflight_tool_call_impl(&st, &headers, body.as_bytes()) + .await + .expect_err("must fail"); assert_eq!(err.status, axum::http::StatusCode::BAD_REQUEST); } From 5aab45869cc76a6bd85c561ff13f13fb53700cc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:48:44 +0000 Subject: [PATCH 3/3] Remove test artifacts from tracking and add gitignore entries for crates/*/artifacts/ and proptest-regressions/ Co-authored-by: jverdicc <23726212+jverdicc@users.noreply.github.com> --- .gitignore | 2 ++ .../evidenceos-daemon/artifacts/postflight-unit.etl.ndjson | 4 ---- .../proptest-regressions/policy_oracle.txt | 7 ------- 3 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson delete mode 100644 crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt diff --git a/.gitignore b/.gitignore index a90e648..af5b286 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ artifacts/* !artifacts/forc10/ !artifacts/forc10/** artifacts/forc10/out/ +crates/*/artifacts/ +proptest-regressions/ diff --git a/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson b/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson deleted file mode 100644 index 07f5ff7..0000000 --- a/crates/evidenceos-daemon/artifacts/postflight-unit.etl.ndjson +++ /dev/null @@ -1,4 +0,0 @@ -{"agentId":null,"decision":"ALLOW","outputHash":"2bfd14f43d17fc7cea24e0917a8879b4b2f880b8baeec1b9d90fbaad655e71bd","paramsHash":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","receiptHash":"3a0263b5bfa79871fd477b2aa69f942158f9db1168d8424b5a6ac6955b44fa3e","schema":"evidenceos.v2.postflight","sessionId":"op1","toolName":"tool.a"} -{"agentId":null,"decision":"REDACT","outputHash":"363379742f80b51bdb9206579af7754911543079b9399cb3fc315fb199f476e8","paramsHash":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","receiptHash":"bef3735fe7e2806caeec6847d26020c32d0dd7b9b4ad7ca26690e70e51762702","schema":"evidenceos.v2.postflight","sessionId":"op1","toolName":"tool.a"} -{"agentId":null,"decision":"REDACT","outputHash":"4931605f92478cebee22ccab5535a07e8661d4c2896fe478d3bbf62ceb158172","paramsHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","receiptHash":"d80d7cfb82f004604e777bf76951f7301f2cc88c6a9919970191071855c5ecf3","schema":"evidenceos.v2.postflight","sessionId":"s1","toolName":"exec"} -{"agentId":null,"decision":"REDACT","outputHash":"4931605f92478cebee22ccab5535a07e8661d4c2896fe478d3bbf62ceb158172","paramsHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","receiptHash":"d80d7cfb82f004604e777bf76951f7301f2cc88c6a9919970191071855c5ecf3","schema":"evidenceos.v2.postflight","sessionId":"s1","toolName":"exec"} diff --git a/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt b/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt deleted file mode 100644 index 491069d..0000000 --- a/crates/evidenceos-daemon/proptest-regressions/policy_oracle.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc b6175de6f47ba312561527a3742c6aee3ae91a2b7bd26c7aa49f3fb528b807b7 # shrinks to input = []