From a47093892c37fda778fbc14d1300f63ac26bf60a Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 26 Aug 2026 20:06:41 +0000 Subject: [PATCH] fix(runner): preserve safe result authorization fields Keep the schema-enumerated authorization boundary in canonical semantic result receipts while continuing to redact arbitrary authorization-shaped payload keys. Co-Authored-By: Paperclip --- .../crates/runner-core/src/durable/state.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs index 50c6df265ca..61acefeb229 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs @@ -368,6 +368,7 @@ impl DurableState { let source_seq = self.next_source_seq; let emitted_at = current_timestamp()?; + let sanitized_payload = sanitize_event_payload(&event_type, &payload); let envelope = json!({ "protocol": PROTOCOL, "version": PROTOCOL_VERSION, @@ -392,7 +393,7 @@ impl DurableState { "schemaVersion": 1, "priority": priority.number(), "emittedAt": emitted_at, - "payload": sanitize_value(&payload), + "payload": sanitized_payload, }, }); let byte_size = serde_json::to_vec(&envelope) @@ -1101,6 +1102,34 @@ fn sanitize_value(value: &Value) -> Value { } } +fn sanitize_event_payload(event_type: &str, payload: &Value) -> Value { + let mut sanitized = sanitize_value(payload); + if event_type == "semantic_tool.result" { + let boundary = payload + .pointer("/semantic_tool/authorizationBoundary") + .and_then(Value::as_str) + .filter(|value| { + matches!( + *value, + "company" + | "actor" + | "active_task" + | "grant" + | "governed_action" + | "lock" + | "revision" + ) + }); + if let (Some(boundary), Some(target)) = ( + boundary, + sanitized.pointer_mut("/semantic_tool/authorizationBoundary"), + ) { + *target = Value::String(boundary.to_owned()); + } + } + sanitized +} + pub(crate) fn redact_text(input: &str) -> String { let (bounded, truncated) = if input.len() > 4096 { let boundary = input