Skip to main content

icl_core/
executor.rs

1//! Execution engine — runs contracts deterministically in a sandbox
2//!
3//! The executor evaluates preconditions, runs operations in an isolated
4//! environment, verifies postconditions, and logs all state transitions.
5//!
6//! # Architecture
7//!
8//! ICL is a *specification language*, not a scripting language. Operations
9//! define typed state transitions with preconditions and postconditions
10//! expressed as natural-language strings. The executor:
11//!
12//! 1. Maintains typed state matching DataSemantics.state
13//! 2. Validates inputs against operation parameter types
14//! 3. Evaluates simple condition patterns against state
15//! 4. Applies state transitions (parameter values → state fields)
16//! 5. Verifies postconditions and invariants hold
17//! 6. Enforces resource limits (memory, timeout)
18//! 7. Logs every transition in an immutable provenance log
19//!
20//! # Determinism
21//!
22//! The executor is pure — no I/O, no randomness, no system time.
23//! All operations are deterministic: same state + same inputs = same result.
24
25use std::collections::BTreeMap;
26#[cfg(not(target_arch = "wasm32"))]
27use std::time::Instant;
28
29use crate::{Contract, Error, Result};
30
31// ── Core Types ────────────────────────────────────────────
32
33/// A typed runtime value in the execution state
34#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
35#[serde(untagged)]
36pub enum Value {
37    /// Null / uninitialized
38    Null,
39    /// Boolean value
40    Boolean(bool),
41    /// Integer value (i64)
42    Integer(i64),
43    /// Float value (f64 — deterministic operations only)
44    Float(f64),
45    /// String value
46    String(String),
47    /// Array of values
48    Array(Vec<Value>),
49    /// Ordered map (BTreeMap for deterministic iteration)
50    Object(BTreeMap<String, Value>),
51}
52
53impl std::fmt::Display for Value {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Value::Null => write!(f, "null"),
57            Value::Boolean(b) => write!(f, "{}", b),
58            Value::Integer(i) => write!(f, "{}", i),
59            Value::Float(v) => write!(f, "{}", v),
60            Value::String(s) => write!(f, "\"{}\"", s),
61            Value::Array(arr) => {
62                write!(f, "[")?;
63                for (i, v) in arr.iter().enumerate() {
64                    if i > 0 {
65                        write!(f, ", ")?;
66                    }
67                    write!(f, "{}", v)?;
68                }
69                write!(f, "]")
70            }
71            Value::Object(map) => {
72                write!(f, "{{")?;
73                for (i, (k, v)) in map.iter().enumerate() {
74                    if i > 0 {
75                        write!(f, ", ")?;
76                    }
77                    write!(f, "\"{}\": {}", k, v)?;
78                }
79                write!(f, "}}")
80            }
81        }
82    }
83}
84
85impl Value {
86    /// Check if value is "truthy" for condition evaluation
87    pub fn is_truthy(&self) -> bool {
88        match self {
89            Value::Null => false,
90            Value::Boolean(b) => *b,
91            Value::Integer(i) => *i != 0,
92            Value::Float(f) => *f != 0.0,
93            Value::String(s) => !s.is_empty(),
94            Value::Array(a) => !a.is_empty(),
95            Value::Object(o) => !o.is_empty(),
96        }
97    }
98
99    /// Get the type name for error messages
100    pub fn type_name(&self) -> &'static str {
101        match self {
102            Value::Null => "Null",
103            Value::Boolean(_) => "Boolean",
104            Value::Integer(_) => "Integer",
105            Value::Float(_) => "Float",
106            Value::String(_) => "String",
107            Value::Array(_) => "Array",
108            Value::Object(_) => "Object",
109        }
110    }
111
112    /// Convert from serde_json::Value (deterministic — uses BTreeMap)
113    pub fn from_json(json: &serde_json::Value) -> Self {
114        match json {
115            serde_json::Value::Null => Value::Null,
116            serde_json::Value::Bool(b) => Value::Boolean(*b),
117            serde_json::Value::Number(n) => {
118                if let Some(i) = n.as_i64() {
119                    Value::Integer(i)
120                } else if let Some(f) = n.as_f64() {
121                    Value::Float(f)
122                } else {
123                    Value::Null
124                }
125            }
126            serde_json::Value::String(s) => Value::String(s.clone()),
127            serde_json::Value::Array(arr) => {
128                Value::Array(arr.iter().map(Value::from_json).collect())
129            }
130            serde_json::Value::Object(map) => {
131                let btree: BTreeMap<String, Value> = map
132                    .iter()
133                    .map(|(k, v)| (k.clone(), Value::from_json(v)))
134                    .collect();
135                Value::Object(btree)
136            }
137        }
138    }
139
140    /// Convert to serde_json::Value
141    pub fn to_json(&self) -> serde_json::Value {
142        match self {
143            Value::Null => serde_json::Value::Null,
144            Value::Boolean(b) => serde_json::Value::Bool(*b),
145            Value::Integer(i) => serde_json::json!(*i),
146            Value::Float(f) => serde_json::json!(*f),
147            Value::String(s) => serde_json::Value::String(s.clone()),
148            Value::Array(arr) => {
149                serde_json::Value::Array(arr.iter().map(|v| v.to_json()).collect())
150            }
151            Value::Object(map) => {
152                let obj: serde_json::Map<String, serde_json::Value> =
153                    map.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();
154                serde_json::Value::Object(obj)
155            }
156        }
157    }
158}
159
160// ── Execution State ───────────────────────────────────────
161
162/// The mutable state of a contract during execution.
163/// Uses BTreeMap for deterministic field ordering.
164#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
165pub struct ExecutionState {
166    /// Named state fields with typed values
167    pub fields: BTreeMap<String, Value>,
168}
169
170impl ExecutionState {
171    /// Create initial state from contract's DataSemantics
172    pub fn from_contract(contract: &Contract) -> Self {
173        let fields = if let serde_json::Value::Object(map) = &contract.data_semantics.state {
174            let mut btree = BTreeMap::new();
175            for (key, type_info) in map.iter() {
176                // Extract default value if present, otherwise use type-appropriate default
177                let value = Self::default_for_type(type_info);
178                btree.insert(key.clone(), value);
179            }
180            btree
181        } else {
182            BTreeMap::new()
183        };
184        ExecutionState { fields }
185    }
186
187    /// Derive a default value from a type descriptor
188    fn default_for_type(type_info: &serde_json::Value) -> Value {
189        match type_info {
190            serde_json::Value::String(type_name) => match type_name.as_str() {
191                "Integer" => Value::Integer(0),
192                "Float" => Value::Float(0.0),
193                "String" | "ISO8601" | "UUID" => Value::String(String::new()),
194                "Boolean" => Value::Boolean(false),
195                _ => Value::Null,
196            },
197            serde_json::Value::Object(obj) => {
198                if let Some(serde_json::Value::String(t)) = obj.get("type") {
199                    match t.as_str() {
200                        "Integer" | "Float" | "String" | "Boolean" | "ISO8601" | "UUID" => {
201                            // Check for explicit default value
202                            if let Some(default) = obj.get("default") {
203                                Value::from_json(default)
204                            } else {
205                                Self::default_for_type(&serde_json::Value::String(t.clone()))
206                            }
207                        }
208                        _ => Value::Null,
209                    }
210                } else {
211                    // Nested object — recurse
212                    let mut btree = BTreeMap::new();
213                    for (k, v) in obj {
214                        btree.insert(k.clone(), Self::default_for_type(v));
215                    }
216                    Value::Object(btree)
217                }
218            }
219            serde_json::Value::Array(_) => Value::Array(Vec::new()),
220            _ => Value::Null,
221        }
222    }
223
224    /// Get a field value by name
225    pub fn get(&self, field: &str) -> Option<&Value> {
226        self.fields.get(field)
227    }
228
229    /// Set a field value, returning the previous value
230    pub fn set(&mut self, field: String, value: Value) -> Option<Value> {
231        self.fields.insert(field, value)
232    }
233
234    /// Approximate memory usage in bytes
235    pub fn memory_bytes(&self) -> u64 {
236        self.estimate_size() as u64
237    }
238
239    fn estimate_size(&self) -> usize {
240        self.fields
241            .iter()
242            .map(|(k, v)| k.len() + Self::value_size(v))
243            .sum()
244    }
245
246    fn value_size(value: &Value) -> usize {
247        match value {
248            Value::Null => 1,
249            Value::Boolean(_) => 1,
250            Value::Integer(_) => 8,
251            Value::Float(_) => 8,
252            Value::String(s) => s.len() + 24, // heap overhead
253            Value::Array(arr) => 24 + arr.iter().map(Self::value_size).sum::<usize>(),
254            Value::Object(map) => {
255                24 + map
256                    .iter()
257                    .map(|(k, v)| k.len() + Self::value_size(v))
258                    .sum::<usize>()
259            }
260        }
261    }
262}
263
264// ── Expression Evaluator ──────────────────────────────────
265
266/// Evaluates simple condition patterns against execution state.
267///
268/// Supports common invariant/condition patterns from ICL contracts:
269/// - `"<field> is not empty"` — string/array length > 0
270/// - `"<field> >= <number>"` — numeric comparison
271/// - `"<field> <= <number>"` — numeric comparison
272/// - `"<field> > <number>"` — numeric comparison  
273/// - `"<field> < <number>"` — numeric comparison
274/// - `"<field> is boolean"` — type check
275/// - `"<field> is valid ..."` — always true (advisory)
276/// - Opaque strings — always true (not machine-evaluable)
277pub struct ExpressionEvaluator;
278
279impl ExpressionEvaluator {
280    /// Evaluate a condition string against the current state.
281    /// Returns (result, is_evaluable) — false for `is_evaluable` means
282    /// the condition is an opaque string that can't be machine-evaluated.
283    pub fn evaluate(condition: &str, state: &ExecutionState) -> (bool, bool) {
284        let trimmed = condition.trim();
285
286        // Pattern: "<field> is not empty"
287        if let Some(field) = trimmed.strip_suffix(" is not empty") {
288            let field = field.trim();
289            if let Some(value) = state.get(field) {
290                return (value.is_truthy(), true);
291            }
292            // Field doesn't exist — fails
293            return (false, true);
294        }
295
296        // Pattern: "<field> >= <number>"
297        if let Some((field, num)) = Self::parse_comparison(trimmed, " >= ") {
298            return (Self::numeric_cmp(state, field, num, |a, b| a >= b), true);
299        }
300
301        // Pattern: "<field> <= <number>"
302        if let Some((field, num)) = Self::parse_comparison(trimmed, " <= ") {
303            return (Self::numeric_cmp(state, field, num, |a, b| a <= b), true);
304        }
305
306        // Pattern: "<field> > <number>"
307        if let Some((field, num)) = Self::parse_comparison(trimmed, " > ") {
308            // Don't match ">=" which was already handled
309            return (Self::numeric_cmp(state, field, num, |a, b| a > b), true);
310        }
311
312        // Pattern: "<field> < <number>"
313        if let Some((field, num)) = Self::parse_comparison(trimmed, " < ") {
314            return (Self::numeric_cmp(state, field, num, |a, b| a < b), true);
315        }
316
317        // Pattern: "<field> is boolean"
318        if let Some(field) = trimmed.strip_suffix(" is boolean") {
319            let field = field.trim();
320            if let Some(Value::Boolean(_)) = state.get(field) {
321                return (true, true);
322            }
323            return (false, true);
324        }
325
326        // Pattern: "<field> is valid ..." — advisory, always true
327        if trimmed.contains("is valid ") {
328            return (true, false);
329        }
330
331        // Opaque condition — not machine-evaluable, treat as true
332        (true, false)
333    }
334
335    /// Parse a comparison pattern like "field >= 0" into (field_name, number)
336    fn parse_comparison<'a>(s: &'a str, operator: &str) -> Option<(&'a str, f64)> {
337        let parts: Vec<&str> = s.splitn(2, operator).collect();
338        if parts.len() == 2 {
339            let field = parts[0].trim();
340            let num_str = parts[1].trim();
341            if let Ok(num) = num_str.parse::<f64>() {
342                return Some((field, num));
343            }
344        }
345        None
346    }
347
348    /// Do numeric comparison on a state field
349    fn numeric_cmp(
350        state: &ExecutionState,
351        field: &str,
352        rhs: f64,
353        cmp: fn(f64, f64) -> bool,
354    ) -> bool {
355        match state.get(field) {
356            Some(Value::Integer(i)) => cmp(*i as f64, rhs),
357            Some(Value::Float(f)) => cmp(*f, rhs),
358            _ => false,
359        }
360    }
361
362    /// Evaluate all contract invariants against state
363    pub fn check_invariants(
364        invariants: &[String],
365        state: &ExecutionState,
366    ) -> std::result::Result<(), Vec<String>> {
367        let mut violations = Vec::new();
368        for inv in invariants {
369            let (result, evaluable) = Self::evaluate(inv, state);
370            if evaluable && !result {
371                violations.push(inv.clone());
372            }
373        }
374        if violations.is_empty() {
375            Ok(())
376        } else {
377            Err(violations)
378        }
379    }
380}
381
382// ── Sandbox ───────────────────────────────────────────────
383
384/// Isolated execution environment with resource limits
385#[derive(Debug, Clone)]
386pub struct Sandbox {
387    /// Maximum memory in bytes
388    pub max_memory_bytes: u64,
389    /// Computation timeout in milliseconds
390    pub computation_timeout_ms: u64,
391    /// Maximum state size in bytes
392    pub max_state_size_bytes: u64,
393    /// Sandbox isolation mode
394    pub mode: SandboxMode,
395    /// External permissions granted
396    pub permissions: Vec<String>,
397}
398
399/// Sandbox isolation levels from spec §1.6
400#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
401pub enum SandboxMode {
402    /// No external access, full determinism guarantee
403    FullIsolation,
404    /// Limited external access (declared permissions only)
405    Restricted,
406    /// No sandbox — advisory mode only
407    None,
408}
409
410impl Sandbox {
411    /// Create sandbox from contract execution constraints
412    pub fn from_contract(contract: &Contract) -> Self {
413        let mode = match contract.execution_constraints.sandbox_mode.as_str() {
414            "full_isolation" => SandboxMode::FullIsolation,
415            "restricted" => SandboxMode::Restricted,
416            "none" => SandboxMode::None,
417            _ => SandboxMode::FullIsolation, // default to safest
418        };
419
420        Sandbox {
421            max_memory_bytes: contract
422                .execution_constraints
423                .resource_limits
424                .max_memory_bytes,
425            computation_timeout_ms: contract
426                .execution_constraints
427                .resource_limits
428                .computation_timeout_ms,
429            max_state_size_bytes: contract
430                .execution_constraints
431                .resource_limits
432                .max_state_size_bytes,
433            mode,
434            permissions: contract.execution_constraints.external_permissions.clone(),
435        }
436    }
437
438    /// Check if current state is within memory limits
439    pub fn check_memory(&self, state: &ExecutionState) -> Result<()> {
440        let used = state.memory_bytes();
441        if used > self.max_state_size_bytes {
442            return Err(Error::ExecutionError(format!(
443                "State size {} bytes exceeds limit of {} bytes",
444                used, self.max_state_size_bytes
445            )));
446        }
447        if used > self.max_memory_bytes {
448            return Err(Error::ExecutionError(format!(
449                "Memory usage {} bytes exceeds limit of {} bytes",
450                used, self.max_memory_bytes
451            )));
452        }
453        Ok(())
454    }
455
456    /// Check if an operation has required permissions
457    pub fn check_permissions(&self, required: &[String]) -> Result<()> {
458        if self.mode == SandboxMode::FullIsolation && !required.is_empty() {
459            return Err(Error::ExecutionError(
460                "Full isolation sandbox does not permit external access".into(),
461            ));
462        }
463        for perm in required {
464            if !self.permissions.contains(perm) {
465                return Err(Error::ExecutionError(format!(
466                    "Permission '{}' not granted in sandbox",
467                    perm
468                )));
469            }
470        }
471        Ok(())
472    }
473}
474
475// ── Provenance Log ────────────────────────────────────────
476
477/// A single entry in the provenance log — records one state transition
478#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
479pub struct ProvenanceEntry {
480    /// Sequential operation number (0-indexed)
481    pub sequence: u64,
482    /// Name of the operation that caused this transition
483    pub operation: String,
484    /// Input parameters as JSON
485    pub inputs: serde_json::Value,
486    /// State snapshot before the operation
487    pub state_before: BTreeMap<String, Value>,
488    /// State snapshot after the operation
489    pub state_after: BTreeMap<String, Value>,
490    /// Fields that changed
491    pub changes: Vec<StateChange>,
492    /// Whether all postconditions held
493    pub postconditions_verified: bool,
494    /// Whether all invariants held
495    pub invariants_verified: bool,
496}
497
498/// A single field change within a state transition
499#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
500pub struct StateChange {
501    pub field: String,
502    pub old_value: Value,
503    pub new_value: Value,
504}
505
506/// Immutable append-only provenance log
507#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
508pub struct ProvenanceLog {
509    pub entries: Vec<ProvenanceEntry>,
510}
511
512impl ProvenanceLog {
513    pub fn new() -> Self {
514        ProvenanceLog {
515            entries: Vec::new(),
516        }
517    }
518
519    pub fn append(&mut self, entry: ProvenanceEntry) {
520        self.entries.push(entry);
521    }
522
523    pub fn len(&self) -> usize {
524        self.entries.len()
525    }
526
527    pub fn is_empty(&self) -> bool {
528        self.entries.is_empty()
529    }
530}
531
532impl Default for ProvenanceLog {
533    fn default() -> Self {
534        Self::new()
535    }
536}
537
538// ── Execution Result ──────────────────────────────────────
539
540/// Result of executing a single operation
541#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
542pub struct OperationResult {
543    /// Name of the operation executed
544    pub operation: String,
545    /// Whether execution succeeded
546    pub success: bool,
547    /// The new state after execution (if successful)
548    pub state: BTreeMap<String, Value>,
549    /// Error message (if failed)
550    pub error: Option<String>,
551    /// Provenance entry for this operation
552    pub provenance: Option<ProvenanceEntry>,
553}
554
555/// Result of executing a full contract
556#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
557pub struct ExecutionResult {
558    /// Contract stable_id
559    pub contract_id: String,
560    /// Whether overall execution succeeded
561    pub success: bool,
562    /// Individual operation results
563    pub operations: Vec<OperationResult>,
564    /// Final state
565    pub final_state: BTreeMap<String, Value>,
566    /// Complete provenance log
567    pub provenance: ProvenanceLog,
568    /// Error message (if failed)
569    pub error: Option<String>,
570}
571
572// ── Executor ──────────────────────────────────────────────
573
574/// The contract executor — runs operations deterministically in a sandbox
575pub struct Executor {
576    /// The contract being executed
577    contract: Contract,
578    /// Current execution state
579    state: ExecutionState,
580    /// Sandbox environment with resource limits
581    sandbox: Sandbox,
582    /// Provenance log (append-only)
583    provenance: ProvenanceLog,
584    /// Operation counter
585    sequence: u64,
586}
587
588impl Executor {
589    /// Create a new executor for a contract
590    pub fn new(contract: Contract) -> Self {
591        let state = ExecutionState::from_contract(&contract);
592        let sandbox = Sandbox::from_contract(&contract);
593        Executor {
594            contract,
595            state,
596            sandbox,
597            provenance: ProvenanceLog::new(),
598            sequence: 0,
599        }
600    }
601
602    /// Execute a named operation with JSON input parameters
603    pub fn execute_operation(
604        &mut self,
605        operation_name: &str,
606        inputs_json: &str,
607    ) -> Result<OperationResult> {
608        #[cfg(not(target_arch = "wasm32"))]
609        let start = Instant::now();
610
611        // 1. Find the operation definition
612        let op = self
613            .contract
614            .behavioral_semantics
615            .operations
616            .iter()
617            .find(|o| o.name == operation_name)
618            .ok_or_else(|| {
619                Error::ExecutionError(format!(
620                    "Operation '{}' not found in contract",
621                    operation_name
622                ))
623            })?
624            .clone();
625
626        // 2. Parse inputs
627        let inputs: serde_json::Value = serde_json::from_str(inputs_json)
628            .map_err(|e| Error::ExecutionError(format!("Invalid JSON input: {}", e)))?;
629
630        // 3. Validate input parameters against operation definition
631        self.validate_inputs(&op, &inputs)?;
632
633        // 4. Check precondition
634        let (pre_result, pre_evaluable) =
635            ExpressionEvaluator::evaluate(&op.precondition, &self.state);
636        if pre_evaluable && !pre_result {
637            return Err(Error::ExecutionError(format!(
638                "Precondition failed for operation '{}': {}",
639                operation_name, op.precondition
640            )));
641        }
642
643        // 5. Snapshot state before
644        let state_before = self.state.fields.clone();
645
646        // 6. Apply operation — update state with input parameters
647        self.apply_inputs(&inputs)?;
648
649        // 7. Check timeout (not available on wasm32)
650        #[cfg(not(target_arch = "wasm32"))]
651        {
652            let elapsed_ms = start.elapsed().as_millis() as u64;
653            if elapsed_ms > self.sandbox.computation_timeout_ms {
654                // Rollback state
655                self.state.fields = state_before.clone();
656                return Err(Error::ExecutionError(format!(
657                    "Operation '{}' exceeded timeout of {}ms (took {}ms)",
658                    operation_name, self.sandbox.computation_timeout_ms, elapsed_ms
659                )));
660            }
661        }
662
663        // 8. Check postcondition
664        let (post_result, post_evaluable) =
665            ExpressionEvaluator::evaluate(&op.postcondition, &self.state);
666        let postconditions_verified = !post_evaluable || post_result;
667
668        if post_evaluable && !post_result {
669            // Rollback state
670            self.state.fields = state_before;
671            return Err(Error::ContractViolation {
672                commitment: format!("postcondition of '{}'", operation_name),
673                violation: op.postcondition.clone(),
674            });
675        }
676
677        // 9. Check all invariants
678        let invariants_verified = match ExpressionEvaluator::check_invariants(
679            &self.contract.data_semantics.invariants,
680            &self.state,
681        ) {
682            Ok(()) => true,
683            Err(violations) => {
684                // Rollback state
685                self.state.fields = state_before;
686                return Err(Error::ContractViolation {
687                    commitment: "invariant".into(),
688                    violation: format!("Violated invariants: {}", violations.join(", ")),
689                });
690            }
691        };
692
693        // 10. Check resource limits
694        self.sandbox.check_memory(&self.state).inspect_err(|_| {
695            self.state.fields = state_before.clone();
696        })?;
697
698        // 11. Compute changes
699        let changes = Self::compute_changes(&state_before, &self.state.fields);
700
701        // 12. Record provenance
702        let entry = ProvenanceEntry {
703            sequence: self.sequence,
704            operation: operation_name.to_string(),
705            inputs: inputs.clone(),
706            state_before,
707            state_after: self.state.fields.clone(),
708            changes,
709            postconditions_verified,
710            invariants_verified,
711        };
712        self.provenance.append(entry.clone());
713        self.sequence += 1;
714
715        Ok(OperationResult {
716            operation: operation_name.to_string(),
717            success: true,
718            state: self.state.fields.clone(),
719            error: None,
720            provenance: Some(entry),
721        })
722    }
723
724    /// Validate that inputs match operation parameter types
725    fn validate_inputs(&self, op: &crate::Operation, inputs: &serde_json::Value) -> Result<()> {
726        if let serde_json::Value::Object(params_def) = &op.parameters {
727            if let serde_json::Value::Object(input_map) = inputs {
728                // Check all required parameters are provided
729                for (param_name, _param_type) in params_def {
730                    if !input_map.contains_key(param_name) {
731                        return Err(Error::ExecutionError(format!(
732                            "Missing required parameter '{}' for operation '{}'",
733                            param_name, op.name
734                        )));
735                    }
736                }
737            }
738        }
739        Ok(())
740    }
741
742    /// Apply input values to the execution state
743    fn apply_inputs(&mut self, inputs: &serde_json::Value) -> Result<()> {
744        if let serde_json::Value::Object(input_map) = inputs {
745            for (key, value) in input_map {
746                let typed_value = Value::from_json(value);
747                self.state.set(key.clone(), typed_value);
748            }
749        }
750        Ok(())
751    }
752
753    /// Compute the list of field changes between two state snapshots
754    fn compute_changes(
755        before: &BTreeMap<String, Value>,
756        after: &BTreeMap<String, Value>,
757    ) -> Vec<StateChange> {
758        let mut changes = Vec::new();
759
760        // Check fields in before
761        for (key, old_val) in before {
762            match after.get(key) {
763                Some(new_val) if new_val != old_val => {
764                    changes.push(StateChange {
765                        field: key.clone(),
766                        old_value: old_val.clone(),
767                        new_value: new_val.clone(),
768                    });
769                }
770                None => {
771                    changes.push(StateChange {
772                        field: key.clone(),
773                        old_value: old_val.clone(),
774                        new_value: Value::Null,
775                    });
776                }
777                _ => {}
778            }
779        }
780
781        // Check new fields
782        for (key, new_val) in after {
783            if !before.contains_key(key) {
784                changes.push(StateChange {
785                    field: key.clone(),
786                    old_value: Value::Null,
787                    new_value: new_val.clone(),
788                });
789            }
790        }
791
792        changes
793    }
794
795    /// Execute a contract fully: run all operations from a JSON array of requests
796    /// Each request: { "operation": "name", "inputs": { ... } }
797    pub fn execute_all(&mut self, requests_json: &str) -> Result<ExecutionResult> {
798        let requests: Vec<serde_json::Value> = serde_json::from_str(requests_json)
799            .map_err(|e| Error::ExecutionError(format!("Invalid JSON requests: {}", e)))?;
800
801        let mut operation_results = Vec::new();
802
803        for req in &requests {
804            let op_name = req
805                .get("operation")
806                .and_then(|v| v.as_str())
807                .ok_or_else(|| {
808                    Error::ExecutionError("Each request must have an 'operation' field".into())
809                })?;
810
811            let empty_obj = serde_json::Value::Object(serde_json::Map::new());
812            let inputs = req.get("inputs").unwrap_or(&empty_obj);
813
814            let inputs_str = serde_json::to_string(inputs)
815                .map_err(|e| Error::ExecutionError(format!("Failed to serialize inputs: {}", e)))?;
816
817            match self.execute_operation(op_name, &inputs_str) {
818                Ok(result) => operation_results.push(result),
819                Err(e) => {
820                    operation_results.push(OperationResult {
821                        operation: op_name.to_string(),
822                        success: false,
823                        state: self.state.fields.clone(),
824                        error: Some(e.to_string()),
825                        provenance: None,
826                    });
827                    return Ok(ExecutionResult {
828                        contract_id: self.contract.identity.stable_id.clone(),
829                        success: false,
830                        operations: operation_results,
831                        final_state: self.state.fields.clone(),
832                        provenance: self.provenance.clone(),
833                        error: Some(e.to_string()),
834                    });
835                }
836            }
837        }
838
839        Ok(ExecutionResult {
840            contract_id: self.contract.identity.stable_id.clone(),
841            success: true,
842            operations: operation_results,
843            final_state: self.state.fields.clone(),
844            provenance: self.provenance.clone(),
845            error: None,
846        })
847    }
848
849    /// Get current state (immutable ref)
850    pub fn state(&self) -> &ExecutionState {
851        &self.state
852    }
853
854    /// Get provenance log (immutable ref)
855    pub fn provenance(&self) -> &ProvenanceLog {
856        &self.provenance
857    }
858}
859
860/// Execute a contract with given inputs (convenience function — public API)
861///
862/// # Arguments
863/// - `contract` — parsed & verified contract
864/// - `inputs` — JSON string: array of `{ "operation": "name", "inputs": { ... } }`
865///   OR single `{ "operation": "name", "inputs": { ... } }`
866///
867/// # Returns
868/// JSON string with execution result including provenance log
869///
870/// # Guarantees
871/// - Deterministic: same inputs → same outputs
872/// - Bounded: resource limits enforced (memory, time)
873/// - Verifiable: preconditions checked, postconditions verified
874/// - Logged: all state changes recorded in provenance
875pub fn execute_contract(contract: &Contract, inputs: &str) -> Result<String> {
876    let mut executor = Executor::new(contract.clone());
877
878    // Detect if inputs is a single request or array
879    let inputs_trimmed = inputs.trim();
880    let requests_json = if inputs_trimmed.starts_with('[') {
881        inputs_trimmed.to_string()
882    } else if inputs_trimmed.starts_with('{') {
883        format!("[{}]", inputs_trimmed)
884    } else {
885        return Err(Error::ExecutionError(
886            "Input must be a JSON object or array of objects".into(),
887        ));
888    };
889
890    let result = executor.execute_all(&requests_json)?;
891
892    serde_json::to_string_pretty(&result)
893        .map_err(|e| Error::ExecutionError(format!("Failed to serialize result: {}", e)))
894}
895
896// ── Tests ─────────────────────────────────────────────────
897
898#[cfg(test)]
899mod tests {
900    use super::*;
901    use crate::*;
902
903    /// Helper: create a minimal contract for testing
904    fn test_contract() -> Contract {
905        Contract {
906            identity: Identity {
907                stable_id: "ic-test-001".into(),
908                version: 1,
909                created_timestamp: "2026-02-01T10:00:00Z".into(),
910                owner: "test".into(),
911                semantic_hash: "abc123".into(),
912            },
913            purpose_statement: PurposeStatement {
914                narrative: "Test contract".into(),
915                intent_source: "test".into(),
916                confidence_level: 1.0,
917            },
918            data_semantics: DataSemantics {
919                state: serde_json::json!({
920                    "message": "String",
921                    "count": "Integer"
922                }),
923                invariants: vec!["message is not empty".into(), "count >= 0".into()],
924            },
925            behavioral_semantics: BehavioralSemantics {
926                operations: vec![Operation {
927                    name: "echo".into(),
928                    precondition: "input_provided".into(),
929                    parameters: serde_json::json!({
930                        "message": "String"
931                    }),
932                    postcondition: "state_updated".into(),
933                    side_effects: vec!["log_operation".into()],
934                    idempotence: "idempotent".into(),
935                }],
936            },
937            execution_constraints: ExecutionConstraints {
938                trigger_types: vec!["manual".into()],
939                resource_limits: ResourceLimits {
940                    max_memory_bytes: 1_048_576,
941                    computation_timeout_ms: 1000,
942                    max_state_size_bytes: 1_048_576,
943                },
944                external_permissions: vec![],
945                sandbox_mode: "full_isolation".into(),
946            },
947            human_machine_contract: HumanMachineContract {
948                system_commitments: vec!["All messages echoed".into()],
949                system_refusals: vec!["Will not lose data".into()],
950                user_obligations: vec!["Provide messages".into()],
951            },
952        }
953    }
954
955    // ── Value Tests ───────────────────────────────────────
956
957    #[test]
958    #[allow(clippy::approx_constant)]
959    fn test_value_from_json_primitives() {
960        assert_eq!(Value::from_json(&serde_json::json!(null)), Value::Null);
961        assert_eq!(
962            Value::from_json(&serde_json::json!(true)),
963            Value::Boolean(true)
964        );
965        assert_eq!(Value::from_json(&serde_json::json!(42)), Value::Integer(42));
966        assert_eq!(
967            Value::from_json(&serde_json::json!(3.14)),
968            Value::Float(3.14)
969        );
970        assert_eq!(
971            Value::from_json(&serde_json::json!("hello")),
972            Value::String("hello".into())
973        );
974    }
975
976    #[test]
977    fn test_value_from_json_collections() {
978        let arr = Value::from_json(&serde_json::json!([1, 2, 3]));
979        assert_eq!(
980            arr,
981            Value::Array(vec![
982                Value::Integer(1),
983                Value::Integer(2),
984                Value::Integer(3),
985            ])
986        );
987
988        let obj = Value::from_json(&serde_json::json!({"a": 1, "b": "two"}));
989        let mut expected = BTreeMap::new();
990        expected.insert("a".into(), Value::Integer(1));
991        expected.insert("b".into(), Value::String("two".into()));
992        assert_eq!(obj, Value::Object(expected));
993    }
994
995    #[test]
996    fn test_value_roundtrip_json() {
997        let original = serde_json::json!({
998            "name": "test",
999            "count": 42,
1000            "active": true,
1001            "items": [1, 2, 3]
1002        });
1003        let value = Value::from_json(&original);
1004        let back = value.to_json();
1005        assert_eq!(original, back);
1006    }
1007
1008    #[test]
1009    fn test_value_is_truthy() {
1010        assert!(!Value::Null.is_truthy());
1011        assert!(!Value::Boolean(false).is_truthy());
1012        assert!(Value::Boolean(true).is_truthy());
1013        assert!(!Value::Integer(0).is_truthy());
1014        assert!(Value::Integer(1).is_truthy());
1015        assert!(!Value::String(String::new()).is_truthy());
1016        assert!(Value::String("hello".into()).is_truthy());
1017        assert!(!Value::Array(vec![]).is_truthy());
1018        assert!(Value::Array(vec![Value::Integer(1)]).is_truthy());
1019    }
1020
1021    #[test]
1022    fn test_value_display() {
1023        assert_eq!(format!("{}", Value::Null), "null");
1024        assert_eq!(format!("{}", Value::Boolean(true)), "true");
1025        assert_eq!(format!("{}", Value::Integer(42)), "42");
1026        assert_eq!(format!("{}", Value::String("hi".into())), "\"hi\"");
1027    }
1028
1029    // ── ExecutionState Tests ──────────────────────────────
1030
1031    #[test]
1032    fn test_execution_state_from_contract() {
1033        let contract = test_contract();
1034        let state = ExecutionState::from_contract(&contract);
1035
1036        assert_eq!(state.get("message"), Some(&Value::String(String::new())));
1037        assert_eq!(state.get("count"), Some(&Value::Integer(0)));
1038    }
1039
1040    #[test]
1041    fn test_execution_state_set_get() {
1042        let mut state = ExecutionState {
1043            fields: BTreeMap::new(),
1044        };
1045        state.set("x".into(), Value::Integer(10));
1046        assert_eq!(state.get("x"), Some(&Value::Integer(10)));
1047
1048        let old = state.set("x".into(), Value::Integer(20));
1049        assert_eq!(old, Some(Value::Integer(10)));
1050        assert_eq!(state.get("x"), Some(&Value::Integer(20)));
1051    }
1052
1053    #[test]
1054    fn test_execution_state_memory_bytes() {
1055        let mut state = ExecutionState {
1056            fields: BTreeMap::new(),
1057        };
1058        let empty_size = state.memory_bytes();
1059        state.set("big_string".into(), Value::String("x".repeat(1000)));
1060        assert!(state.memory_bytes() > empty_size + 1000);
1061    }
1062
1063    // ── ExpressionEvaluator Tests ─────────────────────────
1064
1065    #[test]
1066    fn test_eval_is_not_empty_true() {
1067        let mut state = ExecutionState {
1068            fields: BTreeMap::new(),
1069        };
1070        state.set("message".into(), Value::String("hello".into()));
1071        let (result, evaluable) = ExpressionEvaluator::evaluate("message is not empty", &state);
1072        assert!(evaluable);
1073        assert!(result);
1074    }
1075
1076    #[test]
1077    fn test_eval_is_not_empty_false() {
1078        let mut state = ExecutionState {
1079            fields: BTreeMap::new(),
1080        };
1081        state.set("message".into(), Value::String(String::new()));
1082        let (result, evaluable) = ExpressionEvaluator::evaluate("message is not empty", &state);
1083        assert!(evaluable);
1084        assert!(!result);
1085    }
1086
1087    #[test]
1088    fn test_eval_numeric_comparisons() {
1089        let mut state = ExecutionState {
1090            fields: BTreeMap::new(),
1091        };
1092        state.set("count".into(), Value::Integer(5));
1093
1094        assert!(ExpressionEvaluator::evaluate("count >= 0", &state).0);
1095        assert!(ExpressionEvaluator::evaluate("count >= 5", &state).0);
1096        assert!(!ExpressionEvaluator::evaluate("count >= 6", &state).0);
1097        assert!(ExpressionEvaluator::evaluate("count > 4", &state).0);
1098        assert!(!ExpressionEvaluator::evaluate("count > 5", &state).0);
1099        assert!(ExpressionEvaluator::evaluate("count <= 5", &state).0);
1100        assert!(ExpressionEvaluator::evaluate("count < 6", &state).0);
1101        assert!(!ExpressionEvaluator::evaluate("count < 5", &state).0);
1102    }
1103
1104    #[test]
1105    fn test_eval_is_boolean() {
1106        let mut state = ExecutionState {
1107            fields: BTreeMap::new(),
1108        };
1109        state.set("flag".into(), Value::Boolean(true));
1110        state.set("count".into(), Value::Integer(5));
1111
1112        assert!(ExpressionEvaluator::evaluate("flag is boolean", &state).0);
1113        assert!(!ExpressionEvaluator::evaluate("count is boolean", &state).0);
1114    }
1115
1116    #[test]
1117    fn test_eval_opaque_condition() {
1118        let state = ExecutionState {
1119            fields: BTreeMap::new(),
1120        };
1121        let (result, evaluable) = ExpressionEvaluator::evaluate("some_opaque_condition", &state);
1122        assert!(!evaluable);
1123        assert!(result); // opaque = pass
1124    }
1125
1126    #[test]
1127    fn test_check_invariants_all_pass() {
1128        let mut state = ExecutionState {
1129            fields: BTreeMap::new(),
1130        };
1131        state.set("message".into(), Value::String("hello".into()));
1132        state.set("count".into(), Value::Integer(5));
1133
1134        let invariants = vec!["message is not empty".into(), "count >= 0".into()];
1135        assert!(ExpressionEvaluator::check_invariants(&invariants, &state).is_ok());
1136    }
1137
1138    #[test]
1139    fn test_check_invariants_one_fails() {
1140        let mut state = ExecutionState {
1141            fields: BTreeMap::new(),
1142        };
1143        state.set("message".into(), Value::String(String::new()));
1144        state.set("count".into(), Value::Integer(5));
1145
1146        let invariants = vec!["message is not empty".into(), "count >= 0".into()];
1147        let result = ExpressionEvaluator::check_invariants(&invariants, &state);
1148        assert!(result.is_err());
1149        let violations = result.unwrap_err();
1150        assert_eq!(violations, vec!["message is not empty"]);
1151    }
1152
1153    // ── Sandbox Tests ─────────────────────────────────────
1154
1155    #[test]
1156    fn test_sandbox_from_contract() {
1157        let contract = test_contract();
1158        let sandbox = Sandbox::from_contract(&contract);
1159        assert_eq!(sandbox.mode, SandboxMode::FullIsolation);
1160        assert_eq!(sandbox.max_memory_bytes, 1_048_576);
1161        assert_eq!(sandbox.computation_timeout_ms, 1000);
1162    }
1163
1164    #[test]
1165    fn test_sandbox_check_memory_within_limits() {
1166        let contract = test_contract();
1167        let sandbox = Sandbox::from_contract(&contract);
1168        let state = ExecutionState::from_contract(&contract);
1169        assert!(sandbox.check_memory(&state).is_ok());
1170    }
1171
1172    #[test]
1173    fn test_sandbox_check_memory_exceeds_limit() {
1174        let contract = test_contract();
1175        let sandbox = Sandbox {
1176            max_memory_bytes: 10,
1177            max_state_size_bytes: 10,
1178            ..Sandbox::from_contract(&contract)
1179        };
1180        let mut state = ExecutionState::from_contract(&contract);
1181        state.set("big".into(), Value::String("x".repeat(100)));
1182        assert!(sandbox.check_memory(&state).is_err());
1183    }
1184
1185    #[test]
1186    fn test_sandbox_permissions_full_isolation() {
1187        let sandbox = Sandbox {
1188            max_memory_bytes: 1_000_000,
1189            computation_timeout_ms: 1000,
1190            max_state_size_bytes: 1_000_000,
1191            mode: SandboxMode::FullIsolation,
1192            permissions: vec![],
1193        };
1194        assert!(sandbox.check_permissions(&[]).is_ok());
1195        assert!(sandbox.check_permissions(&["network".to_string()]).is_err());
1196    }
1197
1198    #[test]
1199    fn test_sandbox_permissions_restricted() {
1200        let sandbox = Sandbox {
1201            max_memory_bytes: 1_000_000,
1202            computation_timeout_ms: 1000,
1203            max_state_size_bytes: 1_000_000,
1204            mode: SandboxMode::Restricted,
1205            permissions: vec!["database_query".into()],
1206        };
1207        assert!(sandbox
1208            .check_permissions(&["database_query".to_string()])
1209            .is_ok());
1210        assert!(sandbox.check_permissions(&["network".to_string()]).is_err());
1211    }
1212
1213    // ── ProvenanceLog Tests ───────────────────────────────
1214
1215    #[test]
1216    fn test_provenance_log_new_empty() {
1217        let log = ProvenanceLog::new();
1218        assert!(log.is_empty());
1219        assert_eq!(log.len(), 0);
1220    }
1221
1222    #[test]
1223    fn test_provenance_log_append() {
1224        let mut log = ProvenanceLog::new();
1225        let entry = ProvenanceEntry {
1226            sequence: 0,
1227            operation: "test".into(),
1228            inputs: serde_json::json!({}),
1229            state_before: BTreeMap::new(),
1230            state_after: BTreeMap::new(),
1231            changes: vec![],
1232            postconditions_verified: true,
1233            invariants_verified: true,
1234        };
1235        log.append(entry);
1236        assert_eq!(log.len(), 1);
1237        assert!(!log.is_empty());
1238    }
1239
1240    // ── Executor Tests ────────────────────────────────────
1241
1242    #[test]
1243    fn test_executor_new() {
1244        let contract = test_contract();
1245        let executor = Executor::new(contract);
1246        assert_eq!(
1247            executor.state().get("message"),
1248            Some(&Value::String(String::new()))
1249        );
1250        assert_eq!(executor.state().get("count"), Some(&Value::Integer(0)));
1251        assert!(executor.provenance().is_empty());
1252    }
1253
1254    #[test]
1255    fn test_execute_operation_success() {
1256        let contract = test_contract();
1257        let mut executor = Executor::new(contract);
1258
1259        let result = executor
1260            .execute_operation("echo", r#"{"message": "hello"}"#)
1261            .unwrap();
1262
1263        assert!(result.success);
1264        assert_eq!(result.operation, "echo");
1265        assert_eq!(
1266            executor.state().get("message"),
1267            Some(&Value::String("hello".into()))
1268        );
1269        assert!(result.provenance.is_some());
1270    }
1271
1272    #[test]
1273    fn test_execute_operation_not_found() {
1274        let contract = test_contract();
1275        let mut executor = Executor::new(contract);
1276
1277        let result = executor.execute_operation("nonexistent", "{}");
1278        assert!(result.is_err());
1279        let err = result.unwrap_err().to_string();
1280        assert!(err.contains("not found"));
1281    }
1282
1283    #[test]
1284    fn test_execute_operation_invalid_json() {
1285        let contract = test_contract();
1286        let mut executor = Executor::new(contract);
1287
1288        let result = executor.execute_operation("echo", "not json");
1289        assert!(result.is_err());
1290    }
1291
1292    #[test]
1293    fn test_execute_operation_invariant_violation() {
1294        let contract = test_contract();
1295        let mut executor = Executor::new(contract);
1296
1297        // count >= 0 invariant — setting count to -1 should fail
1298        let result = executor.execute_operation("echo", r#"{"count": -1, "message": "hi"}"#);
1299        assert!(result.is_err());
1300        let err = result.unwrap_err().to_string();
1301        assert!(err.contains("invariant") || err.contains("Violated"));
1302    }
1303
1304    #[test]
1305    fn test_execute_operation_state_rollback_on_failure() {
1306        let contract = test_contract();
1307        let mut executor = Executor::new(contract);
1308
1309        // First: set valid state
1310        executor
1311            .execute_operation("echo", r#"{"message": "hello"}"#)
1312            .unwrap();
1313
1314        let state_after_success = executor.state().clone();
1315
1316        // Second: try invalid operation (violates count >= 0)
1317        let _ = executor.execute_operation("echo", r#"{"count": -1, "message": "hi"}"#);
1318
1319        // State should be rolled back to after first success
1320        assert_eq!(*executor.state(), state_after_success);
1321    }
1322
1323    #[test]
1324    fn test_execute_all_success() {
1325        let contract = test_contract();
1326        let mut executor = Executor::new(contract);
1327
1328        let requests = r#"[
1329            {"operation": "echo", "inputs": {"message": "hello"}},
1330            {"operation": "echo", "inputs": {"message": "world"}}
1331        ]"#;
1332
1333        let result = executor.execute_all(requests).unwrap();
1334        assert!(result.success);
1335        assert_eq!(result.operations.len(), 2);
1336        assert_eq!(result.provenance.len(), 2);
1337    }
1338
1339    #[test]
1340    fn test_execute_all_stops_on_failure() {
1341        let contract = test_contract();
1342        let mut executor = Executor::new(contract);
1343
1344        let requests = r#"[
1345            {"operation": "echo", "inputs": {"message": "hello"}},
1346            {"operation": "nonexistent", "inputs": {}},
1347            {"operation": "echo", "inputs": {"message": "world"}}
1348        ]"#;
1349
1350        let result = executor.execute_all(requests).unwrap();
1351        assert!(!result.success);
1352        assert_eq!(result.operations.len(), 2); // only 2 attempted
1353    }
1354
1355    #[test]
1356    fn test_provenance_records_state_changes() {
1357        let contract = test_contract();
1358        let mut executor = Executor::new(contract);
1359
1360        executor
1361            .execute_operation("echo", r#"{"message": "hello"}"#)
1362            .unwrap();
1363
1364        let log = executor.provenance();
1365        assert_eq!(log.len(), 1);
1366
1367        let entry = &log.entries[0];
1368        assert_eq!(entry.operation, "echo");
1369        assert_eq!(entry.sequence, 0);
1370        assert!(entry.postconditions_verified);
1371        assert!(entry.invariants_verified);
1372        assert!(!entry.changes.is_empty());
1373
1374        // Verify the message change was recorded
1375        let msg_change = entry.changes.iter().find(|c| c.field == "message").unwrap();
1376        assert_eq!(msg_change.old_value, Value::String(String::new()));
1377        assert_eq!(msg_change.new_value, Value::String("hello".into()));
1378    }
1379
1380    #[test]
1381    fn test_provenance_sequential_numbering() {
1382        let contract = test_contract();
1383        let mut executor = Executor::new(contract);
1384
1385        executor
1386            .execute_operation("echo", r#"{"message": "first"}"#)
1387            .unwrap();
1388        executor
1389            .execute_operation("echo", r#"{"message": "second"}"#)
1390            .unwrap();
1391        executor
1392            .execute_operation("echo", r#"{"message": "third"}"#)
1393            .unwrap();
1394
1395        let log = executor.provenance();
1396        assert_eq!(log.entries[0].sequence, 0);
1397        assert_eq!(log.entries[1].sequence, 1);
1398        assert_eq!(log.entries[2].sequence, 2);
1399    }
1400
1401    // ── Public API Tests ──────────────────────────────────
1402
1403    #[test]
1404    fn test_execute_contract_single_request() {
1405        let contract = test_contract();
1406        let result = execute_contract(
1407            &contract,
1408            r#"{"operation": "echo", "inputs": {"message": "hello"}}"#,
1409        )
1410        .unwrap();
1411
1412        let json: serde_json::Value = serde_json::from_str(&result).unwrap();
1413        assert_eq!(json["success"], true);
1414        assert_eq!(json["contract_id"], "ic-test-001");
1415    }
1416
1417    #[test]
1418    fn test_execute_contract_array_requests() {
1419        let contract = test_contract();
1420        let result = execute_contract(
1421            &contract,
1422            r#"[{"operation": "echo", "inputs": {"message": "hello"}}]"#,
1423        )
1424        .unwrap();
1425
1426        let json: serde_json::Value = serde_json::from_str(&result).unwrap();
1427        assert_eq!(json["success"], true);
1428    }
1429
1430    #[test]
1431    fn test_execute_contract_invalid_input() {
1432        let contract = test_contract();
1433        let result = execute_contract(&contract, "not json");
1434        assert!(result.is_err());
1435    }
1436
1437    // ── Determinism Tests ─────────────────────────────────
1438
1439    #[test]
1440    fn test_deterministic_execution() {
1441        let contract = test_contract();
1442        let input = r#"{"operation": "echo", "inputs": {"message": "determinism test"}}"#;
1443
1444        let first = execute_contract(&contract, input).unwrap();
1445        for i in 0..100 {
1446            let result = execute_contract(&contract, input).unwrap();
1447            assert_eq!(first, result, "Non-determinism at iteration {}", i);
1448        }
1449    }
1450
1451    #[test]
1452    fn test_deterministic_multi_operation() {
1453        let contract = test_contract();
1454        let input = r#"[
1455            {"operation": "echo", "inputs": {"message": "first"}},
1456            {"operation": "echo", "inputs": {"message": "second"}}
1457        ]"#;
1458
1459        let first = execute_contract(&contract, input).unwrap();
1460        for i in 0..100 {
1461            let result = execute_contract(&contract, input).unwrap();
1462            assert_eq!(first, result, "Non-determinism at iteration {}", i);
1463        }
1464    }
1465
1466    #[test]
1467    fn test_deterministic_provenance() {
1468        let contract = test_contract();
1469        let input = r#"{"operation": "echo", "inputs": {"message": "prov test"}}"#;
1470
1471        let first_json: serde_json::Value =
1472            serde_json::from_str(&execute_contract(&contract, input).unwrap()).unwrap();
1473        let first_provenance = &first_json["provenance"];
1474
1475        for i in 0..100 {
1476            let result_json: serde_json::Value =
1477                serde_json::from_str(&execute_contract(&contract, input).unwrap()).unwrap();
1478            assert_eq!(
1479                first_provenance, &result_json["provenance"],
1480                "Provenance non-determinism at iteration {}",
1481                i
1482            );
1483        }
1484    }
1485
1486    // ── Resource Limit Tests ──────────────────────────────
1487
1488    #[test]
1489    fn test_resource_limit_memory_exceeded() {
1490        let mut contract = test_contract();
1491        contract
1492            .execution_constraints
1493            .resource_limits
1494            .max_state_size_bytes = 10;
1495        contract
1496            .execution_constraints
1497            .resource_limits
1498            .max_memory_bytes = 10;
1499        // Remove invariants so the only failure mode is memory
1500        contract.data_semantics.invariants.clear();
1501
1502        let mut executor = Executor::new(contract);
1503        let result = executor.execute_operation(
1504            "echo",
1505            r#"{"message": "this string is way too long for the tiny memory limit we set"}"#,
1506        );
1507        assert!(result.is_err());
1508        let err = result.unwrap_err().to_string();
1509        assert!(err.contains("exceeds limit") || err.contains("bytes"));
1510    }
1511
1512    #[test]
1513    fn test_precondition_enforcement() {
1514        // Create a contract where precondition is evaluable and fails
1515        let mut contract = test_contract();
1516        contract.behavioral_semantics.operations[0].precondition = "count >= 10".into();
1517        // Clear invariants to isolate precondition testing
1518        contract.data_semantics.invariants.clear();
1519
1520        let mut executor = Executor::new(contract);
1521        // count starts at 0, precondition requires >= 10
1522        let result = executor.execute_operation("echo", r#"{"message": "hello"}"#);
1523        assert!(result.is_err());
1524        let err = result.unwrap_err().to_string();
1525        assert!(err.contains("Precondition failed"));
1526    }
1527
1528    #[test]
1529    fn test_postcondition_verification() {
1530        // Create a contract where postcondition is evaluable
1531        let mut contract = test_contract();
1532        contract.behavioral_semantics.operations[0].postcondition = "count >= 1".into();
1533        // Clear invariants to isolate postcondition testing
1534        contract.data_semantics.invariants.clear();
1535
1536        let mut executor = Executor::new(contract);
1537        // Operation doesn't set count, so postcondition count >= 1 fails
1538        let result = executor.execute_operation("echo", r#"{"message": "hello"}"#);
1539        assert!(result.is_err());
1540        let err = result.unwrap_err().to_string();
1541        assert!(err.contains("postcondition") || err.contains("Contract violation"));
1542    }
1543}