Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,15 @@ jobs:
env:
CI_RUST_SHARD: ${{ matrix.shard }}
run: python3 scripts/ci_rust_test.py --report target/ci-test-reports/${{ matrix.shard }}.json -- cargo test --locked ${{ matrix.packages }}
- name: Run experimental Code Mode and receipt tests
if: matrix.shard == 'server'
run: |
CI_RUST_SHARD=experimental-code-mode \
python3 scripts/ci_rust_test.py --report target/ci-test-reports/experimental-code-mode.json -- \
cargo test --locked -p webcodex --features experimental-code-mode code_mode
CI_RUST_SHARD=experimental-code-mode-receipts \
python3 scripts/ci_rust_test.py --report target/ci-test-reports/experimental-code-mode-receipts.json -- \
cargo test --locked -p webcodex --features experimental-code-mode receipt_tests
- name: Upload bounded Rust test evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
Expand Down
1 change: 1 addition & 0 deletions src/tool_runtime/control_sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ fn rejection_result(result: ToolResult) -> ToolCallOutcome {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/tool_runtime/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ pub(crate) struct ToolCallOutcome {
/// Existing privacy-safe audit projection captured before model compaction.
/// Never serialized into ToolResult or interpreted as execution authority.
pub(crate) canonical_audit_output: Option<Value>,
/// Canonical pre-model-projection state-change truth for request-local
/// internal effect accounting. This is never serialized into ToolResult and
/// carries no authorization, source-fence, or replay authority.
#[cfg_attr(not(feature = "experimental-code-mode"), allow(dead_code))]
pub(crate) canonical_state_changed: Option<bool>,
/// Trusted internal Window/Workflow Session correlation evidence. This is
/// adapter metadata only and is never part of the public ToolResult.
pub(crate) correlation: super::window_activity::ToolCallCorrelation,
Expand All @@ -132,6 +137,7 @@ fn session_selector_failure(error: super::SessionSelectorError) -> ToolCallOutco
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
},
super::SessionSelectorError::RetentionExpired { session_id } => ToolCallOutcome {
Expand All @@ -143,6 +149,7 @@ fn session_selector_failure(error: super::SessionSelectorError) -> ToolCallOutco
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
},
}
Expand Down Expand Up @@ -447,6 +454,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
}
}
Expand Down Expand Up @@ -487,6 +495,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -501,6 +510,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -521,6 +531,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -535,6 +546,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -560,6 +572,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -582,6 +595,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -605,6 +619,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -626,6 +641,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -644,6 +660,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -664,6 +681,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -738,6 +756,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -773,6 +792,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -787,6 +807,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -825,6 +846,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -854,6 +876,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -920,6 +943,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -938,6 +962,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -989,6 +1014,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand All @@ -1011,6 +1037,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -1064,6 +1091,7 @@ impl ToolRuntime {
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
};
}
Expand Down Expand Up @@ -1237,6 +1265,7 @@ impl ToolRuntime {
recorder_ack_requested,
);
}
let canonical_state_changed = result.output.get("state_changed").and_then(Value::as_bool);
// Session/permission evidence is sealed above. The response stage owns
// canonical audit capture, one-shot model projection, and late sidecars.
let postprocess::PostRecordResult {
Expand All @@ -1260,6 +1289,7 @@ impl ToolRuntime {
project,
model_ergonomics: None,
canonical_audit_output,
canonical_state_changed,
correlation,
}
}
Expand Down
77 changes: 54 additions & 23 deletions src/tool_runtime/orchestration_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,12 @@ impl OrchestrationEffectAccumulator {
self.children.remove(&ordinal);
}

fn finish(&mut self, ordinal: usize, result: &super::ToolResult) {
fn finish(
&mut self,
ordinal: usize,
result: &super::ToolResult,
canonical_state_changed: Option<bool>,
) {
let output = &result.output;
let execution_state = output.get("execution_state").and_then(Value::as_str);
let failure_kind = output.get("failure_kind").and_then(Value::as_str);
Expand Down Expand Up @@ -413,9 +418,7 @@ impl OrchestrationEffectAccumulator {
.children
.get(&ordinal)
.is_some_and(|child| runtime_tool_metadata(&child.tool).effect == ToolEffect::Mutate);
let mutation_state_changed = is_mutation
.then(|| output.get("state_changed").and_then(Value::as_bool))
.flatten();
let mutation_state_changed = is_mutation.then_some(canonical_state_changed).flatten();
if let Some(child) = self.children.get_mut(&ordinal) {
child.job_id = None;
child.continuation = None;
Expand Down Expand Up @@ -879,7 +882,7 @@ impl CanonicalOrchestrationHost {
if outcome.error_status.is_some() {
effects.remove(child_ordinal);
} else if let Some(result) = outcome.result.as_ref() {
effects.finish(child_ordinal, result);
effects.finish(child_ordinal, result, outcome.canonical_state_changed);
}
}
// Publish effect truth before releasing the sequential scheduling fence:
Expand Down Expand Up @@ -935,24 +938,52 @@ mod receipt_tests {
use super::*;

#[test]
fn mutation_result_without_authoritative_state_changed_fails_closed() {
let mut effects = OrchestrationEffectAccumulator::default();
effects.begin_if_consequential(1, "edit_project_files");
effects.finish(
1,
&crate::tool_runtime::ToolResult::ok(serde_json::json!({
"execution_state": "completed"
})),
);
fn mutation_result_uses_preprojection_state_changed_truth() {
for state_changed in [false, true] {
let mut effects = OrchestrationEffectAccumulator::default();
effects.begin_if_consequential(1, "edit_project_files");
effects.finish(
1,
&crate::tool_runtime::ToolResult::ok(serde_json::json!({
"changed": state_changed
})),
Some(state_changed),
);

let receipt = effects.receipt();
assert_eq!(receipt.consequential_calls, 1);
assert_eq!(receipt.known_results, 1);
assert_eq!(receipt.outcome_unknown, 0);
assert_eq!(
receipt.children[0].outcome,
ConsequentialChildOutcome::KnownResult
);
assert_eq!(receipt.children[0].success, Some(true));
assert_eq!(receipt.children[0].state_changed, Some(state_changed));
}
}

let receipt = effects.receipt();
assert_eq!(receipt.consequential_calls, 1);
assert_eq!(receipt.known_results, 0);
assert_eq!(receipt.outcome_unknown, 1);
assert_eq!(
receipt.children[0].outcome,
ConsequentialChildOutcome::OutcomeUnknown
);
assert_eq!(receipt.children[0].state_changed, None);
#[test]
fn mutation_result_without_authoritative_state_changed_fails_closed() {
for projected_state_changed in [None, Some(false), Some(true)] {
let mut effects = OrchestrationEffectAccumulator::default();
effects.begin_if_consequential(1, "edit_project_files");
let mut output = serde_json::json!({"execution_state": "completed"});
if let Some(state_changed) = projected_state_changed {
output["state_changed"] = serde_json::json!(state_changed);
}
effects.finish(1, &crate::tool_runtime::ToolResult::ok(output), None);

let receipt = effects.receipt();
assert_eq!(receipt.consequential_calls, 1);
assert_eq!(receipt.known_results, 0);
assert_eq!(receipt.outcome_unknown, 1);
assert_eq!(
receipt.children[0].outcome,
ConsequentialChildOutcome::OutcomeUnknown
);
assert_eq!(receipt.children[0].success, None);
assert_eq!(receipt.children[0].state_changed, None);
}
}
}
4 changes: 4 additions & 0 deletions src/tool_runtime/specialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub(crate) async fn try_dispatch_specialized_gateway(
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
});
}
Expand Down Expand Up @@ -142,6 +143,7 @@ pub(crate) async fn try_dispatch_specialized_gateway(
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation,
}
}
Expand All @@ -152,6 +154,7 @@ pub(crate) async fn try_dispatch_specialized_gateway(
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
},
Err(SpecializedGovernanceDenial::Scope {
Expand All @@ -167,6 +170,7 @@ pub(crate) async fn try_dispatch_specialized_gateway(
project: None,
model_ergonomics: None,
canonical_audit_output: None,
canonical_state_changed: None,
correlation: Default::default(),
},
})
Expand Down
Loading
Loading