git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
299 lines
11 KiB
Rust
299 lines
11 KiB
Rust
//! # Execution Module: Coherence-Gated Action Execution
|
|
//!
|
|
//! This module implements the coherence gate and compute ladder from ADR-014,
|
|
//! providing threshold-based gating for external side effects with mandatory
|
|
//! witness creation.
|
|
//!
|
|
//! ## Architecture Overview
|
|
//!
|
|
//! ```text
|
|
//! ┌─────────────────────────────────────────────────────────────────────────┐
|
|
//! │ ACTION EXECUTOR │
|
|
//! │ Orchestrates the entire execution flow with mandatory witnesses │
|
|
//! └─────────────────────────────────────────────────────────────────────────┘
|
|
//! │
|
|
//! ▼
|
|
//! ┌─────────────────────────────────────────────────────────────────────────┐
|
|
//! │ COHERENCE GATE │
|
|
//! │ Threshold-based gating with persistence detection │
|
|
//! │ Policy bundle reference • Energy history • Witness creation │
|
|
//! └─────────────────────────────────────────────────────────────────────────┘
|
|
//! │
|
|
//! ▼
|
|
//! ┌─────────────────────────────────────────────────────────────────────────┐
|
|
//! │ COMPUTE LADDER │
|
|
//! │ Lane 0 (Reflex) → Lane 1 (Retrieval) → Lane 2 (Heavy) → Lane 3 (Human)│
|
|
//! │ <1ms ~10ms ~100ms async │
|
|
//! └─────────────────────────────────────────────────────────────────────────┘
|
|
//! │
|
|
//! ▼
|
|
//! ┌─────────────────────────────────────────────────────────────────────────┐
|
|
//! │ ACTION TRAIT │
|
|
//! │ Scope • Impact • Metadata • Execute • Content Hash │
|
|
//! └─────────────────────────────────────────────────────────────────────────┘
|
|
//! ```
|
|
//!
|
|
//! ## Key Design Principles
|
|
//!
|
|
//! 1. **Most updates stay in reflex lane** - Low energy (<threshold) = fast path
|
|
//! 2. **Persistence detection** - Sustained incoherence triggers escalation
|
|
//! 3. **Mandatory witness creation** - Every decision is auditable
|
|
//! 4. **Policy bundle reference** - All decisions reference signed governance
|
|
//!
|
|
//! ## Example Usage
|
|
//!
|
|
//! ```ignore
|
|
//! use prime_radiant::execution::{
|
|
//! Action, ActionExecutor, CoherenceGate, EnergySnapshot,
|
|
//! LaneThresholds, PolicyBundleRef,
|
|
//! };
|
|
//!
|
|
//! // Create gate with thresholds
|
|
//! let gate = CoherenceGate::new(
|
|
//! LaneThresholds::default(),
|
|
//! Duration::from_secs(5),
|
|
//! PolicyBundleRef::placeholder(),
|
|
//! );
|
|
//!
|
|
//! // Create executor
|
|
//! let executor = ActionExecutor::with_defaults(gate);
|
|
//!
|
|
//! // Execute action
|
|
//! let energy = EnergySnapshot::new(0.1, 0.05, action.scope().clone());
|
|
//! let result = executor.execute(&action, &energy);
|
|
//!
|
|
//! // Result always includes witness
|
|
//! assert!(result.witness.verify_integrity());
|
|
//! ```
|
|
//!
|
|
//! ## Module Structure
|
|
//!
|
|
//! - [`action`] - Action trait and related types for external side effects
|
|
//! - [`gate`] - Coherence gate with threshold-based gating logic
|
|
//! - [`ladder`] - Compute lane enum and escalation logic
|
|
//! - [`executor`] - Action executor with mandatory witness creation
|
|
|
|
pub mod action;
|
|
pub mod executor;
|
|
pub mod gate;
|
|
pub mod ladder;
|
|
|
|
// Re-export primary types for convenient access
|
|
pub use action::{
|
|
Action, ActionError, ActionId, ActionImpact, ActionMetadata, ActionResult, BoxedAction,
|
|
ExecutionContext, ScopeId,
|
|
};
|
|
|
|
pub use gate::{
|
|
CoherenceGate, EnergyHistory, EnergySnapshot, GateDecision, PolicyBundleRef, WitnessId,
|
|
WitnessRecord,
|
|
};
|
|
|
|
pub use ladder::{ComputeLane, EscalationReason, LaneThresholds, LaneTransition, ThresholdError};
|
|
|
|
pub use executor::{
|
|
ActionExecutor, ActionResultBuilder, ExecutionResult, ExecutionStats, ExecutorConfig,
|
|
ExecutorStats, HumanReviewItem,
|
|
};
|
|
|
|
/// Prelude module for convenient imports.
|
|
pub mod prelude {
|
|
pub use super::{
|
|
Action, ActionError, ActionExecutor, ActionId, ActionImpact, ActionMetadata, ActionResult,
|
|
CoherenceGate, ComputeLane, EnergySnapshot, EscalationReason, ExecutionContext,
|
|
ExecutionResult, ExecutorConfig, GateDecision, LaneThresholds, PolicyBundleRef, ScopeId,
|
|
WitnessId, WitnessRecord,
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::time::Duration;
|
|
|
|
// Integration test action
|
|
struct IntegrationTestAction {
|
|
scope: ScopeId,
|
|
metadata: ActionMetadata,
|
|
}
|
|
|
|
impl IntegrationTestAction {
|
|
fn new(scope: &str) -> Self {
|
|
Self {
|
|
scope: ScopeId::new(scope),
|
|
metadata: ActionMetadata::new("IntegrationTest", "Test action", "test"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Action for IntegrationTestAction {
|
|
type Output = String;
|
|
type Error = ActionError;
|
|
|
|
fn scope(&self) -> &ScopeId {
|
|
&self.scope
|
|
}
|
|
|
|
fn impact(&self) -> ActionImpact {
|
|
ActionImpact::low()
|
|
}
|
|
|
|
fn metadata(&self) -> &ActionMetadata {
|
|
&self.metadata
|
|
}
|
|
|
|
fn execute(&self, ctx: &ExecutionContext) -> Result<String, ActionError> {
|
|
Ok(format!(
|
|
"Executed in {:?} lane, energy: {:.3}",
|
|
ctx.assigned_lane, ctx.current_energy
|
|
))
|
|
}
|
|
|
|
fn content_hash(&self) -> [u8; 32] {
|
|
let hash = blake3::hash(format!("test:{}", self.scope.as_str()).as_bytes());
|
|
let mut result = [0u8; 32];
|
|
result.copy_from_slice(hash.as_bytes());
|
|
result
|
|
}
|
|
|
|
fn make_rollback_not_supported_error() -> ActionError {
|
|
ActionError::RollbackNotSupported
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_integration_low_energy() {
|
|
let gate = CoherenceGate::new(
|
|
LaneThresholds::default(),
|
|
Duration::from_secs(5),
|
|
PolicyBundleRef::placeholder(),
|
|
);
|
|
let executor = ActionExecutor::with_defaults(gate);
|
|
|
|
let action = IntegrationTestAction::new("users.123");
|
|
let energy = EnergySnapshot::new(0.1, 0.05, action.scope.clone());
|
|
|
|
let result = executor.execute(&action, &energy);
|
|
|
|
assert!(result.result.is_ok());
|
|
assert_eq!(result.decision.lane, ComputeLane::Reflex);
|
|
assert!(result.witness.verify_integrity());
|
|
assert!(result.result.unwrap().contains("Reflex"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_integration_escalation() {
|
|
let gate = CoherenceGate::new(
|
|
LaneThresholds::new(0.1, 0.3, 0.6),
|
|
Duration::from_secs(5),
|
|
PolicyBundleRef::placeholder(),
|
|
);
|
|
let executor = ActionExecutor::with_defaults(gate);
|
|
|
|
let action = IntegrationTestAction::new("trades.456");
|
|
let energy = EnergySnapshot::new(0.4, 0.25, action.scope.clone());
|
|
|
|
let result = executor.execute(&action, &energy);
|
|
|
|
assert!(result.result.is_ok());
|
|
assert!(result.decision.lane >= ComputeLane::Retrieval);
|
|
assert!(result.decision.is_escalated());
|
|
}
|
|
|
|
#[test]
|
|
fn test_integration_denial() {
|
|
let gate = CoherenceGate::new(
|
|
LaneThresholds::new(0.1, 0.3, 0.6),
|
|
Duration::from_secs(5),
|
|
PolicyBundleRef::placeholder(),
|
|
);
|
|
let executor = ActionExecutor::with_defaults(gate);
|
|
|
|
let action = IntegrationTestAction::new("critical.789");
|
|
let energy = EnergySnapshot::new(0.9, 0.85, action.scope.clone());
|
|
|
|
let result = executor.execute(&action, &energy);
|
|
|
|
assert!(result.result.is_err());
|
|
assert!(!result.decision.allow);
|
|
assert_eq!(result.decision.lane, ComputeLane::Human);
|
|
}
|
|
|
|
#[test]
|
|
fn test_integration_witness_chain() {
|
|
let gate = CoherenceGate::new(
|
|
LaneThresholds::default(),
|
|
Duration::from_secs(5),
|
|
PolicyBundleRef::placeholder(),
|
|
);
|
|
let executor = ActionExecutor::with_defaults(gate);
|
|
|
|
// Execute multiple actions
|
|
let mut witnesses = Vec::new();
|
|
for i in 0..3 {
|
|
let action = IntegrationTestAction::new(&format!("scope.{}", i));
|
|
let energy = EnergySnapshot::new(0.1, 0.05, action.scope.clone());
|
|
let result = executor.execute(&action, &energy);
|
|
witnesses.push(result.witness);
|
|
}
|
|
|
|
// Verify chain
|
|
assert!(witnesses[0].previous_witness.is_none());
|
|
assert_eq!(witnesses[1].previous_witness, Some(witnesses[0].id.clone()));
|
|
assert_eq!(witnesses[2].previous_witness, Some(witnesses[1].id.clone()));
|
|
|
|
// All witnesses should have valid integrity
|
|
for witness in &witnesses {
|
|
assert!(witness.verify_integrity());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_lane_budget_ordering() {
|
|
// Verify that lane latency budgets increase with lane number
|
|
let lanes = [
|
|
ComputeLane::Reflex,
|
|
ComputeLane::Retrieval,
|
|
ComputeLane::Heavy,
|
|
ComputeLane::Human,
|
|
];
|
|
|
|
for window in lanes.windows(2) {
|
|
assert!(window[0].latency_budget_us() < window[1].latency_budget_us());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_scope_hierarchy() {
|
|
let global = ScopeId::global();
|
|
let parent = ScopeId::new("users");
|
|
let child = ScopeId::path(&["users", "123", "profile"]);
|
|
|
|
assert!(global.is_parent_of(&parent));
|
|
assert!(global.is_parent_of(&child));
|
|
assert!(parent.is_parent_of(&child));
|
|
assert!(!child.is_parent_of(&parent));
|
|
}
|
|
|
|
#[test]
|
|
fn test_impact_risk_scores() {
|
|
let impacts = [
|
|
ActionImpact::minimal(),
|
|
ActionImpact::low(),
|
|
ActionImpact::medium(),
|
|
ActionImpact::high(),
|
|
ActionImpact::critical(),
|
|
];
|
|
|
|
// Risk scores should generally increase
|
|
for window in impacts.windows(2) {
|
|
assert!(
|
|
window[0].risk_score() <= window[1].risk_score(),
|
|
"Risk scores should increase: {:?} vs {:?}",
|
|
window[0].risk_score(),
|
|
window[1].risk_score()
|
|
);
|
|
}
|
|
}
|
|
}
|