Skip to main content

traverse_runtime/
lib.rs

1//! Runtime control-plane support for Traverse.
2
3mod workflows;
4pub use workflows::*;
5mod artifact_router;
6pub use artifact_router::*;
7pub mod data_store;
8pub mod events;
9pub mod executor;
10pub mod inference;
11pub mod placement;
12pub mod router;
13pub mod security;
14pub mod trace;
15
16use chrono::Utc;
17use events::{
18    EventBroker, EventCatalog, EventError, InProcessBroker, NoopRuntimeEventSink, RuntimeEventSink,
19    Subscription, SubscriptionPoll, TraverseEvent,
20};
21use executor::{
22    ArtifactType, CapabilityExecutor, ExecutorCapability, ExecutorError, ExecutorOutput,
23};
24use placement::{PlacementConstraintEvaluator, RuntimeSnapshot};
25use router::{CapabilityExecutorRegistry, PlacementRouter, RouterError, RouterRequest};
26use security::{
27    ArtifactVerificationFailure, ArtifactVerificationRecord, RuntimeIdentity,
28    RuntimeSecurityConfig, RuntimeWarning, derive_identity_from_jwt, verify_artifact,
29};
30use semver::Version;
31use serde::{Deserialize, Serialize};
32use serde_json::{Map, Value, json};
33use std::fmt;
34use std::fs;
35use std::path::Path;
36use std::sync::{Arc, Mutex};
37use trace::TraceStore;
38use traverse_contracts::{
39    EventReference, ExecutionTarget, HostApiAccess, Lifecycle, NetworkAccess, ServiceType,
40    ViolationRecord,
41};
42use traverse_registry::{
43    CapabilityRegistration, CapabilityRegistry, DiscoveryQuery, ImplementationKind, LookupScope,
44    ModelResolutionEvidence, RegistrationOutcome, RegistryFailure, RegistryScope, ResolutionError,
45    ResolvedCapability, WorkflowFailure, WorkflowRegistration, WorkflowRegistrationOutcome,
46    WorkflowRegistry, WorkspaceAppStateFailure, WorkspaceApplicationRegistration,
47    load_workspace_application_registries, resolve_dependencies, resolve_version_range,
48};
49use uuid::Uuid;
50
51const RUNTIME_REQUEST_KIND: &str = "runtime_request";
52const RUNTIME_RESULT_KIND: &str = "runtime_result";
53const RUNTIME_STATE_EVENT_KIND: &str = "runtime_state_event";
54const RUNTIME_TRACE_KIND: &str = "runtime_trace";
55const RUNTIME_STATE_MACHINE_VALIDATION_KIND: &str = "runtime_state_machine_validation";
56const BROWSER_SUBSCRIPTION_REQUEST_KIND: &str = "browser_runtime_subscription_request";
57const BROWSER_SUBSCRIPTION_ERROR_KIND: &str = "browser_runtime_subscription_error";
58const BROWSER_SUBSCRIPTION_LIFECYCLE_KIND: &str = "browser_runtime_subscription_lifecycle";
59const BROWSER_SUBSCRIPTION_STATE_KIND: &str = "browser_runtime_subscription_state";
60const BROWSER_SUBSCRIPTION_TRACE_KIND: &str = "browser_runtime_subscription_trace_artifact";
61const BROWSER_SUBSCRIPTION_TERMINAL_KIND: &str = "browser_runtime_subscription_terminal";
62const SUPPORTED_SCHEMA_VERSION: &str = "1.0.0";
63const GOVERNING_SPEC: &str = "006-runtime-request-execution";
64const STATE_MACHINE_GOVERNING_SPEC: &str = "010-runtime-state-machine";
65const BROWSER_SUBSCRIPTION_GOVERNING_SPEC: &str = "013-browser-runtime-subscription";
66const EXECUTION_PREFIX: &str = "exec_";
67const TRACE_PREFIX: &str = "trace_";
68const RUNTIME_EXECUTION_EVENT_TYPE: &str = "dev.traverse.runtime.execution.completed";
69
70pub struct Runtime<E> {
71    registry: CapabilityRegistry,
72    workflow_registry: WorkflowRegistry,
73    applications: Vec<WorkspaceApplicationRegistration>,
74    executor: Arc<E>,
75    observability: RuntimeObservabilityConfig,
76    security: RuntimeSecurityConfig,
77    event_sink: Arc<dyn RuntimeEventSink>,
78    trace_store: Arc<Mutex<TraceStore>>,
79    event_broker: Arc<dyn EventBroker>,
80}
81
82impl<E: Clone> Clone for Runtime<E> {
83    fn clone(&self) -> Self {
84        Self {
85            registry: self.registry.clone(),
86            workflow_registry: self.workflow_registry.clone(),
87            applications: self.applications.clone(),
88            executor: Arc::clone(&self.executor),
89            observability: self.observability.clone(),
90            security: self.security.clone(),
91            event_sink: Arc::clone(&self.event_sink),
92            trace_store: Arc::clone(&self.trace_store),
93            event_broker: Arc::clone(&self.event_broker),
94        }
95    }
96}
97
98impl<E: fmt::Debug> fmt::Debug for Runtime<E> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("Runtime")
101            .field("registry", &self.registry)
102            .field("workflow_registry", &self.workflow_registry)
103            .field("applications", &self.applications)
104            .field("executor", &self.executor)
105            .field("observability", &self.observability)
106            .field("security", &self.security)
107            .field("event_sink", &self.event_sink)
108            .field("trace_store", &self.trace_store)
109            .field("event_broker", &"Arc<dyn EventBroker>")
110            .finish()
111    }
112}
113
114impl<E> Runtime<E> {
115    #[must_use]
116    pub fn new(registry: CapabilityRegistry, executor: E) -> Self {
117        Self {
118            registry,
119            workflow_registry: WorkflowRegistry::new(),
120            applications: Vec::new(),
121            executor: Arc::new(executor),
122            observability: RuntimeObservabilityConfig::default(),
123            security: RuntimeSecurityConfig::default(),
124            event_sink: Arc::new(NoopRuntimeEventSink),
125            trace_store: Arc::new(Mutex::new(TraceStore::new())),
126            event_broker: default_event_broker(),
127        }
128    }
129
130    #[must_use]
131    pub fn with_workflow_registry(mut self, workflow_registry: WorkflowRegistry) -> Self {
132        self.workflow_registry = workflow_registry;
133        self
134    }
135
136    #[must_use]
137    pub fn with_workspace_applications(
138        mut self,
139        applications: Vec<WorkspaceApplicationRegistration>,
140    ) -> Self {
141        self.applications = applications;
142        self
143    }
144
145    /// Loads a runtime from durable local workspace app registration state.
146    ///
147    /// # Errors
148    ///
149    /// Returns [`WorkspaceAppStateFailure`] when the workspace has no app
150    /// registration state, state is malformed or incompatible, or registry
151    /// reconstruction fails validation.
152    pub fn from_workspace_app_state(
153        workspace_root: &Path,
154        workspace_id: &str,
155        executor: E,
156        validator_version: &str,
157    ) -> Result<Self, WorkspaceAppStateFailure> {
158        let loaded =
159            load_workspace_application_registries(workspace_root, workspace_id, validator_version)?;
160        Ok(Self::new(loaded.capability_registry, executor)
161            .with_workflow_registry(loaded.workflow_registry)
162            .with_workspace_applications(loaded.applications))
163    }
164
165    #[must_use]
166    pub fn with_observability_config(mut self, observability: RuntimeObservabilityConfig) -> Self {
167        self.observability = observability;
168        self
169    }
170
171    #[must_use]
172    pub fn observability_config(&self) -> &RuntimeObservabilityConfig {
173        &self.observability
174    }
175
176    #[must_use]
177    pub fn with_security_config(mut self, security: RuntimeSecurityConfig) -> Self {
178        self.security = security;
179        self
180    }
181
182    /// Configures delivery for runtime-owned lifecycle envelopes.
183    #[must_use]
184    pub fn with_event_sink(mut self, event_sink: Arc<dyn RuntimeEventSink>) -> Self {
185        self.event_sink = event_sink;
186        self
187    }
188
189    /// Injects the [`EventBroker`] used by [`PlacementRouter`] Step 5.
190    #[must_use]
191    pub fn with_event_broker(mut self, event_broker: Arc<dyn EventBroker>) -> Self {
192        self.event_broker = event_broker;
193        self
194    }
195
196    /// Injects the [`TraceStore`] written by [`PlacementRouter`] Step 4.
197    #[must_use]
198    pub fn with_trace_store(mut self, trace_store: Arc<Mutex<TraceStore>>) -> Self {
199        self.trace_store = trace_store;
200        self
201    }
202
203    /// Returns the runtime-owned event broker used for capability event publishing.
204    #[must_use]
205    pub fn event_broker(&self) -> Arc<dyn EventBroker> {
206        Arc::clone(&self.event_broker)
207    }
208
209    /// Returns the runtime-owned placement-router trace store.
210    #[must_use]
211    pub fn trace_store(&self) -> Arc<Mutex<TraceStore>> {
212        Arc::clone(&self.trace_store)
213    }
214
215    #[must_use]
216    pub fn security_config(&self) -> &RuntimeSecurityConfig {
217        &self.security
218    }
219
220    /// Returns a reference to the capability registry.
221    #[must_use]
222    pub fn capability_registry(&self) -> &CapabilityRegistry {
223        &self.registry
224    }
225
226    /// Registers a capability into the runtime's registry.
227    ///
228    /// Returns `true` when the capability was newly registered, `false` when
229    /// the same contract digest was already present (idempotent no-op).
230    ///
231    /// # Errors
232    ///
233    /// Returns [`RegistryFailure`] when contract validation fails or when a
234    /// different contract digest conflicts with an existing immutable version.
235    pub fn register_capability(
236        &mut self,
237        registration: CapabilityRegistration,
238    ) -> Result<RegistrationOutcome, RegistryFailure> {
239        self.registry.register(registration)
240    }
241
242    /// Returns a reference to the workflow registry.
243    #[must_use]
244    pub fn workflow_registry(&self) -> &WorkflowRegistry {
245        &self.workflow_registry
246    }
247
248    /// Returns workspace application registrations loaded into this runtime.
249    #[must_use]
250    pub fn workspace_applications(&self) -> &[WorkspaceApplicationRegistration] {
251        self.applications.as_slice()
252    }
253
254    /// Returns a mutable reference to the workflow registry.
255    #[must_use]
256    pub fn workflow_registry_mut(&mut self) -> &mut WorkflowRegistry {
257        &mut self.workflow_registry
258    }
259
260    /// Registers a workflow into the runtime's workflow registry.
261    ///
262    /// # Errors
263    ///
264    /// Returns [`WorkflowFailure`] when the workflow is invalid, references a
265    /// missing capability, contains a cycle, or violates immutability.
266    pub fn register_workflow(
267        &mut self,
268        registration: WorkflowRegistration,
269    ) -> Result<WorkflowRegistrationOutcome, WorkflowFailure> {
270        self.workflow_registry
271            .register(&self.registry, registration)
272    }
273
274    /// Executes an app-declared model dependency through the governed inference surface.
275    ///
276    /// # Errors
277    ///
278    /// Returns [`inference::GovernedModelExecutionError`] when the app or
279    /// interface is not registered, model resolution fails, or provider
280    /// execution fails.
281    pub fn execute_governed_model_dependency(
282        &self,
283        app_id: &str,
284        app_version: &str,
285        request: &inference::GovernedModelExecutionRequest,
286    ) -> Result<inference::GovernedModelExecutionOutcome, inference::GovernedModelExecutionError>
287    {
288        let Some(application) = self.applications.iter().find(|application| {
289            application.app_id == app_id && application.app_version == app_version
290        }) else {
291            return Err(inference::GovernedModelExecutionError::new(
292                inference::GovernedModelExecutionErrorCode::InterfaceNotDeclared,
293                "application registration was not loaded into this runtime",
294            ));
295        };
296        let Some(dependency) = application
297            .model_dependencies
298            .iter()
299            .find(|dependency| dependency.interface_id == request.interface_id)
300        else {
301            return Err(inference::GovernedModelExecutionError::new(
302                inference::GovernedModelExecutionErrorCode::InterfaceNotDeclared,
303                "requested inference interface is not declared by this application",
304            ));
305        };
306
307        inference::execute_governed_ollama_model_dependency(dependency, request)
308    }
309}
310
311pub trait LocalExecutor: Send + Sync {
312    /// Executes one locally selected capability.
313    ///
314    /// # Errors
315    ///
316    /// Returns [`LocalExecutionFailure`] when the executor cannot complete the
317    /// selected capability.
318    fn execute(
319        &self,
320        capability: &ResolvedCapability,
321        input: &Value,
322    ) -> Result<LocalExecutionOutput, LocalExecutionFailure>;
323}
324
325/// A [`LocalExecutor`]'s output: the capability's JSON value plus any events
326/// it emitted (spec 101-local-executor-event-emission FR-001). Mirrors
327/// [`crate::executor::ExecutorOutput`] for the WASM `CapabilityExecutor` path.
328#[derive(Debug, Clone, PartialEq)]
329pub struct LocalExecutionOutput {
330    pub value: Value,
331    pub emitted_events: Vec<TraverseEvent>,
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
335pub struct LocalExecutionFailure {
336    pub code: LocalExecutionFailureCode,
337    pub message: String,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum LocalExecutionFailureCode {
342    /// The executor failed for an unclassified reason. Retryable with caution.
343    ExecutionFailed,
344    /// The execution did not complete within the allowed time window.
345    /// Transient — retryable with exponential backoff.
346    Timeout,
347    /// The input provided to the capability was invalid or malformed.
348    /// Fatal — do not retry; fix the input before resubmitting.
349    InvalidInput,
350    /// A required resource (memory, CPU, file handles, etc.) was exhausted.
351    /// Transient — retry with a longer backoff interval.
352    ResourceExhausted,
353    /// A capability contract constraint (precondition, postcondition, or policy) was violated.
354    /// Fatal — do not retry; the request violates the contract.
355    ConstraintViolated,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct RuntimeRequest {
360    pub kind: String,
361    pub schema_version: String,
362    pub request_id: String,
363    pub intent: RuntimeIntent,
364    pub input: Value,
365    pub lookup: RuntimeLookup,
366    pub context: RuntimeContext,
367    pub governing_spec: String,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
371pub struct RuntimeIntent {
372    #[serde(default)]
373    pub capability_id: Option<String>,
374    #[serde(default)]
375    pub capability_version: Option<String>,
376    /// Optional semver range expression (e.g. `^1.0.0`, `>=1.2 <2`).
377    /// When present and `capability_version` is absent, the runtime uses
378    /// range resolution rather than exact version lookup.
379    #[serde(default)]
380    pub version_range: Option<String>,
381    #[serde(default)]
382    pub intent_key: Option<String>,
383}
384
385#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
386pub struct RuntimeLookup {
387    pub scope: RuntimeLookupScope,
388    pub allow_ambiguity: bool,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
392#[serde(rename_all = "snake_case")]
393pub enum RuntimeLookupScope {
394    PublicOnly,
395    PreferPrivate,
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
399pub struct RuntimeContext {
400    pub requested_target: PlacementTarget,
401    #[serde(default)]
402    pub correlation_id: Option<String>,
403    #[serde(default)]
404    pub caller: Option<String>,
405    #[serde(default)]
406    pub traceparent: Option<String>,
407    #[serde(default)]
408    pub tracestate: Option<String>,
409    #[serde(default)]
410    pub metadata: Option<Value>,
411    #[serde(default)]
412    pub identity: Option<RuntimeIdentity>,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
416pub struct RuntimeObservabilityConfig {
417    pub signals: OTelSignalConfig,
418    pub exporter: OTelExporterConfig,
419    pub deterministic_ids: bool,
420    #[serde(default)]
421    pub deterministic_seed: Option<String>,
422}
423
424#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
425pub struct OTelSignalConfig {
426    pub traces_enabled: bool,
427    pub logs_enabled: bool,
428    pub metrics_enabled: bool,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
432pub struct OTelExporterConfig {
433    #[serde(default)]
434    pub endpoint: Option<String>,
435    pub protocol: OtlpProtocol,
436}
437
438impl Default for RuntimeObservabilityConfig {
439    fn default() -> Self {
440        Self {
441            signals: OTelSignalConfig {
442                traces_enabled: true,
443                logs_enabled: false,
444                metrics_enabled: false,
445            },
446            exporter: OTelExporterConfig {
447                endpoint: None,
448                protocol: OtlpProtocol::Http,
449            },
450            deterministic_ids: false,
451            deterministic_seed: None,
452        }
453    }
454}
455
456impl RuntimeObservabilityConfig {
457    #[must_use]
458    pub fn deterministic_test(seed: &str) -> Self {
459        Self {
460            deterministic_ids: true,
461            deterministic_seed: Some(seed.to_string()),
462            ..Self::default()
463        }
464    }
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
468#[serde(rename_all = "snake_case")]
469pub enum OtlpProtocol {
470    Http,
471    Grpc,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
475#[serde(rename_all = "snake_case")]
476pub enum PlacementTarget {
477    Local,
478    Browser,
479    Edge,
480    Cloud,
481    Worker,
482    Device,
483}
484
485#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
486pub struct PlacementDecisionRecord {
487    pub requested_target: PlacementTarget,
488    #[serde(default)]
489    pub selected_target: Option<PlacementTarget>,
490    pub status: PlacementDecisionStatus,
491    pub reason: PlacementDecisionReason,
492    pub supported_executor_targets: Vec<PlacementTarget>,
493}
494
495#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(rename_all = "snake_case")]
497pub enum PlacementDecisionStatus {
498    NotAttempted,
499    Selected,
500    Unsupported,
501}
502
503#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
504#[serde(rename_all = "snake_case")]
505pub enum PlacementDecisionReason {
506    SelectionNotReached,
507    RequestedTargetSelected,
508    RequestedTargetUnsupported,
509}
510
511#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
512pub struct RuntimeStateEvent {
513    pub kind: String,
514    pub schema_version: String,
515    pub event_id: String,
516    pub execution_id: String,
517    pub request_id: String,
518    pub state: RuntimeState,
519    pub entered_at: String,
520    pub details: Value,
521}
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
524#[serde(rename_all = "snake_case")]
525pub enum RuntimeState {
526    Idle,
527    LoadingRegistry,
528    Ready,
529    Discovering,
530    EvaluatingConstraints,
531    Selecting,
532    Executing,
533    EmittingEvents,
534    Completed,
535    Error,
536}
537
538#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
539#[serde(rename_all = "snake_case")]
540pub enum RuntimeTransitionReasonCode {
541    RuntimeInitializationStarted,
542    RegistryLoaded,
543    RegistryLoadFailed,
544    RequestStarted,
545    CandidatesCollected,
546    NoMatch,
547    ConstraintsEvaluated,
548    ConstraintValidationFailed,
549    CandidateSelected,
550    SelectionFailed,
551    ExecutionSucceededWithEvents,
552    ExecutionSucceeded,
553    ExecutionFailed,
554    EventsEmitted,
555    EventEmissionFailed,
556    ExecutionClosed,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
560pub struct RuntimeTransitionRecord {
561    pub from_state: RuntimeState,
562    pub to_state: RuntimeState,
563    pub reason_code: RuntimeTransitionReasonCode,
564    pub occurred_at: String,
565    #[serde(default)]
566    pub request_id: Option<String>,
567    #[serde(default)]
568    pub execution_id: Option<String>,
569    #[serde(default)]
570    pub details: Option<Value>,
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
574pub struct RuntimeStateMachineValidationEvidence {
575    pub kind: String,
576    pub schema_version: String,
577    pub governing_spec: String,
578    pub validated_at: String,
579    pub status: RuntimeStateMachineValidationStatus,
580    pub checked_states: Vec<RuntimeState>,
581    pub checked_transitions: Vec<String>,
582    pub violations: Vec<Value>,
583}
584
585#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
586#[serde(rename_all = "snake_case")]
587pub enum RuntimeStateMachineValidationStatus {
588    Passed,
589    Failed,
590}
591
592#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
593pub struct RuntimeTrace {
594    pub kind: String,
595    pub schema_version: String,
596    pub trace_id: String,
597    pub execution_id: String,
598    pub request_id: String,
599    pub governing_spec: String,
600    pub request: RuntimeRequest,
601    pub decision_evidence: TraceDecisionEvidence,
602    pub state_progression: TraceStateProgression,
603    pub terminal_outcome: TraceTerminalOutcome,
604    pub emitted_events: Vec<traverse_contracts::EventReference>,
605    #[serde(default)]
606    pub workflow_evidence: Option<WorkflowTraversalEvidence>,
607    #[serde(default)]
608    pub model_resolution: Vec<ModelResolutionEvidence>,
609    pub state_transitions: Vec<RuntimeTransitionRecord>,
610    pub state_machine_validation: RuntimeStateMachineValidationEvidence,
611    pub candidate_collection: CandidateCollectionRecord,
612    pub selection: SelectionRecord,
613    pub execution: ExecutionRecord,
614    pub result: TraceResultRecord,
615    pub otel_trace: OTelTraceRecord,
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
619pub struct OTelTraceRecord {
620    pub trace_id: String,
621    #[serde(default)]
622    pub parent_traceparent: Option<String>,
623    #[serde(default)]
624    pub tracestate: Option<String>,
625    pub exporter: OTelExporterRecord,
626    pub spans: Vec<OTelSpanRecord>,
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
630pub struct OTelExporterRecord {
631    pub enabled: bool,
632    #[serde(default)]
633    pub endpoint: Option<String>,
634    pub protocol: OtlpProtocol,
635}
636
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
638pub struct OTelSpanRecord {
639    pub trace_id: String,
640    pub span_id: String,
641    #[serde(default)]
642    pub parent_span_id: Option<String>,
643    pub name: String,
644    pub kind: OTelSpanKind,
645    pub status: OTelSpanStatus,
646    pub started_at: String,
647    pub ended_at: String,
648    pub attributes: Vec<OTelAttribute>,
649    #[serde(default)]
650    pub events: Vec<OTelSpanEvent>,
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
654#[serde(rename_all = "snake_case")]
655pub enum OTelSpanKind {
656    Internal,
657}
658
659#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
660#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
661pub enum OTelSpanStatus {
662    Ok,
663    Error,
664}
665
666#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
667pub struct OTelAttribute {
668    pub key: String,
669    pub value: Value,
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
673pub struct OTelSpanEvent {
674    pub name: String,
675    pub timestamp: String,
676    pub attributes: Vec<OTelAttribute>,
677}
678
679impl RuntimeTrace {
680    /// Returns a trace with synchronized public model resolution evidence.
681    #[must_use]
682    pub fn with_model_resolution(mut self, evidence: Vec<ModelResolutionEvidence>) -> Self {
683        self.decision_evidence
684            .model_resolution
685            .clone_from(&evidence);
686        self.model_resolution = evidence;
687        self
688    }
689
690    /// Returns the ID of the selected capability, or `None` if no capability was selected.
691    #[must_use]
692    pub fn selected_capability_id(&self) -> Option<&str> {
693        self.selection.selected_capability_id.as_deref()
694    }
695
696    /// Returns the error from the terminal outcome, or `None` if execution succeeded.
697    #[must_use]
698    pub fn errors(&self) -> Option<&RuntimeError> {
699        self.terminal_outcome.error.as_ref()
700    }
701
702    /// Returns all events emitted during execution.
703    #[must_use]
704    pub fn emitted_events(&self) -> &[traverse_contracts::EventReference] {
705        self.emitted_events.as_slice()
706    }
707
708    /// Returns the output value produced by execution, or `None` if unavailable.
709    #[must_use]
710    pub fn output(&self) -> Option<&serde_json::Value> {
711        self.result.output.as_ref()
712    }
713
714    /// Returns `true` if the execution completed successfully.
715    #[must_use]
716    pub fn is_success(&self) -> bool {
717        self.terminal_outcome.runtime_status == RuntimeResultStatus::Completed
718    }
719}
720
721#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
722pub struct TraceDecisionEvidence {
723    pub candidate_collection: CandidateCollectionRecord,
724    pub selection: SelectionRecord,
725    #[serde(default)]
726    pub model_resolution: Vec<ModelResolutionEvidence>,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
730pub struct TraceStateProgression {
731    pub state_events: Vec<RuntimeStateEvent>,
732    pub transitions: Vec<RuntimeTransitionRecord>,
733    pub validation: RuntimeStateMachineValidationEvidence,
734}
735
736#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737pub struct TraceTerminalOutcome {
738    pub runtime_status: RuntimeResultStatus,
739    pub execution_status: ExecutionStatus,
740    #[serde(default)]
741    pub failure_reason: Option<ExecutionFailureReason>,
742    #[serde(default)]
743    pub error: Option<RuntimeError>,
744}
745
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747pub struct CandidateCollectionRecord {
748    pub lookup_scope: RuntimeLookupScope,
749    pub candidates: Vec<RuntimeCandidate>,
750    pub rejected_candidates: Vec<RejectedRuntimeCandidate>,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
754pub struct RuntimeCandidate {
755    pub scope: RuntimeRegistryScope,
756    pub capability_id: String,
757    pub capability_version: String,
758    pub artifact_ref: String,
759    pub implementation_kind: RuntimeImplementationKind,
760    pub lifecycle: RuntimeLifecycle,
761    pub reason: CandidateReason,
762}
763
764#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
765pub struct RejectedRuntimeCandidate {
766    pub capability_id: String,
767    pub capability_version: String,
768    pub scope: RuntimeRegistryScope,
769    pub reason: RejectedCandidateReason,
770}
771
772#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
773#[serde(rename_all = "snake_case")]
774pub enum CandidateReason {
775    ExactMatch,
776    IntentMatch,
777}
778
779#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
780#[serde(rename_all = "snake_case")]
781pub enum RejectedCandidateReason {
782    WrongScope,
783    NotRunnableLocally,
784    LifecycleNotRunnable,
785    InputContractInvalid,
786    ArtifactMissing,
787    SupersededByPrivateOverlay,
788    NotSelectedAfterOrdering,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
792pub struct SelectionRecord {
793    pub status: SelectionStatus,
794    #[serde(default)]
795    pub selected_capability_id: Option<String>,
796    #[serde(default)]
797    pub selected_capability_version: Option<String>,
798    #[serde(default)]
799    pub failure_reason: Option<SelectionFailureReason>,
800    #[serde(default)]
801    pub remaining_candidates: Vec<RuntimeCandidate>,
802}
803
804#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
805#[serde(rename_all = "snake_case")]
806pub enum SelectionStatus {
807    Selected,
808    NoMatch,
809    Ambiguous,
810    InvalidRequest,
811}
812
813#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
814#[serde(rename_all = "snake_case")]
815pub enum SelectionFailureReason {
816    InvalidRequest,
817    NoMatch,
818    Ambiguous,
819    NotRunnable,
820}
821
822#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
823pub struct ExecutionRecord {
824    pub placement: PlacementDecisionRecord,
825    pub placement_target: PlacementTarget,
826    pub status: ExecutionStatus,
827    #[serde(default)]
828    pub artifact_ref: Option<String>,
829    #[serde(default)]
830    pub started_at: Option<String>,
831    #[serde(default)]
832    pub completed_at: Option<String>,
833    #[serde(default)]
834    pub output_digest: Option<String>,
835    #[serde(default)]
836    pub failure_reason: Option<ExecutionFailureReason>,
837    #[serde(default)]
838    pub artifact_verification: Option<ArtifactVerificationRecord>,
839    #[serde(default)]
840    pub identity: Option<RuntimeIdentity>,
841}
842
843#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
844#[serde(rename_all = "snake_case")]
845pub enum ExecutionStatus {
846    NotStarted,
847    Succeeded,
848    Failed,
849}
850
851#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
852#[serde(rename_all = "snake_case")]
853pub enum ExecutionFailureReason {
854    ContractInputInvalid,
855    ArtifactMissing,
856    ArtifactNotRunnable,
857    PlacementUnsupported,
858    ExecutionFailed,
859    ContractOutputInvalid,
860}
861
862#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
863pub struct TraceResultRecord {
864    pub status: RuntimeResultStatus,
865    #[serde(default)]
866    pub output: Option<serde_json::Value>,
867    #[serde(default)]
868    pub error: Option<RuntimeError>,
869    #[serde(default)]
870    pub warnings: Vec<RuntimeWarning>,
871}
872
873#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
874pub struct RuntimeResult {
875    pub kind: String,
876    pub schema_version: String,
877    pub execution_id: String,
878    pub request_id: String,
879    pub status: RuntimeResultStatus,
880    pub trace_ref: String,
881    #[serde(default)]
882    pub output: Option<Value>,
883    #[serde(default)]
884    pub error: Option<RuntimeError>,
885    #[serde(default)]
886    pub warnings: Vec<RuntimeWarning>,
887}
888
889#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
890#[serde(rename_all = "snake_case")]
891pub enum RuntimeResultStatus {
892    Completed,
893    Error,
894}
895
896#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
897pub struct RuntimeError {
898    pub code: RuntimeErrorCode,
899    pub message: String,
900    pub details: Value,
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
904#[serde(rename_all = "snake_case")]
905pub enum RuntimeErrorCode {
906    RequestInvalid,
907    CapabilityNotFound,
908    CapabilityAmbiguous,
909    CapabilityNotRunnable,
910    PlacementUnsupported,
911    ArtifactMissing,
912    ExecutionFailed,
913    OutputValidationFailed,
914    ContractViolation,
915}
916
917#[derive(Debug, Clone, PartialEq, Eq)]
918pub struct RuntimeExecutionOutcome {
919    pub result: RuntimeResult,
920    pub trace: RuntimeTrace,
921    pub state_events: Vec<RuntimeStateEvent>,
922}
923
924#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
925pub struct BrowserRuntimeSubscriptionRequest {
926    pub kind: String,
927    pub schema_version: String,
928    pub governing_spec: String,
929    #[serde(default)]
930    pub request_id: Option<String>,
931    #[serde(default)]
932    pub execution_id: Option<String>,
933}
934
935#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
936pub struct BrowserRuntimeSubscriptionErrorMessage {
937    pub kind: String,
938    pub schema_version: String,
939    pub sequence: u64,
940    pub code: BrowserRuntimeSubscriptionErrorCode,
941    pub message: String,
942}
943
944#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
945#[serde(rename_all = "snake_case")]
946pub enum BrowserRuntimeSubscriptionErrorCode {
947    InvalidRequest,
948    NotFound,
949    UnsupportedOperation,
950}
951
952#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
953pub struct BrowserRuntimeSubscriptionLifecycleMessage {
954    pub kind: String,
955    pub schema_version: String,
956    pub sequence: u64,
957    pub request_id: String,
958    pub execution_id: String,
959    pub status: BrowserRuntimeSubscriptionLifecycleStatus,
960}
961
962#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
963#[serde(rename_all = "snake_case")]
964pub enum BrowserRuntimeSubscriptionLifecycleStatus {
965    SubscriptionEstablished,
966    StreamCompleted,
967}
968
969#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
970pub struct BrowserRuntimeSubscriptionStateMessage {
971    pub kind: String,
972    pub schema_version: String,
973    pub sequence: u64,
974    pub state_event: RuntimeStateEvent,
975}
976
977#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
978pub struct BrowserRuntimeSubscriptionTraceArtifactMessage {
979    pub kind: String,
980    pub schema_version: String,
981    pub sequence: u64,
982    pub trace: RuntimeTrace,
983}
984
985#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
986pub struct BrowserRuntimeSubscriptionTerminalMessage {
987    pub kind: String,
988    pub schema_version: String,
989    pub sequence: u64,
990    pub result: RuntimeResult,
991}
992
993#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
994pub enum BrowserRuntimeSubscriptionMessage {
995    Error(BrowserRuntimeSubscriptionErrorMessage),
996    Lifecycle(Box<BrowserRuntimeSubscriptionLifecycleMessage>),
997    State(Box<BrowserRuntimeSubscriptionStateMessage>),
998    TraceArtifact(Box<BrowserRuntimeSubscriptionTraceArtifactMessage>),
999    StreamTerminal(Box<BrowserRuntimeSubscriptionTerminalMessage>),
1000}
1001
1002#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1003#[serde(rename_all = "snake_case")]
1004pub enum RuntimeRegistryScope {
1005    Public,
1006    Private,
1007}
1008
1009#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1010#[serde(rename_all = "snake_case")]
1011pub enum RuntimeImplementationKind {
1012    Executable,
1013    Workflow,
1014}
1015
1016#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1017#[serde(rename_all = "snake_case")]
1018pub enum RuntimeLifecycle {
1019    Draft,
1020    Active,
1021    Deprecated,
1022    Retired,
1023    Archived,
1024}
1025
1026#[derive(Debug, Clone, PartialEq, Eq)]
1027pub struct RequestParseFailure {
1028    pub message: String,
1029}
1030
1031impl fmt::Display for RequestParseFailure {
1032    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1033        formatter.write_str(&self.message)
1034    }
1035}
1036
1037impl std::error::Error for RequestParseFailure {}
1038
1039/// Parses a runtime request from raw JSON text.
1040///
1041/// # Errors
1042///
1043/// Returns [`RequestParseFailure`] when the JSON payload cannot be
1044/// deserialized into the runtime request model.
1045pub fn parse_runtime_request(json: &str) -> Result<RuntimeRequest, RequestParseFailure> {
1046    serde_json::from_str::<RuntimeRequest>(json).map_err(|error| RequestParseFailure {
1047        message: error.to_string(),
1048    })
1049}
1050
1051#[must_use]
1052pub fn browser_subscription_messages(
1053    request: &BrowserRuntimeSubscriptionRequest,
1054    outcome: &RuntimeExecutionOutcome,
1055) -> Vec<BrowserRuntimeSubscriptionMessage> {
1056    if let Some(error) = validate_browser_subscription_request(request) {
1057        return vec![BrowserRuntimeSubscriptionMessage::Error(error)];
1058    }
1059
1060    if !subscription_targets_outcome(request, outcome) {
1061        return vec![BrowserRuntimeSubscriptionMessage::Error(
1062            BrowserRuntimeSubscriptionErrorMessage {
1063                kind: BROWSER_SUBSCRIPTION_ERROR_KIND.to_string(),
1064                schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1065                sequence: 0,
1066                code: BrowserRuntimeSubscriptionErrorCode::NotFound,
1067                message: "subscription target did not match the supplied execution outcome"
1068                    .to_string(),
1069            },
1070        )];
1071    }
1072
1073    let mut sequence = 0_u64;
1074    let mut messages = Vec::new();
1075    messages.push(BrowserRuntimeSubscriptionMessage::Lifecycle(Box::new(
1076        BrowserRuntimeSubscriptionLifecycleMessage {
1077            kind: BROWSER_SUBSCRIPTION_LIFECYCLE_KIND.to_string(),
1078            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1079            sequence,
1080            request_id: outcome.result.request_id.clone(),
1081            execution_id: outcome.result.execution_id.clone(),
1082            status: BrowserRuntimeSubscriptionLifecycleStatus::SubscriptionEstablished,
1083        },
1084    )));
1085    sequence += 1;
1086
1087    for state_event in &outcome.state_events {
1088        messages.push(BrowserRuntimeSubscriptionMessage::State(Box::new(
1089            BrowserRuntimeSubscriptionStateMessage {
1090                kind: BROWSER_SUBSCRIPTION_STATE_KIND.to_string(),
1091                schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1092                sequence,
1093                state_event: state_event.clone(),
1094            },
1095        )));
1096        sequence += 1;
1097    }
1098
1099    messages.push(BrowserRuntimeSubscriptionMessage::TraceArtifact(Box::new(
1100        BrowserRuntimeSubscriptionTraceArtifactMessage {
1101            kind: BROWSER_SUBSCRIPTION_TRACE_KIND.to_string(),
1102            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1103            sequence,
1104            trace: outcome.trace.clone(),
1105        },
1106    )));
1107    sequence += 1;
1108
1109    messages.push(BrowserRuntimeSubscriptionMessage::StreamTerminal(Box::new(
1110        BrowserRuntimeSubscriptionTerminalMessage {
1111            kind: BROWSER_SUBSCRIPTION_TERMINAL_KIND.to_string(),
1112            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1113            sequence,
1114            result: outcome.result.clone(),
1115        },
1116    )));
1117    sequence += 1;
1118
1119    messages.push(BrowserRuntimeSubscriptionMessage::Lifecycle(Box::new(
1120        BrowserRuntimeSubscriptionLifecycleMessage {
1121            kind: BROWSER_SUBSCRIPTION_LIFECYCLE_KIND.to_string(),
1122            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1123            sequence,
1124            request_id: outcome.result.request_id.clone(),
1125            execution_id: outcome.result.execution_id.clone(),
1126            status: BrowserRuntimeSubscriptionLifecycleStatus::StreamCompleted,
1127        },
1128    )));
1129
1130    messages
1131}
1132
1133fn validate_browser_subscription_request(
1134    request: &BrowserRuntimeSubscriptionRequest,
1135) -> Option<BrowserRuntimeSubscriptionErrorMessage> {
1136    if request.kind != BROWSER_SUBSCRIPTION_REQUEST_KIND {
1137        return Some(browser_subscription_error(
1138            BrowserRuntimeSubscriptionErrorCode::InvalidRequest,
1139            "kind must equal browser_runtime_subscription_request",
1140        ));
1141    }
1142    if request.schema_version != SUPPORTED_SCHEMA_VERSION {
1143        return Some(browser_subscription_error(
1144            BrowserRuntimeSubscriptionErrorCode::InvalidRequest,
1145            "schema_version must equal 1.0.0",
1146        ));
1147    }
1148    if request.governing_spec != BROWSER_SUBSCRIPTION_GOVERNING_SPEC {
1149        return Some(browser_subscription_error(
1150            BrowserRuntimeSubscriptionErrorCode::InvalidRequest,
1151            "governing_spec must equal 013-browser-runtime-subscription",
1152        ));
1153    }
1154
1155    match (&request.request_id, &request.execution_id) {
1156        (Some(request_id), None) if non_empty(request_id) => None,
1157        (None, Some(execution_id)) if non_empty(execution_id) => None,
1158        (Some(_), Some(_)) => Some(browser_subscription_error(
1159            BrowserRuntimeSubscriptionErrorCode::InvalidRequest,
1160            "exactly one target selector must be supplied",
1161        )),
1162        _ => Some(browser_subscription_error(
1163            BrowserRuntimeSubscriptionErrorCode::InvalidRequest,
1164            "subscription request must include request_id or execution_id",
1165        )),
1166    }
1167}
1168
1169fn subscription_targets_outcome(
1170    request: &BrowserRuntimeSubscriptionRequest,
1171    outcome: &RuntimeExecutionOutcome,
1172) -> bool {
1173    match (&request.request_id, &request.execution_id) {
1174        (Some(request_id), None) => request_id == &outcome.result.request_id,
1175        (None, Some(execution_id)) => execution_id == &outcome.result.execution_id,
1176        _ => false,
1177    }
1178}
1179
1180fn browser_subscription_error(
1181    code: BrowserRuntimeSubscriptionErrorCode,
1182    message: &str,
1183) -> BrowserRuntimeSubscriptionErrorMessage {
1184    BrowserRuntimeSubscriptionErrorMessage {
1185        kind: BROWSER_SUBSCRIPTION_ERROR_KIND.to_string(),
1186        schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1187        sequence: 0,
1188        code,
1189        message: message.to_string(),
1190    }
1191}
1192
1193impl<E> Runtime<E>
1194where
1195    E: LocalExecutor,
1196{
1197    /// Executes one runtime request against the current registry state.
1198    #[must_use]
1199    pub fn execute(&self, request: RuntimeRequest) -> RuntimeExecutionOutcome {
1200        let identity = request.context.identity.clone();
1201        let (attempt, mut emitter) = begin_attempt(request, self.observability.clone());
1202        emitter.push(
1203            RuntimeState::Discovering,
1204            RuntimeTransitionReasonCode::RequestStarted,
1205            json!({
1206                "lookup_scope": attempt.request.lookup.scope,
1207                "identity": attempt.request.context.identity,
1208            }),
1209        );
1210
1211        let mut outcome = if let Some(error) = validate_request(&attempt.request) {
1212            invalid_request_outcome(attempt, emitter, error)
1213        } else {
1214            let resolution = self.resolve_candidates(&attempt.request, &mut emitter);
1215
1216            if resolution.eligible.is_empty() {
1217                no_eligible_outcome(attempt, emitter, resolution.collection)
1218            } else if resolution.eligible.len() > 1 {
1219                ambiguous_outcome(attempt, emitter, resolution)
1220            } else {
1221                let mut eligible = resolution.eligible;
1222                let selected = eligible.remove(0);
1223                let selection = SelectionRecord {
1224                    status: SelectionStatus::Selected,
1225                    selected_capability_id: Some(selected.record.id.clone()),
1226                    selected_capability_version: Some(selected.record.version.clone()),
1227                    failure_reason: None,
1228                    remaining_candidates: Vec::new(),
1229                };
1230
1231                self.execute_selected(
1232                    attempt,
1233                    emitter,
1234                    resolution.collection,
1235                    selection,
1236                    &selected,
1237                )
1238            }
1239        };
1240
1241        self.emit_execution_lifecycle_event(&mut outcome, identity.as_ref());
1242        outcome
1243    }
1244
1245    fn emit_execution_lifecycle_event(
1246        &self,
1247        outcome: &mut RuntimeExecutionOutcome,
1248        identity: Option<&RuntimeIdentity>,
1249    ) {
1250        let event = TraverseEvent {
1251            id: Uuid::new_v4().to_string(),
1252            source: "traverse-runtime".to_string(),
1253            event_type: RUNTIME_EXECUTION_EVENT_TYPE.to_string(),
1254            datacontenttype: "application/json".to_string(),
1255            time: Utc::now().to_rfc3339(),
1256            data: json!({
1257                "execution_id": outcome.result.execution_id,
1258                "request_id": outcome.result.request_id,
1259                "status": outcome.result.status,
1260                "trace_ref": outcome.result.trace_ref,
1261            }),
1262            owner: "traverse-runtime".to_string(),
1263            version: SUPPORTED_SCHEMA_VERSION.to_string(),
1264            lifecycle_status: events::LifecycleStatus::Active,
1265            deduplication_id: Some(outcome.result.execution_id.clone()),
1266            ordering_scope: Some(outcome.result.request_id.clone()),
1267            correlation_id: Some(outcome.result.request_id.clone()),
1268            causation_id: Some(outcome.result.request_id.clone()),
1269            subject_id: identity.map(|identity| identity.subject_id.clone()),
1270            actor_id: identity.and_then(|identity| identity.actor_id.clone()),
1271        };
1272
1273        if let Err(error) = self.event_sink.emit(event) {
1274            let warning = RuntimeWarning {
1275                code: "runtime_event_sink_delivery_failed".to_string(),
1276                message: error.to_string(),
1277            };
1278            outcome.result.warnings.push(warning.clone());
1279            outcome.trace.result.warnings.push(warning);
1280        }
1281    }
1282
1283    fn collect_candidates(
1284        &self,
1285        request: &RuntimeRequest,
1286        _reason: CandidateReason,
1287    ) -> Vec<ResolvedCapability> {
1288        let lookup_scope = map_lookup_scope(request.lookup.scope);
1289
1290        // Exact version lookup — highest priority.
1291        if is_exact_target(&request.intent) {
1292            return request
1293                .intent
1294                .capability_id
1295                .as_deref()
1296                .zip(request.intent.capability_version.as_deref())
1297                .and_then(|(id, version)| self.registry.find_exact(lookup_scope, id, version))
1298                .into_iter()
1299                .collect();
1300        }
1301
1302        // Semver range lookup — when capability_id + version_range are non-empty.
1303        if let (Some(capability_id), Some(range_str)) = (
1304            request.intent.capability_id.as_deref(),
1305            request.intent.version_range.as_deref(),
1306        ) && non_empty(capability_id)
1307            && non_empty(range_str)
1308        {
1309            return match resolve_version_range(
1310                &self.registry,
1311                capability_id,
1312                range_str,
1313                lookup_scope,
1314            ) {
1315                Ok(resolved) => {
1316                    let entry_lookup = match resolved.scope {
1317                        RegistryScope::Public => LookupScope::PublicOnly,
1318                        RegistryScope::Private => LookupScope::PreferPrivate,
1319                    };
1320                    self.registry
1321                        .find_exact(entry_lookup, &resolved.capability_id, &resolved.version)
1322                        .into_iter()
1323                        .collect()
1324                }
1325                Err(_) => Vec::new(),
1326            };
1327        }
1328
1329        // Intent/discovery lookup — fallback.
1330        let target = request
1331            .intent
1332            .capability_id
1333            .as_deref()
1334            .or(request.intent.intent_key.as_deref())
1335            .unwrap_or_default();
1336
1337        self.registry
1338            .discover(lookup_scope, &DiscoveryQuery::default())
1339            .into_iter()
1340            .filter(|entry| entry.id == target)
1341            .filter_map(|entry| {
1342                let scope = match entry.scope {
1343                    traverse_registry::RegistryScope::Public => LookupScope::PublicOnly,
1344                    traverse_registry::RegistryScope::Private => LookupScope::PreferPrivate,
1345                };
1346                self.registry.find_exact(scope, &entry.id, &entry.version)
1347            })
1348            .collect()
1349    }
1350
1351    fn resolve_candidates(
1352        &self,
1353        request: &RuntimeRequest,
1354        emitter: &mut StateEmitter,
1355    ) -> CandidateResolution {
1356        let candidate_reason = if is_exact_target(&request.intent) {
1357            CandidateReason::ExactMatch
1358        } else {
1359            CandidateReason::IntentMatch
1360        };
1361
1362        let discovered = self.collect_candidates(request, candidate_reason);
1363        if !discovered.is_empty() {
1364            emitter.push(
1365                RuntimeState::EvaluatingConstraints,
1366                RuntimeTransitionReasonCode::CandidatesCollected,
1367                json!({"candidate_count": discovered.len()}),
1368            );
1369        }
1370
1371        let mut eligible = Vec::new();
1372        let mut rejected = Vec::new();
1373        for candidate in discovered {
1374            match evaluate_candidate(candidate) {
1375                CandidateEvaluation::Eligible(capability) => eligible.push(capability),
1376                CandidateEvaluation::Rejected(candidate, reason) => {
1377                    rejected.push(RejectedRuntimeCandidate {
1378                        capability_id: candidate.record.id.clone(),
1379                        capability_version: candidate.record.version.clone(),
1380                        scope: map_registry_scope(candidate.record.scope),
1381                        reason,
1382                    });
1383                }
1384            }
1385        }
1386
1387        if !eligible.is_empty() {
1388            emitter.push(
1389                RuntimeState::Selecting,
1390                RuntimeTransitionReasonCode::ConstraintsEvaluated,
1391                json!({
1392                    "eligible_candidates": eligible.len(),
1393                    "rejected_candidates": rejected.len()
1394                }),
1395            );
1396        }
1397
1398        CandidateResolution {
1399            eligible: eligible.clone(),
1400            collection: CandidateCollectionRecord {
1401                lookup_scope: request.lookup.scope,
1402                candidates: eligible
1403                    .iter()
1404                    .map(|capability| runtime_candidate(capability, candidate_reason))
1405                    .collect(),
1406                rejected_candidates: rejected,
1407            },
1408            candidate_reason,
1409        }
1410    }
1411
1412    #[allow(clippy::too_many_lines)]
1413    fn execute_selected(
1414        &self,
1415        attempt: AttemptContext,
1416        emitter: StateEmitter,
1417        candidate_collection: CandidateCollectionRecord,
1418        selection: SelectionRecord,
1419        selected: &ResolvedCapability,
1420    ) -> RuntimeExecutionOutcome {
1421        let mut context = ExecutionContext {
1422            attempt,
1423            emitter,
1424            candidate_collection,
1425            selection,
1426        };
1427        let requested_target = context.attempt.request.context.requested_target;
1428
1429        if contains_drafts_segment(&selected.record.contract_path) {
1430            let violation = ViolationRecord::new(
1431                "draft_artifact_not_executable",
1432                selected.record.contract_path.clone(),
1433                "draft artifacts are quarantined under drafts/ and must not be executable",
1434            );
1435            let error = runtime_error(
1436                RuntimeErrorCode::ContractViolation,
1437                "draft artifacts are not executable",
1438                json!({"violations": [violation]}),
1439            );
1440            return pre_execution_failure_outcome(
1441                context,
1442                PreExecutionFailure {
1443                    artifact_ref: Some(selected.record.artifact_ref.clone()),
1444                    failure_reason: ExecutionFailureReason::ArtifactNotRunnable,
1445                    placement: placement_not_attempted(
1446                        requested_target,
1447                        PlacementDecisionReason::SelectionNotReached,
1448                    ),
1449                    error,
1450                    artifact_verification: None,
1451                },
1452            );
1453        }
1454
1455        let placement = match resolve_placement(requested_target) {
1456            Ok(placement) => placement,
1457            Err(error) => {
1458                return pre_execution_failure_outcome(
1459                    context,
1460                    PreExecutionFailure {
1461                        artifact_ref: Some(selected.record.artifact_ref.clone()),
1462                        failure_reason: ExecutionFailureReason::PlacementUnsupported,
1463                        placement: placement_not_attempted(
1464                            requested_target,
1465                            PlacementDecisionReason::RequestedTargetUnsupported,
1466                        ),
1467                        error,
1468                        artifact_verification: None,
1469                    },
1470                );
1471            }
1472        };
1473
1474        let artifact_bytes = match load_artifact_bytes_for_verification(selected) {
1475            Ok(bytes) => bytes,
1476            Err(error) => {
1477                return pre_execution_failure_outcome(
1478                    context,
1479                    PreExecutionFailure {
1480                        artifact_ref: Some(selected.record.artifact_ref.clone()),
1481                        failure_reason: ExecutionFailureReason::ArtifactMissing,
1482                        placement,
1483                        error,
1484                        artifact_verification: None,
1485                    },
1486                );
1487            }
1488        };
1489
1490        match verify_artifact(selected, &artifact_bytes, &self.security) {
1491            Ok(record) => {
1492                if record.warning_code.is_some() {
1493                    context.attempt.warnings.push(RuntimeWarning {
1494                        code: record.warning_code.clone().unwrap_or_default(),
1495                        message: "unsigned local/dev artifact allowed by development security mode"
1496                            .to_string(),
1497                    });
1498                }
1499                context.attempt.artifact_verification = Some(record);
1500            }
1501            Err(error) => {
1502                let record = error.record().clone();
1503                let runtime_error = artifact_verification_runtime_error(&error);
1504                return pre_execution_failure_outcome(
1505                    context,
1506                    PreExecutionFailure {
1507                        artifact_ref: Some(selected.record.artifact_ref.clone()),
1508                        failure_reason: ExecutionFailureReason::ArtifactNotRunnable,
1509                        placement,
1510                        error: runtime_error,
1511                        artifact_verification: Some(record),
1512                    },
1513                );
1514            }
1515        }
1516
1517        // Dependency resolution gate (spec 043): resolve and verify all
1518        // Capability-typed dependencies before executing.
1519        let lookup_scope = map_lookup_scope(context.attempt.request.lookup.scope);
1520        if let Err(dep_error) = resolve_dependencies(
1521            &self.registry,
1522            &selected.record.id,
1523            &selected.contract.dependencies,
1524            lookup_scope,
1525        ) {
1526            let (detail_id, detail_version) = match &dep_error {
1527                ResolutionError::MissingDependency {
1528                    capability_id,
1529                    required_version,
1530                } => (capability_id.clone(), required_version.clone()),
1531                ResolutionError::CircularDependency { cycle } => {
1532                    (cycle.join(" -> "), String::new())
1533                }
1534                ResolutionError::MaxTransitiveDepthExceeded { depth, chain } => {
1535                    (format!("depth={depth}"), chain.join(" -> "))
1536                }
1537            };
1538            let error = runtime_error(
1539                RuntimeErrorCode::CapabilityNotFound,
1540                "dependency resolution failed before execution",
1541                serde_json::json!({
1542                    "dependency_id": detail_id,
1543                    "required_version": detail_version,
1544                }),
1545            );
1546            let artifact_verification = context.attempt.artifact_verification.clone();
1547            return pre_execution_failure_outcome(
1548                context,
1549                PreExecutionFailure {
1550                    artifact_ref: Some(selected.record.artifact_ref.clone()),
1551                    failure_reason: ExecutionFailureReason::ArtifactMissing,
1552                    placement,
1553                    error,
1554                    artifact_verification,
1555                },
1556            );
1557        }
1558
1559        if let Err(error) = validate_payload_against_contract(
1560            &context.attempt.request.input,
1561            &selected.contract.inputs.schema,
1562            RuntimeErrorCode::RequestInvalid,
1563            "runtime request input does not satisfy the selected capability input contract",
1564        ) {
1565            let artifact_verification = context.attempt.artifact_verification.clone();
1566            return pre_execution_failure_outcome(
1567                context,
1568                PreExecutionFailure {
1569                    artifact_ref: Some(selected.record.artifact_ref.clone()),
1570                    failure_reason: ExecutionFailureReason::ContractInputInvalid,
1571                    placement,
1572                    error,
1573                    artifact_verification,
1574                },
1575            );
1576        }
1577
1578        self.execute_started_selection(context, selected, placement)
1579    }
1580
1581    fn execute_started_selection(
1582        &self,
1583        mut context: ExecutionContext,
1584        selected: &ResolvedCapability,
1585        placement: PlacementDecisionRecord,
1586    ) -> RuntimeExecutionOutcome {
1587        let identity = context.attempt.request.context.identity.clone();
1588        let started_execution =
1589            start_selected_execution(&mut context.emitter, selected, placement, identity.as_ref());
1590        if selected.record.implementation_kind == ImplementationKind::Workflow {
1591            return self.execute_workflow_capability(context, selected, started_execution);
1592        }
1593
1594        let artifact_type = artifact_type_for(selected);
1595        let executor_capability = executor_capability_for(selected, artifact_type.clone());
1596        let bridge = BoundLocalExecutor {
1597            executor: Arc::clone(&self.executor),
1598            selected: selected.clone(),
1599        };
1600
1601        let router = PlacementRouter::new(
1602            PlacementConstraintEvaluator,
1603            CapabilityExecutorRegistry::new(),
1604            Arc::clone(&self.trace_store),
1605            Arc::clone(&self.event_broker),
1606        );
1607
1608        let target_hint = execution_target_from_placement(
1609            started_execution
1610                .placement
1611                .selected_target
1612                .unwrap_or(started_execution.placement.requested_target),
1613        );
1614
1615        let router_request = RouterRequest {
1616            capability_id: selected.record.id.clone(),
1617            artifact_type,
1618            contract: selected.contract.clone(),
1619            target_hint: Some(target_hint),
1620            runtime_snapshot: idle_runtime_snapshot(),
1621            input: context.attempt.request.input.clone(),
1622            executor_capability,
1623            trace_id_override: Some(context.attempt.trace_id.clone()),
1624        };
1625
1626        let router_result = router.execute_with_executor(router_request, &bridge);
1627        match router_result {
1628            Ok(response) => {
1629                if let Err(error) = validate_payload_against_contract(
1630                    &response.output,
1631                    &selected.contract.outputs.schema,
1632                    RuntimeErrorCode::OutputValidationFailed,
1633                    "executor output does not satisfy the selected capability output contract",
1634                ) {
1635                    return execution_failure_outcome(
1636                        context,
1637                        ExecutionFailureState {
1638                            artifact_ref: selected.record.artifact_ref.clone(),
1639                            started_at: started_execution.started_at,
1640                            placement: started_execution.placement,
1641                            failure_reason: ExecutionFailureReason::ContractOutputInvalid,
1642                        },
1643                        error,
1644                        Vec::new(),
1645                        None,
1646                    );
1647                }
1648
1649                // `BoundLocalExecutor` now threads a `LocalExecutor`'s real
1650                // `emitted_events` into `ExecutorOutput` (spec
1651                // 101-local-executor-event-emission FR-002), already
1652                // validated and published by `PlacementRouter` Step 5, so
1653                // `response.emitted_events` carries the real events here.
1654                let emitted_events: Vec<EventReference> = response
1655                    .emitted_events
1656                    .iter()
1657                    .map(|event| EventReference {
1658                        event_id: event.event_type.clone(),
1659                        version: event.version.clone(),
1660                    })
1661                    .collect();
1662                successful_execution_outcome(
1663                    context,
1664                    selected,
1665                    started_execution,
1666                    response.output,
1667                    emitted_events,
1668                    None,
1669                )
1670            }
1671            Err(error) => {
1672                let (runtime_error, failure_reason, emitted_events) = map_router_error(&error);
1673                execution_failure_outcome(
1674                    context,
1675                    ExecutionFailureState {
1676                        artifact_ref: selected.record.artifact_ref.clone(),
1677                        started_at: started_execution.started_at,
1678                        placement: started_execution.placement,
1679                        failure_reason,
1680                    },
1681                    runtime_error,
1682                    emitted_events,
1683                    None,
1684                )
1685            }
1686        }
1687    }
1688}
1689
1690/// Bridges the host [`LocalExecutor`] into a [`CapabilityExecutor`] for one selected capability.
1691struct BoundLocalExecutor<E> {
1692    executor: Arc<E>,
1693    selected: ResolvedCapability,
1694}
1695
1696impl<E> CapabilityExecutor for BoundLocalExecutor<E>
1697where
1698    E: LocalExecutor,
1699{
1700    fn execute(
1701        &self,
1702        _capability: &ExecutorCapability,
1703        input: &Value,
1704    ) -> Result<ExecutorOutput, ExecutorError> {
1705        let output = self
1706            .executor
1707            .execute(&self.selected, input)
1708            .map_err(|failure| ExecutorError::ExecutionFailed(failure.message))?;
1709        validate_natively_emitted_events(&self.selected.contract, &output.emitted_events)
1710            .map_err(ExecutorError::ExecutionFailed)?;
1711        Ok(ExecutorOutput {
1712            value: output.value,
1713            emitted_events: output.emitted_events,
1714        })
1715    }
1716}
1717
1718/// Validates events a native [`LocalExecutor`] implementor populated
1719/// directly (not via the WASM `traverse_host::emit_event` ABI, which
1720/// validates synchronously at call time) against the executing capability
1721/// contract's `emits` list and `service_type`, mirroring spec
1722/// `098-capability-event-host-abi`'s WASM-boundary checks (spec
1723/// `101-local-executor-event-emission` FR-007/FR-008). Runs after the
1724/// native closure has already returned — there is no host-function call
1725/// boundary to reject mid-call the way WASM has — but always before any
1726/// publish to [`EventBroker`].
1727fn validate_natively_emitted_events(
1728    contract: &traverse_contracts::CapabilityContract,
1729    emitted_events: &[TraverseEvent],
1730) -> Result<(), String> {
1731    if emitted_events.is_empty() {
1732        return Ok(());
1733    }
1734    if contract.service_type != ServiceType::Subscribable {
1735        return Err(format!(
1736            "capability '{}' emitted events but its service_type is not Subscribable",
1737            contract.id
1738        ));
1739    }
1740    for event in emitted_events {
1741        let declared = contract
1742            .emits
1743            .iter()
1744            .any(|decl| decl.event_id == event.event_type && decl.version == event.version);
1745        if !declared {
1746            return Err(format!(
1747                "capability '{}' emitted an undeclared event {}@{}",
1748                contract.id, event.event_type, event.version
1749            ));
1750        }
1751    }
1752    Ok(())
1753}
1754
1755/// Best-effort broker used only when the default in-process broker cannot be constructed.
1756#[derive(Debug, Default)]
1757struct DiscardEventBroker;
1758
1759impl EventBroker for DiscardEventBroker {
1760    fn publish(&self, _event: TraverseEvent) -> Result<(), EventError> {
1761        Ok(())
1762    }
1763
1764    fn subscribe(&self, event_type: &str, _from_cursor: &str) -> Result<Subscription, EventError> {
1765        Err(EventError::UnregisteredEventType(event_type.to_string()))
1766    }
1767
1768    fn subscribe_for_subject(
1769        &self,
1770        event_type: &str,
1771        _from_cursor: &str,
1772        _subject_id: Option<&str>,
1773    ) -> Result<Subscription, EventError> {
1774        Err(EventError::UnregisteredEventType(event_type.to_string()))
1775    }
1776
1777    fn poll(
1778        &self,
1779        subscription_id: &str,
1780        _max_events: usize,
1781    ) -> Result<SubscriptionPoll, EventError> {
1782        Err(EventError::SubscriptionNotFound(
1783            subscription_id.to_string(),
1784        ))
1785    }
1786
1787    fn cancel(&self, subscription_id: &str) -> Result<(), EventError> {
1788        Err(EventError::SubscriptionNotFound(
1789            subscription_id.to_string(),
1790        ))
1791    }
1792}
1793
1794fn default_event_broker() -> Arc<dyn EventBroker> {
1795    event_broker_or_discard(InProcessBroker::new(Arc::new(EventCatalog::new())))
1796}
1797
1798fn event_broker_or_discard(result: Result<InProcessBroker, EventError>) -> Arc<dyn EventBroker> {
1799    match result {
1800        Ok(broker) => Arc::new(broker),
1801        Err(_) => Arc::new(DiscardEventBroker),
1802    }
1803}
1804
1805fn idle_runtime_snapshot() -> RuntimeSnapshot {
1806    RuntimeSnapshot {
1807        target_loads: [
1808            (ExecutionTarget::Local, 0.0),
1809            (ExecutionTarget::Browser, 0.0),
1810            (ExecutionTarget::Edge, 0.0),
1811            (ExecutionTarget::Cloud, 0.0),
1812            (ExecutionTarget::Worker, 0.0),
1813            (ExecutionTarget::Device, 0.0),
1814        ]
1815        .into_iter()
1816        .collect(),
1817    }
1818}
1819
1820fn artifact_type_for(selected: &ResolvedCapability) -> ArtifactType {
1821    if selected.artifact.binary.is_some() {
1822        ArtifactType::Wasm
1823    } else {
1824        ArtifactType::Native
1825    }
1826}
1827
1828fn executor_capability_for(
1829    selected: &ResolvedCapability,
1830    artifact_type: ArtifactType,
1831) -> ExecutorCapability {
1832    let binary = selected.artifact.binary.as_ref();
1833    ExecutorCapability {
1834        capability_id: selected.contract.id.clone(),
1835        artifact_type,
1836        wasm_binary_path: binary.map(|binary| binary.location.clone()),
1837        wasm_checksum: selected
1838            .artifact
1839            .digests
1840            .binary_digest
1841            .as_deref()
1842            .and_then(|digest| digest.strip_prefix("sha256:"))
1843            .map(str::to_string),
1844        host_abi_version: None,
1845        emits: selected.contract.emits.clone(),
1846        service_type: selected.contract.service_type.clone(),
1847    }
1848}
1849
1850fn execution_target_from_placement(target: PlacementTarget) -> ExecutionTarget {
1851    match target {
1852        PlacementTarget::Local => ExecutionTarget::Local,
1853        PlacementTarget::Browser => ExecutionTarget::Browser,
1854        PlacementTarget::Edge => ExecutionTarget::Edge,
1855        PlacementTarget::Cloud => ExecutionTarget::Cloud,
1856        PlacementTarget::Worker => ExecutionTarget::Worker,
1857        PlacementTarget::Device => ExecutionTarget::Device,
1858    }
1859}
1860
1861fn map_router_error(
1862    error: &RouterError,
1863) -> (RuntimeError, ExecutionFailureReason, Vec<EventReference>) {
1864    match error {
1865        RouterError::PlacementFailed(placement_error) => (
1866            runtime_error(
1867                RuntimeErrorCode::PlacementUnsupported,
1868                "placement constraints rejected the capability execution request",
1869                json!({"placement_error": format!("{placement_error:?}")}),
1870            ),
1871            ExecutionFailureReason::PlacementUnsupported,
1872            Vec::new(),
1873        ),
1874        RouterError::ExecutorNotFound(artifact_type) => (
1875            runtime_error(
1876                RuntimeErrorCode::CapabilityNotRunnable,
1877                "no executor is registered for the capability artifact type",
1878                json!({"artifact_type": artifact_type}),
1879            ),
1880            ExecutionFailureReason::ArtifactNotRunnable,
1881            Vec::new(),
1882        ),
1883        RouterError::ExecutionFailed(message) => (
1884            runtime_error(
1885                RuntimeErrorCode::ExecutionFailed,
1886                message,
1887                json!({"code": "execution_failed"}),
1888            ),
1889            ExecutionFailureReason::ExecutionFailed,
1890            Vec::new(),
1891        ),
1892        RouterError::ContractViolation(violations) => (
1893            runtime_error(
1894                RuntimeErrorCode::ContractViolation,
1895                "capability execution violated its governed contract",
1896                json!({"violations": violations}),
1897            ),
1898            ExecutionFailureReason::ExecutionFailed,
1899            Vec::new(),
1900        ),
1901        RouterError::TraceLockPoisoned => (
1902            runtime_error(
1903                RuntimeErrorCode::ExecutionFailed,
1904                "trace store lock is poisoned",
1905                json!({"code": "trace_lock_poisoned"}),
1906            ),
1907            ExecutionFailureReason::ExecutionFailed,
1908            Vec::new(),
1909        ),
1910    }
1911}
1912
1913fn terminal_failure(context: FailureContext) -> RuntimeExecutionOutcome {
1914    let result_record = TraceResultRecord {
1915        status: RuntimeResultStatus::Error,
1916        output: None,
1917        error: Some(context.error.clone()),
1918        warnings: context.attempt.warnings.clone(),
1919    };
1920    let otel_trace = otel_trace_record(
1921        &context.attempt,
1922        &context.state_transitions,
1923        &context.selection,
1924        &context.execution,
1925        &result_record,
1926    );
1927    let trace = RuntimeTrace {
1928        kind: RUNTIME_TRACE_KIND.to_string(),
1929        schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1930        trace_id: context.attempt.trace_id.clone(),
1931        execution_id: context.attempt.execution_id.clone(),
1932        request_id: context.attempt.request.request_id.clone(),
1933        governing_spec: GOVERNING_SPEC.to_string(),
1934        request: context.attempt.request.clone(),
1935        decision_evidence: TraceDecisionEvidence {
1936            candidate_collection: context.candidate_collection.clone(),
1937            selection: context.selection.clone(),
1938            model_resolution: Vec::new(),
1939        },
1940        state_progression: TraceStateProgression {
1941            state_events: context.state_events.clone(),
1942            transitions: context.state_transitions.clone(),
1943            validation: context.state_machine_validation.clone(),
1944        },
1945        terminal_outcome: TraceTerminalOutcome {
1946            runtime_status: RuntimeResultStatus::Error,
1947            execution_status: context.execution.status,
1948            failure_reason: context.execution.failure_reason,
1949            error: Some(context.error.clone()),
1950        },
1951        emitted_events: context.emitted_events,
1952        workflow_evidence: context.workflow_evidence,
1953        model_resolution: Vec::new(),
1954        state_transitions: context.state_transitions,
1955        state_machine_validation: context.state_machine_validation,
1956        candidate_collection: context.candidate_collection,
1957        selection: context.selection,
1958        execution: context.execution,
1959        result: result_record,
1960        otel_trace,
1961    };
1962
1963    let result = RuntimeResult {
1964        kind: RUNTIME_RESULT_KIND.to_string(),
1965        schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
1966        execution_id: context.attempt.execution_id,
1967        request_id: context.attempt.request.request_id,
1968        status: RuntimeResultStatus::Error,
1969        trace_ref: context.attempt.trace_id,
1970        output: None,
1971        error: Some(context.error),
1972        warnings: context.attempt.warnings,
1973    };
1974
1975    RuntimeExecutionOutcome {
1976        result,
1977        trace,
1978        state_events: context.state_events,
1979    }
1980}
1981
1982fn supported_executor_targets() -> Vec<PlacementTarget> {
1983    vec![PlacementTarget::Local]
1984}
1985
1986fn placement_not_attempted(
1987    requested_target: PlacementTarget,
1988    reason: PlacementDecisionReason,
1989) -> PlacementDecisionRecord {
1990    PlacementDecisionRecord {
1991        requested_target,
1992        selected_target: None,
1993        status: PlacementDecisionStatus::NotAttempted,
1994        reason,
1995        supported_executor_targets: supported_executor_targets(),
1996    }
1997}
1998
1999fn resolve_placement(
2000    requested_target: PlacementTarget,
2001) -> Result<PlacementDecisionRecord, RuntimeError> {
2002    if requested_target == PlacementTarget::Local {
2003        return Ok(PlacementDecisionRecord {
2004            requested_target,
2005            selected_target: Some(PlacementTarget::Local),
2006            status: PlacementDecisionStatus::Selected,
2007            reason: PlacementDecisionReason::RequestedTargetSelected,
2008            supported_executor_targets: supported_executor_targets(),
2009        });
2010    }
2011
2012    Err(runtime_error(
2013        RuntimeErrorCode::PlacementUnsupported,
2014        "requested placement target is not supported by the available executor set",
2015        json!({
2016            "requested_target": requested_target,
2017            "supported_executor_targets": supported_executor_targets(),
2018        }),
2019    ))
2020}
2021
2022fn sanitized_request(mut request: RuntimeRequest) -> RuntimeRequest {
2023    if let Some(token) = request.context.caller.take() {
2024        if let Some(identity) = derive_identity_from_jwt(&token) {
2025            request.context.identity = Some(identity);
2026        } else {
2027            request.context.caller = Some(token);
2028        }
2029    }
2030    request
2031}
2032
2033fn load_artifact_bytes_for_verification(
2034    selected: &ResolvedCapability,
2035) -> Result<Vec<u8>, RuntimeError> {
2036    let Some(binary) = selected.artifact.binary.as_ref() else {
2037        return Ok(Vec::new());
2038    };
2039    match fs::read(&binary.location) {
2040        Ok(bytes) => Ok(bytes),
2041        Err(_) if binary.signature.is_none() => Ok(selected
2042            .artifact
2043            .digests
2044            .binary_digest
2045            .clone()
2046            .unwrap_or_else(|| selected.record.artifact_ref.clone())
2047            .into_bytes()),
2048        Err(error) => Err(runtime_error(
2049            RuntimeErrorCode::ArtifactMissing,
2050            "artifact bytes could not be loaded for signature verification",
2051            json!({
2052                "artifact_ref": selected.record.artifact_ref,
2053                "location": binary.location,
2054                "code": "artifact_load_failed",
2055                "message": error.to_string(),
2056            }),
2057        )),
2058    }
2059}
2060
2061fn artifact_verification_runtime_error(error: &ArtifactVerificationFailure) -> RuntimeError {
2062    runtime_error(
2063        RuntimeErrorCode::ContractViolation,
2064        "artifact signature verification failed before execution",
2065        json!({
2066            "code": error.code(),
2067            "artifact_verification": error.record(),
2068        }),
2069    )
2070}
2071
2072fn begin_attempt(
2073    request: RuntimeRequest,
2074    observability: RuntimeObservabilityConfig,
2075) -> (AttemptContext, StateEmitter) {
2076    let request = sanitized_request(request);
2077    let request_id = request.request_id.clone();
2078    let execution_id = format!("{EXECUTION_PREFIX}{request_id}");
2079    let trace_id = format!("{TRACE_PREFIX}{execution_id}");
2080    let mut emitter = StateEmitter::new(&execution_id, &request_id);
2081    emitter.push(
2082        RuntimeState::LoadingRegistry,
2083        RuntimeTransitionReasonCode::RuntimeInitializationStarted,
2084        json!({
2085            "registry_status": "available",
2086            "identity": request.context.identity,
2087        }),
2088    );
2089    emitter.push(
2090        RuntimeState::Ready,
2091        RuntimeTransitionReasonCode::RegistryLoaded,
2092        json!({"governing_spec": GOVERNING_SPEC}),
2093    );
2094
2095    (
2096        AttemptContext {
2097            request,
2098            execution_id,
2099            trace_id,
2100            observability,
2101            artifact_verification: None,
2102            warnings: Vec::new(),
2103        },
2104        emitter,
2105    )
2106}
2107
2108fn invalid_request_outcome(
2109    attempt: AttemptContext,
2110    mut emitter: StateEmitter,
2111    error: RuntimeError,
2112) -> RuntimeExecutionOutcome {
2113    let placement = placement_not_attempted(
2114        attempt.request.context.requested_target,
2115        PlacementDecisionReason::SelectionNotReached,
2116    );
2117    emitter.push(
2118        RuntimeState::EvaluatingConstraints,
2119        RuntimeTransitionReasonCode::CandidatesCollected,
2120        json!({"candidate_count": 0}),
2121    );
2122    emitter.push(
2123        RuntimeState::Error,
2124        RuntimeTransitionReasonCode::ConstraintValidationFailed,
2125        json!({"code": error.code, "message": error.message}),
2126    );
2127    emitter.push(
2128        RuntimeState::Ready,
2129        RuntimeTransitionReasonCode::ExecutionClosed,
2130        json!({"terminal_state": RuntimeState::Error}),
2131    );
2132    let finished = emitter.finish();
2133    let identity = attempt.request.context.identity.clone();
2134    terminal_failure(FailureContext {
2135        attempt,
2136        state_events: finished.events,
2137        state_transitions: finished.transitions,
2138        state_machine_validation: finished.validation,
2139        candidate_collection: CandidateCollectionRecord {
2140            lookup_scope: RuntimeLookupScope::PreferPrivate,
2141            candidates: Vec::new(),
2142            rejected_candidates: Vec::new(),
2143        },
2144        selection: SelectionRecord {
2145            status: SelectionStatus::InvalidRequest,
2146            selected_capability_id: None,
2147            selected_capability_version: None,
2148            failure_reason: Some(SelectionFailureReason::InvalidRequest),
2149            remaining_candidates: Vec::new(),
2150        },
2151        execution: ExecutionRecord {
2152            placement: placement.clone(),
2153            placement_target: placement.requested_target,
2154            status: ExecutionStatus::NotStarted,
2155            artifact_ref: None,
2156            started_at: None,
2157            completed_at: None,
2158            output_digest: None,
2159            failure_reason: Some(ExecutionFailureReason::ContractInputInvalid),
2160            artifact_verification: None,
2161            identity,
2162        },
2163        error,
2164        emitted_events: Vec::new(),
2165        workflow_evidence: None,
2166    })
2167}
2168
2169fn no_eligible_outcome(
2170    attempt: AttemptContext,
2171    mut emitter: StateEmitter,
2172    candidate_collection: CandidateCollectionRecord,
2173) -> RuntimeExecutionOutcome {
2174    let placement = placement_not_attempted(
2175        attempt.request.context.requested_target,
2176        PlacementDecisionReason::SelectionNotReached,
2177    );
2178    let error = if candidate_collection.rejected_candidates.is_empty() {
2179        runtime_error(
2180            RuntimeErrorCode::CapabilityNotFound,
2181            "no eligible capability matched the runtime request",
2182            json!({"request_id": attempt.request.request_id}),
2183        )
2184    } else {
2185        runtime_error(
2186            RuntimeErrorCode::CapabilityNotRunnable,
2187            "matching capabilities were found but none were runnable locally",
2188            json!({"rejected_candidates": candidate_collection.rejected_candidates}),
2189        )
2190    };
2191    let reason = if candidate_collection.rejected_candidates.is_empty() {
2192        RuntimeTransitionReasonCode::NoMatch
2193    } else {
2194        RuntimeTransitionReasonCode::ConstraintValidationFailed
2195    };
2196    emitter.push(RuntimeState::Error, reason, json!({"code": error.code}));
2197    emitter.push(
2198        RuntimeState::Ready,
2199        RuntimeTransitionReasonCode::ExecutionClosed,
2200        json!({"terminal_state": RuntimeState::Error}),
2201    );
2202    let failure_reason = if error.code == RuntimeErrorCode::CapabilityNotFound {
2203        SelectionFailureReason::NoMatch
2204    } else {
2205        SelectionFailureReason::NotRunnable
2206    };
2207    let finished = emitter.finish();
2208    let identity = attempt.request.context.identity.clone();
2209
2210    terminal_failure(FailureContext {
2211        attempt,
2212        state_events: finished.events,
2213        state_transitions: finished.transitions,
2214        state_machine_validation: finished.validation,
2215        candidate_collection,
2216        selection: SelectionRecord {
2217            status: SelectionStatus::NoMatch,
2218            selected_capability_id: None,
2219            selected_capability_version: None,
2220            failure_reason: Some(failure_reason),
2221            remaining_candidates: Vec::new(),
2222        },
2223        execution: ExecutionRecord {
2224            placement: placement.clone(),
2225            placement_target: placement.requested_target,
2226            status: ExecutionStatus::NotStarted,
2227            artifact_ref: None,
2228            started_at: None,
2229            completed_at: None,
2230            output_digest: None,
2231            failure_reason: Some(ExecutionFailureReason::ArtifactNotRunnable),
2232            artifact_verification: None,
2233            identity,
2234        },
2235        error,
2236        emitted_events: Vec::new(),
2237        workflow_evidence: None,
2238    })
2239}
2240
2241fn ambiguous_outcome(
2242    attempt: AttemptContext,
2243    mut emitter: StateEmitter,
2244    resolution: CandidateResolution,
2245) -> RuntimeExecutionOutcome {
2246    let placement = placement_not_attempted(
2247        attempt.request.context.requested_target,
2248        PlacementDecisionReason::SelectionNotReached,
2249    );
2250    let remaining_candidates = resolution
2251        .eligible
2252        .iter()
2253        .map(|candidate| runtime_candidate(candidate, resolution.candidate_reason))
2254        .collect::<Vec<_>>();
2255    let error = runtime_error(
2256        RuntimeErrorCode::CapabilityAmbiguous,
2257        "runtime request matched more than one eligible capability",
2258        json!({"remaining_candidates": remaining_candidates}),
2259    );
2260    emitter.push(
2261        RuntimeState::Error,
2262        RuntimeTransitionReasonCode::SelectionFailed,
2263        json!({"code": error.code}),
2264    );
2265    emitter.push(
2266        RuntimeState::Ready,
2267        RuntimeTransitionReasonCode::ExecutionClosed,
2268        json!({"terminal_state": RuntimeState::Error}),
2269    );
2270    let finished = emitter.finish();
2271    let identity = attempt.request.context.identity.clone();
2272
2273    terminal_failure(FailureContext {
2274        attempt,
2275        state_events: finished.events,
2276        state_transitions: finished.transitions,
2277        state_machine_validation: finished.validation,
2278        candidate_collection: resolution.collection,
2279        selection: SelectionRecord {
2280            status: SelectionStatus::Ambiguous,
2281            selected_capability_id: None,
2282            selected_capability_version: None,
2283            failure_reason: Some(SelectionFailureReason::Ambiguous),
2284            remaining_candidates,
2285        },
2286        execution: ExecutionRecord {
2287            placement: placement.clone(),
2288            placement_target: placement.requested_target,
2289            status: ExecutionStatus::NotStarted,
2290            artifact_ref: None,
2291            started_at: None,
2292            completed_at: None,
2293            output_digest: None,
2294            failure_reason: Some(ExecutionFailureReason::ArtifactNotRunnable),
2295            artifact_verification: None,
2296            identity,
2297        },
2298        error,
2299        emitted_events: Vec::new(),
2300        workflow_evidence: None,
2301    })
2302}
2303
2304fn pre_execution_failure_outcome(
2305    context: ExecutionContext,
2306    failure: PreExecutionFailure,
2307) -> RuntimeExecutionOutcome {
2308    let ExecutionContext {
2309        attempt,
2310        mut emitter,
2311        candidate_collection,
2312        selection,
2313    } = context;
2314    let reason = if emitter.current_state == RuntimeState::Selecting {
2315        RuntimeTransitionReasonCode::SelectionFailed
2316    } else {
2317        RuntimeTransitionReasonCode::ConstraintValidationFailed
2318    };
2319    emitter.push(
2320        RuntimeState::Error,
2321        reason,
2322        json!({"code": failure.error.code, "details": failure.error.details}),
2323    );
2324    emitter.push(
2325        RuntimeState::Ready,
2326        RuntimeTransitionReasonCode::ExecutionClosed,
2327        json!({"terminal_state": RuntimeState::Error}),
2328    );
2329    let finished = emitter.finish();
2330    let identity = attempt.request.context.identity.clone();
2331    terminal_failure(FailureContext {
2332        attempt,
2333        state_events: finished.events,
2334        state_transitions: finished.transitions,
2335        state_machine_validation: finished.validation,
2336        candidate_collection,
2337        selection,
2338        execution: ExecutionRecord {
2339            placement: failure.placement.clone(),
2340            placement_target: failure
2341                .placement
2342                .selected_target
2343                .unwrap_or(failure.placement.requested_target),
2344            status: ExecutionStatus::NotStarted,
2345            artifact_ref: failure.artifact_ref,
2346            started_at: None,
2347            completed_at: None,
2348            output_digest: None,
2349            failure_reason: Some(failure.failure_reason),
2350            artifact_verification: failure.artifact_verification,
2351            identity,
2352        },
2353        error: failure.error,
2354        emitted_events: Vec::new(),
2355        workflow_evidence: None,
2356    })
2357}
2358
2359#[allow(clippy::too_many_arguments)]
2360fn execution_failure_outcome(
2361    context: ExecutionContext,
2362    failure: ExecutionFailureState,
2363    error: RuntimeError,
2364    emitted_events: Vec<traverse_contracts::EventReference>,
2365    workflow_evidence: Option<WorkflowTraversalEvidence>,
2366) -> RuntimeExecutionOutcome {
2367    let ExecutionContext {
2368        attempt,
2369        mut emitter,
2370        candidate_collection,
2371        selection,
2372    } = context;
2373    emitter.push(
2374        RuntimeState::Error,
2375        RuntimeTransitionReasonCode::ExecutionFailed,
2376        json!({"code": error.code, "details": error.details}),
2377    );
2378    let completed_at = emitter.next_timestamp();
2379    emitter.push(
2380        RuntimeState::Ready,
2381        RuntimeTransitionReasonCode::ExecutionClosed,
2382        json!({"terminal_state": RuntimeState::Error}),
2383    );
2384    let finished = emitter.finish();
2385    let identity = attempt.request.context.identity.clone();
2386    let artifact_verification = attempt.artifact_verification.clone();
2387
2388    terminal_failure(FailureContext {
2389        attempt,
2390        state_events: finished.events,
2391        state_transitions: finished.transitions,
2392        state_machine_validation: finished.validation,
2393        candidate_collection,
2394        selection,
2395        execution: ExecutionRecord {
2396            placement: failure.placement.clone(),
2397            placement_target: failure
2398                .placement
2399                .selected_target
2400                .unwrap_or(failure.placement.requested_target),
2401            status: ExecutionStatus::Failed,
2402            artifact_ref: Some(failure.artifact_ref),
2403            started_at: Some(failure.started_at),
2404            completed_at: Some(completed_at),
2405            output_digest: None,
2406            failure_reason: Some(failure.failure_reason),
2407            artifact_verification,
2408            identity,
2409        },
2410        error,
2411        emitted_events,
2412        workflow_evidence,
2413    })
2414}
2415
2416#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
2417fn successful_execution_outcome(
2418    context: ExecutionContext,
2419    selected: &ResolvedCapability,
2420    started_execution: StartedExecution,
2421    execution_output: Value,
2422    emitted_events: Vec<traverse_contracts::EventReference>,
2423    workflow_evidence: Option<WorkflowTraversalEvidence>,
2424) -> RuntimeExecutionOutcome {
2425    let ExecutionContext {
2426        attempt,
2427        mut emitter,
2428        candidate_collection,
2429        selection,
2430    } = context;
2431    let completed_at = emitter.next_timestamp();
2432    let emits_events = selected.record.implementation_kind == ImplementationKind::Workflow
2433        || !selected.contract.emits.is_empty();
2434    if emits_events {
2435        emitter.push(
2436            RuntimeState::EmittingEvents,
2437            RuntimeTransitionReasonCode::ExecutionSucceededWithEvents,
2438            json!({
2439                "capability_id": selected.record.id,
2440                "capability_version": selected.record.version,
2441                "declared_event_count": selected.contract.emits.len(),
2442            }),
2443        );
2444        emitter.push(
2445            RuntimeState::Completed,
2446            RuntimeTransitionReasonCode::EventsEmitted,
2447            json!({
2448                "capability_id": selected.record.id,
2449                "capability_version": selected.record.version,
2450            }),
2451        );
2452    } else {
2453        emitter.push(
2454            RuntimeState::Completed,
2455            RuntimeTransitionReasonCode::ExecutionSucceeded,
2456            json!({
2457                "capability_id": selected.record.id,
2458                "capability_version": selected.record.version,
2459            }),
2460        );
2461    }
2462    emitter.push(
2463        RuntimeState::Ready,
2464        RuntimeTransitionReasonCode::ExecutionClosed,
2465        json!({"terminal_state": RuntimeState::Completed}),
2466    );
2467    let finished = emitter.finish();
2468
2469    let execution = ExecutionRecord {
2470        placement: started_execution.placement.clone(),
2471        placement_target: started_execution
2472            .placement
2473            .selected_target
2474            .unwrap_or(started_execution.placement.requested_target),
2475        status: ExecutionStatus::Succeeded,
2476        artifact_ref: Some(selected.record.artifact_ref.clone()),
2477        started_at: Some(started_execution.started_at),
2478        completed_at: Some(completed_at),
2479        output_digest: Some(content_digest(&execution_output)),
2480        failure_reason: None,
2481        artifact_verification: attempt.artifact_verification.clone(),
2482        identity: attempt.request.context.identity.clone(),
2483    };
2484    let result_record = TraceResultRecord {
2485        status: RuntimeResultStatus::Completed,
2486        output: Some(execution_output.clone()),
2487        error: None,
2488        warnings: attempt.warnings.clone(),
2489    };
2490    let otel_trace = otel_trace_record(
2491        &attempt,
2492        &finished.transitions,
2493        &selection,
2494        &execution,
2495        &result_record,
2496    );
2497
2498    let trace = RuntimeTrace {
2499        kind: RUNTIME_TRACE_KIND.to_string(),
2500        schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
2501        trace_id: attempt.trace_id.clone(),
2502        execution_id: attempt.execution_id.clone(),
2503        request_id: attempt.request.request_id.clone(),
2504        governing_spec: GOVERNING_SPEC.to_string(),
2505        request: attempt.request.clone(),
2506        decision_evidence: TraceDecisionEvidence {
2507            candidate_collection: candidate_collection.clone(),
2508            selection: selection.clone(),
2509            model_resolution: Vec::new(),
2510        },
2511        state_progression: TraceStateProgression {
2512            state_events: finished.events.clone(),
2513            transitions: finished.transitions.clone(),
2514            validation: finished.validation.clone(),
2515        },
2516        terminal_outcome: TraceTerminalOutcome {
2517            runtime_status: RuntimeResultStatus::Completed,
2518            execution_status: execution.status,
2519            failure_reason: None,
2520            error: None,
2521        },
2522        emitted_events,
2523        workflow_evidence,
2524        model_resolution: Vec::new(),
2525        state_transitions: finished.transitions.clone(),
2526        state_machine_validation: finished.validation.clone(),
2527        candidate_collection,
2528        selection,
2529        execution,
2530        result: result_record,
2531        otel_trace,
2532    };
2533
2534    let result = RuntimeResult {
2535        kind: RUNTIME_RESULT_KIND.to_string(),
2536        schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
2537        execution_id: attempt.execution_id,
2538        request_id: attempt.request.request_id,
2539        status: RuntimeResultStatus::Completed,
2540        trace_ref: attempt.trace_id,
2541        output: Some(execution_output),
2542        error: None,
2543        warnings: attempt.warnings,
2544    };
2545
2546    RuntimeExecutionOutcome {
2547        result,
2548        trace,
2549        state_events: finished.events,
2550    }
2551}
2552
2553fn validate_request(request: &RuntimeRequest) -> Option<RuntimeError> {
2554    if request.kind != RUNTIME_REQUEST_KIND {
2555        return Some(runtime_error(
2556            RuntimeErrorCode::RequestInvalid,
2557            "kind must equal runtime_request",
2558            json!({"path": "$.kind"}),
2559        ));
2560    }
2561    if request.schema_version != SUPPORTED_SCHEMA_VERSION {
2562        return Some(runtime_error(
2563            RuntimeErrorCode::RequestInvalid,
2564            "schema_version must equal 1.0.0",
2565            json!({"path": "$.schema_version"}),
2566        ));
2567    }
2568    if request.governing_spec != GOVERNING_SPEC {
2569        return Some(runtime_error(
2570            RuntimeErrorCode::RequestInvalid,
2571            "governing_spec must equal 006-runtime-request-execution",
2572            json!({"path": "$.governing_spec"}),
2573        ));
2574    }
2575    if request.request_id.trim().is_empty() {
2576        return Some(runtime_error(
2577            RuntimeErrorCode::RequestInvalid,
2578            "request_id must be non-empty",
2579            json!({"path": "$.request_id"}),
2580        ));
2581    }
2582    if request.lookup.allow_ambiguity {
2583        return Some(runtime_error(
2584            RuntimeErrorCode::RequestInvalid,
2585            "allow_ambiguity must be false in this runtime slice",
2586            json!({"path": "$.lookup.allow_ambiguity"}),
2587        ));
2588    }
2589    if request
2590        .intent
2591        .capability_version
2592        .as_deref()
2593        .is_some_and(|version| Version::parse(version).is_err())
2594    {
2595        return Some(runtime_error(
2596            RuntimeErrorCode::RequestInvalid,
2597            "capability_version must be valid semantic versioning",
2598            json!({"path": "$.intent.capability_version"}),
2599        ));
2600    }
2601
2602    let exact_id = request
2603        .intent
2604        .capability_id
2605        .as_deref()
2606        .is_some_and(non_empty);
2607    let exact_version = request
2608        .intent
2609        .capability_version
2610        .as_deref()
2611        .is_some_and(non_empty);
2612    let intent_key = request.intent.intent_key.as_deref().is_some_and(non_empty);
2613
2614    if !(exact_id || intent_key) {
2615        return Some(runtime_error(
2616            RuntimeErrorCode::RequestInvalid,
2617            "runtime intent must include capability_id or intent_key",
2618            json!({"path": "$.intent"}),
2619        ));
2620    }
2621
2622    if exact_version && !exact_id {
2623        return Some(runtime_error(
2624            RuntimeErrorCode::RequestInvalid,
2625            "capability_version requires capability_id",
2626            json!({"path": "$.intent.capability_version"}),
2627        ));
2628    }
2629
2630    let has_version_range = request
2631        .intent
2632        .version_range
2633        .as_deref()
2634        .is_some_and(non_empty);
2635
2636    if has_version_range && !exact_id {
2637        return Some(runtime_error(
2638            RuntimeErrorCode::RequestInvalid,
2639            "version_range requires capability_id",
2640            json!({"path": "$.intent.version_range"}),
2641        ));
2642    }
2643
2644    if has_version_range && exact_version {
2645        return Some(runtime_error(
2646            RuntimeErrorCode::RequestInvalid,
2647            "version_range and capability_version are mutually exclusive",
2648            json!({"path": "$.intent.version_range"}),
2649        ));
2650    }
2651
2652    None
2653}
2654
2655fn is_exact_target(intent: &RuntimeIntent) -> bool {
2656    intent.capability_id.as_deref().is_some_and(non_empty)
2657        && intent.capability_version.as_deref().is_some_and(non_empty)
2658}
2659
2660fn non_empty(value: &str) -> bool {
2661    !value.trim().is_empty()
2662}
2663
2664fn map_lookup_scope(scope: RuntimeLookupScope) -> LookupScope {
2665    match scope {
2666        RuntimeLookupScope::PublicOnly => LookupScope::PublicOnly,
2667        RuntimeLookupScope::PreferPrivate => LookupScope::PreferPrivate,
2668    }
2669}
2670
2671fn evaluate_candidate(candidate: ResolvedCapability) -> CandidateEvaluation {
2672    if !candidate.contract.lifecycle.is_runtime_eligible() {
2673        return CandidateEvaluation::Rejected(
2674            candidate,
2675            RejectedCandidateReason::LifecycleNotRunnable,
2676        );
2677    }
2678    if candidate.record.implementation_kind == ImplementationKind::Workflow {
2679        if candidate.artifact.workflow_ref.is_some() {
2680            return CandidateEvaluation::Eligible(candidate);
2681        }
2682        return CandidateEvaluation::Rejected(candidate, RejectedCandidateReason::ArtifactMissing);
2683    }
2684
2685    let execution = &candidate.contract.execution;
2686    if !execution
2687        .preferred_targets
2688        .contains(&ExecutionTarget::Local)
2689        || execution.constraints.host_api_access != HostApiAccess::None
2690        || execution.constraints.network_access != NetworkAccess::Forbidden
2691    {
2692        return CandidateEvaluation::Rejected(
2693            candidate,
2694            RejectedCandidateReason::NotRunnableLocally,
2695        );
2696    }
2697
2698    match candidate.artifact.binary.as_ref() {
2699        Some(binary) if binary.location.trim().is_empty() => {
2700            CandidateEvaluation::Rejected(candidate, RejectedCandidateReason::ArtifactMissing)
2701        }
2702        // Native/host-handled capabilities execute without a registered binary blob.
2703        None | Some(_) => CandidateEvaluation::Eligible(candidate),
2704    }
2705}
2706
2707fn validate_payload_against_contract(
2708    payload: &Value,
2709    schema: &Value,
2710    code: RuntimeErrorCode,
2711    message: &str,
2712) -> Result<(), RuntimeError> {
2713    let mut errors = Vec::new();
2714    validate_value_against_schema(payload, schema, "$", &mut errors);
2715    if errors.is_empty() {
2716        Ok(())
2717    } else {
2718        Err(runtime_error(
2719            code,
2720            message,
2721            json!({ "violations": errors }),
2722        ))
2723    }
2724}
2725
2726pub(crate) fn validate_value_against_schema(
2727    value: &Value,
2728    schema: &Value,
2729    path: &str,
2730    errors: &mut Vec<Value>,
2731) {
2732    let Some(schema_object) = schema.as_object() else {
2733        errors.push(json!({
2734            "path": path,
2735            "message": "schema must be an object"
2736        }));
2737        return;
2738    };
2739
2740    if let Some(schema_type) = schema_object.get("type").and_then(Value::as_str) {
2741        match schema_type {
2742            "object" => {
2743                let Some(instance) = value.as_object() else {
2744                    errors.push(type_error(path, "object"));
2745                    return;
2746                };
2747                validate_required(instance, schema_object, path, errors);
2748                validate_properties(instance, schema_object, path, errors);
2749            }
2750            "array" => {
2751                let Some(items) = value.as_array() else {
2752                    errors.push(type_error(path, "array"));
2753                    return;
2754                };
2755                if let Some(item_schema) = schema_object.get("items") {
2756                    for (index, item) in items.iter().enumerate() {
2757                        validate_value_against_schema(
2758                            item,
2759                            item_schema,
2760                            &format!("{path}[{index}]"),
2761                            errors,
2762                        );
2763                    }
2764                }
2765            }
2766            "string" if !value.is_string() => errors.push(type_error(path, "string")),
2767            "integer" if value.as_i64().is_none() && value.as_u64().is_none() => {
2768                errors.push(type_error(path, "integer"));
2769            }
2770            "number" if !value.is_number() => errors.push(type_error(path, "number")),
2771            "boolean" if !value.is_boolean() => errors.push(type_error(path, "boolean")),
2772            "null" if !value.is_null() => errors.push(type_error(path, "null")),
2773            _ => {}
2774        }
2775    }
2776}
2777
2778fn validate_required(
2779    instance: &Map<String, Value>,
2780    schema_object: &Map<String, Value>,
2781    path: &str,
2782    errors: &mut Vec<Value>,
2783) {
2784    let Some(required) = schema_object.get("required").and_then(Value::as_array) else {
2785        return;
2786    };
2787
2788    for required_field in required.iter().filter_map(Value::as_str) {
2789        if !instance.contains_key(required_field) {
2790            errors.push(json!({
2791                "path": format!("{path}.{required_field}"),
2792                "message": "required property is missing"
2793            }));
2794        }
2795    }
2796}
2797
2798fn validate_properties(
2799    instance: &Map<String, Value>,
2800    schema_object: &Map<String, Value>,
2801    path: &str,
2802    errors: &mut Vec<Value>,
2803) {
2804    let Some(properties) = schema_object.get("properties").and_then(Value::as_object) else {
2805        return;
2806    };
2807
2808    for (key, value) in instance {
2809        if let Some(property_schema) = properties.get(key) {
2810            validate_value_against_schema(value, property_schema, &format!("{path}.{key}"), errors);
2811        }
2812    }
2813}
2814
2815fn type_error(path: &str, expected: &str) -> Value {
2816    json!({
2817        "path": path,
2818        "message": format!("expected {expected}")
2819    })
2820}
2821
2822fn runtime_candidate(capability: &ResolvedCapability, reason: CandidateReason) -> RuntimeCandidate {
2823    RuntimeCandidate {
2824        scope: map_registry_scope(capability.record.scope),
2825        capability_id: capability.record.id.clone(),
2826        capability_version: capability.record.version.clone(),
2827        artifact_ref: capability.record.artifact_ref.clone(),
2828        implementation_kind: map_implementation_kind(capability.record.implementation_kind),
2829        lifecycle: map_lifecycle(&capability.record.lifecycle),
2830        reason,
2831    }
2832}
2833
2834fn map_registry_scope(scope: RegistryScope) -> RuntimeRegistryScope {
2835    match scope {
2836        RegistryScope::Public => RuntimeRegistryScope::Public,
2837        RegistryScope::Private => RuntimeRegistryScope::Private,
2838    }
2839}
2840
2841fn map_implementation_kind(kind: ImplementationKind) -> RuntimeImplementationKind {
2842    match kind {
2843        ImplementationKind::Executable => RuntimeImplementationKind::Executable,
2844        ImplementationKind::Workflow => RuntimeImplementationKind::Workflow,
2845    }
2846}
2847
2848fn map_lifecycle(lifecycle: &Lifecycle) -> RuntimeLifecycle {
2849    match lifecycle {
2850        Lifecycle::Draft => RuntimeLifecycle::Draft,
2851        Lifecycle::Active => RuntimeLifecycle::Active,
2852        Lifecycle::Deprecated => RuntimeLifecycle::Deprecated,
2853        Lifecycle::Retired => RuntimeLifecycle::Retired,
2854        Lifecycle::Archived => RuntimeLifecycle::Archived,
2855    }
2856}
2857
2858fn runtime_error(code: RuntimeErrorCode, message: &str, details: Value) -> RuntimeError {
2859    RuntimeError {
2860        code,
2861        message: message.to_string(),
2862        details,
2863    }
2864}
2865
2866fn content_digest(value: &Value) -> String {
2867    let json = value.to_string();
2868    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
2869    for byte in json.as_bytes() {
2870        hash ^= u64::from(*byte);
2871        hash = hash.wrapping_mul(0x0000_0001_0000_01b3);
2872    }
2873    format!("0.1.0:{hash:016x}")
2874}
2875
2876fn otel_trace_record(
2877    attempt: &AttemptContext,
2878    state_transitions: &[RuntimeTransitionRecord],
2879    selection: &SelectionRecord,
2880    execution: &ExecutionRecord,
2881    result: &TraceResultRecord,
2882) -> OTelTraceRecord {
2883    let trace_id = otel_trace_id(attempt);
2884    let root_span_id = otel_span_id(attempt, "runtime.request", 0);
2885    let mut spans = vec![otel_span(OTelSpanInput {
2886        trace_id: &trace_id,
2887        span_id: &root_span_id,
2888        parent_span_id: None,
2889        name: "traverse.runtime.request",
2890        status: span_status(result.status),
2891        started_at: first_transition_time(state_transitions),
2892        ended_at: last_transition_time(state_transitions),
2893        attributes: base_otel_attributes(attempt, selection, execution),
2894        events: error_events(result),
2895    })];
2896
2897    for (index, phase) in otel_phase_names().iter().enumerate() {
2898        spans.push(otel_span(OTelSpanInput {
2899            trace_id: &trace_id,
2900            span_id: &otel_span_id(attempt, phase, index + 1),
2901            parent_span_id: Some(root_span_id.clone()),
2902            name: phase,
2903            status: phase_status(phase, result.status),
2904            started_at: phase_started_at(state_transitions, index),
2905            ended_at: phase_ended_at(state_transitions, index),
2906            attributes: base_otel_attributes(attempt, selection, execution),
2907            events: if result.status == RuntimeResultStatus::Error
2908                && *phase == "traverse.trace.assembly"
2909            {
2910                error_events(result)
2911            } else {
2912                Vec::new()
2913            },
2914        }));
2915    }
2916
2917    OTelTraceRecord {
2918        trace_id,
2919        parent_traceparent: attempt.request.context.traceparent.clone(),
2920        tracestate: attempt.request.context.tracestate.clone(),
2921        exporter: OTelExporterRecord {
2922            enabled: attempt.observability.exporter.endpoint.is_some(),
2923            endpoint: attempt.observability.exporter.endpoint.clone(),
2924            protocol: attempt.observability.exporter.protocol,
2925        },
2926        spans,
2927    }
2928}
2929
2930fn otel_phase_names() -> [&'static str; 5] {
2931    [
2932        "traverse.request.intake",
2933        "traverse.registry.lookup",
2934        "traverse.contract.validation",
2935        "traverse.capability.execution",
2936        "traverse.trace.assembly",
2937    ]
2938}
2939
2940fn otel_trace_id(attempt: &AttemptContext) -> String {
2941    if attempt.observability.deterministic_ids {
2942        let seed = attempt
2943            .observability
2944            .deterministic_seed
2945            .as_deref()
2946            .unwrap_or("traverse-test");
2947        return deterministic_hex(seed, &attempt.trace_id, 32);
2948    }
2949    deterministic_hex("traverse-runtime", &attempt.trace_id, 32)
2950}
2951
2952fn otel_span_id(attempt: &AttemptContext, name: &str, index: usize) -> String {
2953    let seed = attempt
2954        .observability
2955        .deterministic_seed
2956        .as_deref()
2957        .unwrap_or("traverse-runtime");
2958    deterministic_hex(
2959        seed,
2960        &format!("{}:{name}:{index}", attempt.execution_id),
2961        16,
2962    )
2963}
2964
2965fn deterministic_hex(seed: &str, value: &str, len: usize) -> String {
2966    let mut hash: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
2967    for byte in seed.as_bytes().iter().chain(value.as_bytes()) {
2968        hash ^= u128::from(*byte);
2969        hash = hash.wrapping_mul(0x0000_0000_0100_0000_0000_0000_0000_013b);
2970    }
2971    format!("{hash:032x}").chars().take(len).collect()
2972}
2973
2974struct OTelSpanInput<'a> {
2975    trace_id: &'a str,
2976    span_id: &'a str,
2977    parent_span_id: Option<String>,
2978    name: &'a str,
2979    status: OTelSpanStatus,
2980    started_at: String,
2981    ended_at: String,
2982    attributes: Vec<OTelAttribute>,
2983    events: Vec<OTelSpanEvent>,
2984}
2985
2986fn otel_span(input: OTelSpanInput<'_>) -> OTelSpanRecord {
2987    OTelSpanRecord {
2988        trace_id: input.trace_id.to_string(),
2989        span_id: input.span_id.to_string(),
2990        parent_span_id: input.parent_span_id,
2991        name: input.name.to_string(),
2992        kind: OTelSpanKind::Internal,
2993        status: input.status,
2994        started_at: input.started_at,
2995        ended_at: input.ended_at,
2996        attributes: input.attributes,
2997        events: input.events,
2998    }
2999}
3000
3001fn base_otel_attributes(
3002    attempt: &AttemptContext,
3003    selection: &SelectionRecord,
3004    execution: &ExecutionRecord,
3005) -> Vec<OTelAttribute> {
3006    let mut attributes = vec![
3007        otel_attr("traverse.request.id", json!(attempt.request.request_id)),
3008        otel_attr("traverse.execution.id", json!(attempt.execution_id)),
3009        otel_attr("traverse.lookup.scope", json!(attempt.request.lookup.scope)),
3010        otel_attr(
3011            "traverse.runtime.placement.target",
3012            json!(execution.placement_target),
3013        ),
3014    ];
3015    if let Some(correlation_id) = &attempt.request.context.correlation_id {
3016        attributes.push(otel_attr("traverse.correlation.id", json!(correlation_id)));
3017    }
3018    if let Some(capability_id) = &selection.selected_capability_id {
3019        attributes.push(otel_attr("traverse.capability.id", json!(capability_id)));
3020    }
3021    if let Some(capability_version) = &selection.selected_capability_version {
3022        attributes.push(otel_attr(
3023            "traverse.capability.version",
3024            json!(capability_version),
3025        ));
3026    }
3027    attributes
3028}
3029
3030fn otel_attr(key: &str, value: Value) -> OTelAttribute {
3031    OTelAttribute {
3032        key: key.to_string(),
3033        value,
3034    }
3035}
3036
3037fn error_events(result: &TraceResultRecord) -> Vec<OTelSpanEvent> {
3038    result
3039        .error
3040        .as_ref()
3041        .map(|error| {
3042            vec![OTelSpanEvent {
3043                name: "exception".to_string(),
3044                timestamp: "1970-01-01T00:00:00Z".to_string(),
3045                attributes: vec![
3046                    otel_attr("traverse.error.classification", json!(error.code)),
3047                    otel_attr("traverse.error.message", json!(error.message)),
3048                ],
3049            }]
3050        })
3051        .unwrap_or_default()
3052}
3053
3054fn span_status(status: RuntimeResultStatus) -> OTelSpanStatus {
3055    match status {
3056        RuntimeResultStatus::Completed => OTelSpanStatus::Ok,
3057        RuntimeResultStatus::Error => OTelSpanStatus::Error,
3058    }
3059}
3060
3061fn phase_status(phase: &str, status: RuntimeResultStatus) -> OTelSpanStatus {
3062    if status == RuntimeResultStatus::Error && phase == "traverse.trace.assembly" {
3063        OTelSpanStatus::Error
3064    } else {
3065        OTelSpanStatus::Ok
3066    }
3067}
3068
3069fn first_transition_time(transitions: &[RuntimeTransitionRecord]) -> String {
3070    transitions.first().map_or_else(
3071        || "1970-01-01T00:00:00Z".to_string(),
3072        |transition| transition.occurred_at.clone(),
3073    )
3074}
3075
3076fn last_transition_time(transitions: &[RuntimeTransitionRecord]) -> String {
3077    transitions.last().map_or_else(
3078        || "1970-01-01T00:00:00Z".to_string(),
3079        |transition| transition.occurred_at.clone(),
3080    )
3081}
3082
3083fn phase_started_at(transitions: &[RuntimeTransitionRecord], index: usize) -> String {
3084    transitions.get(index).map_or_else(
3085        || first_transition_time(transitions),
3086        |transition| transition.occurred_at.clone(),
3087    )
3088}
3089
3090fn phase_ended_at(transitions: &[RuntimeTransitionRecord], index: usize) -> String {
3091    transitions.get(index + 1).map_or_else(
3092        || last_transition_time(transitions),
3093        |transition| transition.occurred_at.clone(),
3094    )
3095}
3096
3097fn contains_drafts_segment(path: &str) -> bool {
3098    path.replace('\\', "/")
3099        .split('/')
3100        .any(|segment| segment == "drafts")
3101}
3102
3103struct AttemptContext {
3104    request: RuntimeRequest,
3105    execution_id: String,
3106    trace_id: String,
3107    observability: RuntimeObservabilityConfig,
3108    artifact_verification: Option<ArtifactVerificationRecord>,
3109    warnings: Vec<RuntimeWarning>,
3110}
3111
3112struct CandidateResolution {
3113    eligible: Vec<ResolvedCapability>,
3114    collection: CandidateCollectionRecord,
3115    candidate_reason: CandidateReason,
3116}
3117
3118struct FailureContext {
3119    attempt: AttemptContext,
3120    state_events: Vec<RuntimeStateEvent>,
3121    state_transitions: Vec<RuntimeTransitionRecord>,
3122    state_machine_validation: RuntimeStateMachineValidationEvidence,
3123    candidate_collection: CandidateCollectionRecord,
3124    selection: SelectionRecord,
3125    execution: ExecutionRecord,
3126    error: RuntimeError,
3127    emitted_events: Vec<traverse_contracts::EventReference>,
3128    workflow_evidence: Option<WorkflowTraversalEvidence>,
3129}
3130
3131struct ExecutionFailureState {
3132    artifact_ref: String,
3133    started_at: String,
3134    placement: PlacementDecisionRecord,
3135    failure_reason: ExecutionFailureReason,
3136}
3137
3138struct ExecutionContext {
3139    attempt: AttemptContext,
3140    emitter: StateEmitter,
3141    candidate_collection: CandidateCollectionRecord,
3142    selection: SelectionRecord,
3143}
3144
3145struct StartedExecution {
3146    started_at: String,
3147    placement: PlacementDecisionRecord,
3148}
3149
3150struct PreExecutionFailure {
3151    artifact_ref: Option<String>,
3152    failure_reason: ExecutionFailureReason,
3153    placement: PlacementDecisionRecord,
3154    error: RuntimeError,
3155    artifact_verification: Option<ArtifactVerificationRecord>,
3156}
3157
3158enum CandidateEvaluation {
3159    Eligible(ResolvedCapability),
3160    Rejected(ResolvedCapability, RejectedCandidateReason),
3161}
3162
3163struct StateEmitter {
3164    execution_id: String,
3165    request_id: String,
3166    next_second: u32,
3167    next_event_index: u32,
3168    current_state: RuntimeState,
3169    events: Vec<RuntimeStateEvent>,
3170    transitions: Vec<RuntimeTransitionRecord>,
3171    violations: Vec<Value>,
3172}
3173
3174struct FinishedStateMachineArtifacts {
3175    events: Vec<RuntimeStateEvent>,
3176    transitions: Vec<RuntimeTransitionRecord>,
3177    validation: RuntimeStateMachineValidationEvidence,
3178}
3179
3180fn start_selected_execution(
3181    emitter: &mut StateEmitter,
3182    selected: &ResolvedCapability,
3183    placement: PlacementDecisionRecord,
3184    identity: Option<&RuntimeIdentity>,
3185) -> StartedExecution {
3186    let started_at = emitter.next_timestamp();
3187    emitter.push(
3188        RuntimeState::Executing,
3189        RuntimeTransitionReasonCode::CandidateSelected,
3190        json!({
3191            "capability_id": selected.record.id,
3192            "capability_version": selected.record.version,
3193            "artifact_ref": selected.record.artifact_ref,
3194            "requested_target": placement.requested_target,
3195            "selected_target": placement.selected_target,
3196            "placement_status": placement.status,
3197            "placement_reason": placement.reason,
3198            "identity": identity,
3199        }),
3200    );
3201    StartedExecution {
3202        started_at,
3203        placement,
3204    }
3205}
3206
3207impl StateEmitter {
3208    fn new(execution_id: &str, request_id: &str) -> Self {
3209        Self {
3210            execution_id: execution_id.to_string(),
3211            request_id: request_id.to_string(),
3212            next_second: 0,
3213            next_event_index: 0,
3214            current_state: RuntimeState::Idle,
3215            events: Vec::new(),
3216            transitions: Vec::new(),
3217            violations: Vec::new(),
3218        }
3219    }
3220
3221    fn push(&mut self, state: RuntimeState, reason: RuntimeTransitionReasonCode, details: Value) {
3222        let transitioned = self.try_push(state, reason, details);
3223        debug_assert!(transitioned, "runtime state transition must be spec-valid");
3224    }
3225
3226    fn try_push(
3227        &mut self,
3228        state: RuntimeState,
3229        reason: RuntimeTransitionReasonCode,
3230        details: Value,
3231    ) -> bool {
3232        let from_state = self.current_state;
3233        if !is_allowed_transition(from_state, state, reason) {
3234            self.violations.push(json!({
3235                "from_state": from_state,
3236                "to_state": state,
3237                "reason_code": reason,
3238                "message": "unexpected runtime state transition"
3239            }));
3240            return false;
3241        }
3242        let entered_at = self.next_timestamp();
3243        let mut event_details = detail_object(details);
3244        event_details.insert(
3245            "transition_reason".to_string(),
3246            serde_json::to_value(reason)
3247                .unwrap_or_else(|_| Value::String("serialization_failed".to_string())),
3248        );
3249        let event = RuntimeStateEvent {
3250            kind: RUNTIME_STATE_EVENT_KIND.to_string(),
3251            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
3252            event_id: format!("rse_{}_{:04}", self.execution_id, self.next_event_index),
3253            execution_id: self.execution_id.clone(),
3254            request_id: self.request_id.clone(),
3255            state,
3256            entered_at: entered_at.clone(),
3257            details: Value::Object(event_details.clone()),
3258        };
3259        self.next_event_index += 1;
3260        self.events.push(event);
3261        self.transitions.push(RuntimeTransitionRecord {
3262            from_state,
3263            to_state: state,
3264            reason_code: reason,
3265            occurred_at: entered_at,
3266            request_id: Some(self.request_id.clone()),
3267            execution_id: Some(self.execution_id.clone()),
3268            details: Some(Value::Object(event_details)),
3269        });
3270        self.current_state = state;
3271        true
3272    }
3273
3274    fn next_timestamp(&mut self) -> String {
3275        let timestamp = format!("1970-01-01T00:00:{:02}Z", self.next_second);
3276        self.next_second += 1;
3277        timestamp
3278    }
3279
3280    fn finish(self) -> FinishedStateMachineArtifacts {
3281        let checked_states = vec![
3282            RuntimeState::Idle,
3283            RuntimeState::LoadingRegistry,
3284            RuntimeState::Ready,
3285            RuntimeState::Discovering,
3286            RuntimeState::EvaluatingConstraints,
3287            RuntimeState::Selecting,
3288            RuntimeState::Executing,
3289            RuntimeState::EmittingEvents,
3290            RuntimeState::Completed,
3291            RuntimeState::Error,
3292        ];
3293        let checked_transitions = self
3294            .transitions
3295            .iter()
3296            .map(|transition| {
3297                format!(
3298                    "{}->{}",
3299                    runtime_state_name(transition.from_state),
3300                    runtime_state_name(transition.to_state)
3301                )
3302            })
3303            .collect();
3304        let validation = RuntimeStateMachineValidationEvidence {
3305            kind: RUNTIME_STATE_MACHINE_VALIDATION_KIND.to_string(),
3306            schema_version: SUPPORTED_SCHEMA_VERSION.to_string(),
3307            governing_spec: STATE_MACHINE_GOVERNING_SPEC.to_string(),
3308            validated_at: format!(
3309                "1970-01-01T00:00:{:02}Z",
3310                self.next_second.saturating_sub(1)
3311            ),
3312            status: if self.violations.is_empty() {
3313                RuntimeStateMachineValidationStatus::Passed
3314            } else {
3315                RuntimeStateMachineValidationStatus::Failed
3316            },
3317            checked_states,
3318            checked_transitions,
3319            violations: self.violations,
3320        };
3321        FinishedStateMachineArtifacts {
3322            events: self.events,
3323            transitions: self.transitions,
3324            validation,
3325        }
3326    }
3327}
3328
3329fn is_allowed_transition(
3330    from: RuntimeState,
3331    to: RuntimeState,
3332    reason: RuntimeTransitionReasonCode,
3333) -> bool {
3334    matches!(
3335        (from, to, reason),
3336        (
3337            RuntimeState::Idle,
3338            RuntimeState::LoadingRegistry,
3339            RuntimeTransitionReasonCode::RuntimeInitializationStarted
3340        ) | (
3341            RuntimeState::LoadingRegistry,
3342            RuntimeState::Ready,
3343            RuntimeTransitionReasonCode::RegistryLoaded
3344        ) | (
3345            RuntimeState::LoadingRegistry,
3346            RuntimeState::Error,
3347            RuntimeTransitionReasonCode::RegistryLoadFailed
3348        ) | (
3349            RuntimeState::Ready,
3350            RuntimeState::Discovering,
3351            RuntimeTransitionReasonCode::RequestStarted
3352        ) | (
3353            RuntimeState::Discovering,
3354            RuntimeState::EvaluatingConstraints,
3355            RuntimeTransitionReasonCode::CandidatesCollected
3356        ) | (
3357            RuntimeState::Discovering,
3358            RuntimeState::Error,
3359            RuntimeTransitionReasonCode::NoMatch
3360        ) | (
3361            RuntimeState::EvaluatingConstraints,
3362            RuntimeState::Selecting,
3363            RuntimeTransitionReasonCode::ConstraintsEvaluated
3364        ) | (
3365            RuntimeState::EvaluatingConstraints,
3366            RuntimeState::Error,
3367            RuntimeTransitionReasonCode::ConstraintValidationFailed
3368        ) | (
3369            RuntimeState::Selecting,
3370            RuntimeState::Executing,
3371            RuntimeTransitionReasonCode::CandidateSelected
3372        ) | (
3373            RuntimeState::Selecting,
3374            RuntimeState::Error,
3375            RuntimeTransitionReasonCode::SelectionFailed
3376        ) | (
3377            RuntimeState::Executing,
3378            RuntimeState::EmittingEvents,
3379            RuntimeTransitionReasonCode::ExecutionSucceededWithEvents
3380        ) | (
3381            RuntimeState::Executing,
3382            RuntimeState::Completed,
3383            RuntimeTransitionReasonCode::ExecutionSucceeded
3384        ) | (
3385            RuntimeState::Executing,
3386            RuntimeState::Error,
3387            RuntimeTransitionReasonCode::ExecutionFailed
3388        ) | (
3389            RuntimeState::EmittingEvents,
3390            RuntimeState::Completed,
3391            RuntimeTransitionReasonCode::EventsEmitted
3392        ) | (
3393            RuntimeState::EmittingEvents,
3394            RuntimeState::Error,
3395            RuntimeTransitionReasonCode::EventEmissionFailed
3396        ) | (
3397            RuntimeState::Completed | RuntimeState::Error,
3398            RuntimeState::Ready,
3399            RuntimeTransitionReasonCode::ExecutionClosed
3400        )
3401    )
3402}
3403
3404fn detail_object(details: Value) -> Map<String, Value> {
3405    match details {
3406        Value::Object(map) => map,
3407        other => {
3408            let mut map = Map::new();
3409            map.insert("value".to_string(), other);
3410            map
3411        }
3412    }
3413}
3414
3415fn runtime_state_name(state: RuntimeState) -> &'static str {
3416    match state {
3417        RuntimeState::Idle => "idle",
3418        RuntimeState::LoadingRegistry => "loading_registry",
3419        RuntimeState::Ready => "ready",
3420        RuntimeState::Discovering => "discovering",
3421        RuntimeState::EvaluatingConstraints => "evaluating_constraints",
3422        RuntimeState::Selecting => "selecting",
3423        RuntimeState::Executing => "executing",
3424        RuntimeState::EmittingEvents => "emitting_events",
3425        RuntimeState::Completed => "completed",
3426        RuntimeState::Error => "error",
3427    }
3428}
3429
3430#[cfg(test)]
3431mod tests {
3432    #![allow(clippy::expect_used)]
3433
3434    use std::fmt::Write as _;
3435
3436    use super::security::{
3437        ArtifactVerificationFailure, ArtifactVerificationScheme, ArtifactVerificationStatus,
3438        RuntimeSecurityConfig, derive_identity_from_jwt, verify_artifact,
3439    };
3440    use super::{
3441        BrowserRuntimeSubscriptionErrorCode, BrowserRuntimeSubscriptionMessage,
3442        BrowserRuntimeSubscriptionRequest, CandidateEvaluation, CandidateReason, LocalExecutor,
3443        PlacementTarget, RejectedCandidateReason, Runtime, RuntimeContext, RuntimeIntent,
3444        RuntimeLookup, RuntimeLookupScope, RuntimeLookupScope::*, RuntimeRequest,
3445        RuntimeResultStatus, RuntimeState, RuntimeTransitionReasonCode,
3446        browser_subscription_messages, evaluate_candidate, map_implementation_kind, map_lifecycle,
3447        map_registry_scope, parse_runtime_request, runtime_candidate, subscription_targets_outcome,
3448        validate_browser_subscription_request, validate_payload_against_contract, validate_request,
3449    };
3450    use ed25519_dalek::{Signer, SigningKey};
3451    use serde_json::json;
3452    use sha2::{Digest, Sha256};
3453    use std::collections::BTreeMap;
3454    use std::fs;
3455    use std::path::{Path, PathBuf};
3456    use std::sync::atomic::{AtomicU64, Ordering};
3457    use std::sync::{Arc, Mutex};
3458    use traverse_contracts::{
3459        BinaryFormat as ContractBinaryFormat, Entrypoint, EntrypointKind, Execution,
3460        ExecutionConstraints, ExecutionTarget, FilesystemAccess, HostApiAccess, Lifecycle,
3461        NetworkAccess, Owner, Provenance, ProvenanceSource, SchemaContainer, ServiceType,
3462    };
3463    use traverse_registry::{
3464        ArtifactDigests, ArtifactSignature, ArtifactSignatureScheme, BinaryFormat, BinaryReference,
3465        CapabilityArtifactRecord, CapabilityRegistration, CapabilityRegistry,
3466        CapabilityRegistryRecord, ComposabilityMetadata, CompositionKind, CompositionPattern,
3467        DiscoveryIndexEntry, ImplementationKind, ModelCandidateReadiness,
3468        ModelCandidateRejectionCode, ModelResolutionEvidence, ModelResolutionPhase,
3469        RegistryProvenance, RegistryScope, ResolvedCapability, SelectedModelCandidate, SourceKind,
3470        SourceReference, WorkspaceAppStateErrorCode,
3471    };
3472
3473    const HEX_TABLE: &[u8; 16] = b"0123456789abcdef";
3474    static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
3475
3476    #[derive(Debug, Default)]
3477    struct RecordingEventSink {
3478        events: Mutex<Vec<super::events::TraverseEvent>>,
3479    }
3480
3481    impl super::events::RuntimeEventSink for RecordingEventSink {
3482        fn emit(
3483            &self,
3484            event: super::events::TraverseEvent,
3485        ) -> Result<(), super::events::EventError> {
3486            self.events
3487                .lock()
3488                .expect("recording sink lock must not be poisoned")
3489                .push(event);
3490            Ok(())
3491        }
3492    }
3493
3494    #[derive(Debug)]
3495    struct FailingEventSink;
3496
3497    impl super::events::RuntimeEventSink for FailingEventSink {
3498        fn emit(
3499            &self,
3500            _event: super::events::TraverseEvent,
3501        ) -> Result<(), super::events::EventError> {
3502            Err(super::events::EventError::JournalWrite(
3503                "sink unavailable".to_string(),
3504            ))
3505        }
3506    }
3507
3508    #[test]
3509    fn missing_binary_metadata_is_eligible_for_native_host_execution() {
3510        let capability = resolved_capability(None, Lifecycle::Active);
3511
3512        let evaluation = evaluate_candidate(capability);
3513
3514        assert!(matches!(evaluation, CandidateEvaluation::Eligible(_)));
3515    }
3516
3517    #[test]
3518    fn live_wiring_helpers_support_native_and_wasm_artifact_types() {
3519        use super::executor::ArtifactType;
3520        use super::placement::PlacementError;
3521        use super::router::RouterError;
3522        use super::{ExecutionFailureReason, RuntimeErrorCode};
3523
3524        let native = resolved_capability(None, Lifecycle::Active);
3525        assert_eq!(super::artifact_type_for(&native), ArtifactType::Native);
3526        assert_eq!(
3527            super::executor_capability_for(&native, ArtifactType::Native).artifact_type,
3528            ArtifactType::Native
3529        );
3530
3531        let wasm = resolved_capability(
3532            Some(traverse_registry::BinaryReference {
3533                format: traverse_registry::BinaryFormat::Wasm,
3534                location: "artifact.wasm".to_string(),
3535                signature: None,
3536            }),
3537            Lifecycle::Active,
3538        );
3539        assert_eq!(super::artifact_type_for(&wasm), ArtifactType::Wasm);
3540
3541        let (code, reason, _) =
3542            super::map_router_error(&RouterError::ExecutorNotFound("Native".to_string()));
3543        assert_eq!(code.code, RuntimeErrorCode::CapabilityNotRunnable);
3544        assert_eq!(reason, ExecutionFailureReason::ArtifactNotRunnable);
3545
3546        let (code, reason, _) = super::map_router_error(&RouterError::TraceLockPoisoned);
3547        assert_eq!(code.code, RuntimeErrorCode::ExecutionFailed);
3548        assert_eq!(reason, ExecutionFailureReason::ExecutionFailed);
3549
3550        let (code, reason, _) = super::map_router_error(&RouterError::PlacementFailed(
3551            PlacementError::NoEligibleTarget,
3552        ));
3553        assert_eq!(code.code, RuntimeErrorCode::PlacementUnsupported);
3554        assert_eq!(reason, ExecutionFailureReason::PlacementUnsupported);
3555
3556        let (code, reason, _) =
3557            super::map_router_error(&RouterError::ExecutionFailed("boom".to_string()));
3558        assert_eq!(code.code, RuntimeErrorCode::ExecutionFailed);
3559        assert_eq!(reason, ExecutionFailureReason::ExecutionFailed);
3560
3561        // `RouterError::ContractViolation` is no longer produced by
3562        // `PlacementRouter` (spec 098-capability-event-host-abi FR-005
3563        // removed the post-hoc check that used to raise it) but the variant
3564        // remains part of `RouterError`'s public surface, so `map_router_error`
3565        // must still map it correctly.
3566        let (code, reason, _) = super::map_router_error(&RouterError::ContractViolation(vec![
3567            traverse_contracts::ViolationRecord::new(
3568                "undeclared_event_emission",
3569                "test.cap",
3570                "test message",
3571            ),
3572        ]));
3573        assert_eq!(code.code, RuntimeErrorCode::ContractViolation);
3574        assert_eq!(reason, ExecutionFailureReason::ExecutionFailed);
3575    }
3576
3577    #[test]
3578    fn live_wiring_runtime_surfaces_and_fallback_broker_are_covered() {
3579        use super::PlacementTarget;
3580        use super::events::{EventError, LifecycleStatus, TraverseEvent};
3581        use traverse_contracts::ExecutionTarget;
3582
3583        let runtime = Runtime::new(CapabilityRegistry::new(), NoopExecutor);
3584        let _ = runtime.clone();
3585        let debug = format!("{runtime:?}");
3586        assert!(debug.contains("Runtime"));
3587        assert!(Arc::ptr_eq(
3588            &runtime.event_broker(),
3589            &runtime.event_broker()
3590        ));
3591        assert!(Arc::ptr_eq(&runtime.trace_store(), &runtime.trace_store()));
3592
3593        let mapped = [
3594            (PlacementTarget::Local, ExecutionTarget::Local),
3595            (PlacementTarget::Browser, ExecutionTarget::Browser),
3596            (PlacementTarget::Edge, ExecutionTarget::Edge),
3597            (PlacementTarget::Cloud, ExecutionTarget::Cloud),
3598            (PlacementTarget::Worker, ExecutionTarget::Worker),
3599            (PlacementTarget::Device, ExecutionTarget::Device),
3600        ];
3601        for (placement, expected) in mapped {
3602            assert_eq!(super::execution_target_from_placement(placement), expected);
3603        }
3604
3605        let discard = super::event_broker_or_discard(Err(EventError::InvalidRetentionWindow(
3606            "forced".to_string(),
3607        )));
3608        assert!(
3609            discard
3610                .publish(TraverseEvent {
3611                    id: "evt".to_string(),
3612                    source: "test".to_string(),
3613                    event_type: "dev.traverse.discard".to_string(),
3614                    datacontenttype: "application/json".to_string(),
3615                    time: "2026-08-06T00:00:00Z".to_string(),
3616                    data: json!({}),
3617                    owner: "test".to_string(),
3618                    version: "1.0.0".to_string(),
3619                    lifecycle_status: LifecycleStatus::Active,
3620                    deduplication_id: None,
3621                    ordering_scope: None,
3622                    correlation_id: None,
3623                    causation_id: None,
3624                    subject_id: None,
3625                    actor_id: None,
3626                })
3627                .is_ok()
3628        );
3629        assert!(discard.subscribe("dev.traverse.discard", "0").is_err());
3630        assert!(
3631            discard
3632                .subscribe_for_subject("dev.traverse.discard", "0", None)
3633                .is_err()
3634        );
3635        assert!(discard.poll("missing", 1).is_err());
3636        assert!(discard.cancel("missing").is_err());
3637    }
3638
3639    /// Shared harness for the `bound_local_executor_*` publish tests: builds a
3640    /// broker with one registered/subscribed event type, a `Subscribable`
3641    /// resolved capability declaring that event in its `emits` list, and runs
3642    /// the given `LocalExecutor` through `PlacementRouter` exactly as the live
3643    /// `Runtime::execute()` path does. Returns the router response and the
3644    /// broker (so callers can poll it) plus the subscription id.
3645    fn run_native_executor_through_placement_router<E: super::LocalExecutor + 'static>(
3646        executor: E,
3647    ) -> (
3648        Result<super::router::RouterResponse, super::router::RouterError>,
3649        Arc<super::events::InProcessBroker>,
3650        String,
3651    ) {
3652        use super::events::{
3653            EventBroker, EventCatalog, EventCatalogEntry, InProcessBroker, LifecycleStatus,
3654        };
3655        use super::executor::ArtifactType;
3656        use super::placement::PlacementConstraintEvaluator;
3657        use super::router::{CapabilityExecutorRegistry, PlacementRouter, RouterRequest};
3658        use super::trace::TraceStore;
3659
3660        let event_type = "dev.traverse.native.live-emitted";
3661        let catalog = Arc::new(EventCatalog::new());
3662        catalog
3663            .register(EventCatalogEntry {
3664                event_type: event_type.to_string(),
3665                owner: "native.live".to_string(),
3666                version: "1.0.0".to_string(),
3667                lifecycle_status: LifecycleStatus::Active,
3668                consumer_count: 0,
3669            })
3670            .expect("catalog entry should register");
3671        let broker = Arc::new(InProcessBroker::new(catalog).expect("broker should construct"));
3672        let subscription = broker
3673            .subscribe(event_type, "0")
3674            .expect("subscribe should succeed");
3675        let trace_store = Arc::new(Mutex::new(TraceStore::new()));
3676
3677        let mut selected = resolved_capability(None, Lifecycle::Active);
3678        selected.contract.service_type = ServiceType::Subscribable;
3679        selected.contract.event_trigger = Some("dev.traverse.native.trigger".to_string());
3680        selected.contract.emits = vec![traverse_contracts::EventReference {
3681            event_id: event_type.to_string(),
3682            version: "1.0.0".to_string(),
3683        }];
3684        selected.contract.permitted_targets = vec![ExecutionTarget::Local, ExecutionTarget::Cloud];
3685
3686        let mut registry = CapabilityExecutorRegistry::new();
3687        registry.insert(
3688            ArtifactType::Native,
3689            Box::new(super::BoundLocalExecutor {
3690                executor: Arc::new(executor),
3691                selected: selected.clone(),
3692            }),
3693        );
3694        let router = PlacementRouter::new(
3695            PlacementConstraintEvaluator,
3696            registry,
3697            Arc::clone(&trace_store),
3698            broker.clone(),
3699        );
3700
3701        let response = router.execute(RouterRequest {
3702            capability_id: selected.record.id.clone(),
3703            artifact_type: ArtifactType::Native,
3704            contract: selected.contract.clone(),
3705            target_hint: Some(ExecutionTarget::Local),
3706            runtime_snapshot: super::idle_runtime_snapshot(),
3707            input: json!({}),
3708            executor_capability: super::executor_capability_for(&selected, ArtifactType::Native),
3709            trace_id_override: Some("trace_native_live".to_string()),
3710        });
3711
3712        (response, broker, subscription.subscription_id)
3713    }
3714
3715    fn native_traverse_event(event_type: &str) -> super::events::TraverseEvent {
3716        super::events::TraverseEvent {
3717            id: "native-event-1".to_string(),
3718            source: "traverse-runtime/test.native".to_string(),
3719            event_type: event_type.to_string(),
3720            datacontenttype: "application/json".to_string(),
3721            time: "2026-01-01T00:00:00Z".to_string(),
3722            data: json!({}),
3723            owner: "test.native".to_string(),
3724            version: "1.0.0".to_string(),
3725            lifecycle_status: super::events::LifecycleStatus::Active,
3726            deduplication_id: Some("native-event-1".to_string()),
3727            ordering_scope: Some("test.native".to_string()),
3728            correlation_id: None,
3729            causation_id: None,
3730            subject_id: None,
3731            actor_id: None,
3732        }
3733    }
3734
3735    #[test]
3736    fn bound_local_executor_publishes_declared_native_events_through_placement_router() {
3737        use super::events::EventBroker;
3738        use serde_json::Value;
3739        // Spec 101-local-executor-event-emission FR-002: `BoundLocalExecutor`
3740        // now threads a native `LocalExecutor`'s real `emitted_events` into
3741        // `ExecutorOutput`, so `PlacementRouter` Step 5 publishes them for
3742        // `Subscribable` capabilities exactly as it already does for the WASM
3743        // `CapabilityExecutor` path. This replaces the old test documenting
3744        // that gap as expected behavior.
3745        struct NativeEmitExecutor;
3746        impl super::LocalExecutor for NativeEmitExecutor {
3747            fn execute(
3748                &self,
3749                _capability: &ResolvedCapability,
3750                _input: &Value,
3751            ) -> Result<super::LocalExecutionOutput, super::LocalExecutionFailure> {
3752                Ok(super::LocalExecutionOutput {
3753                    value: json!({ "draft_id": "native-1" }),
3754                    emitted_events: vec![native_traverse_event("dev.traverse.native.live-emitted")],
3755                })
3756            }
3757        }
3758
3759        let (response, broker, subscription_id) =
3760            run_native_executor_through_placement_router(NativeEmitExecutor);
3761        let response = response.expect("native placement router execution should succeed");
3762
3763        assert_eq!(response.trace_id, "trace_native_live");
3764        assert_eq!(response.emitted_events.len(), 1);
3765        let poll = broker
3766            .poll(&subscription_id, 10)
3767            .expect("poll should succeed");
3768        assert_eq!(poll.events.len(), 1);
3769        assert_eq!(
3770            poll.events[0].event.event_type,
3771            "dev.traverse.native.live-emitted"
3772        );
3773    }
3774
3775    #[test]
3776    fn bound_local_executor_rejects_undeclared_native_event() {
3777        use super::events::EventBroker;
3778        use serde_json::Value;
3779        // Spec 101-local-executor-event-emission FR-007/FR-008: an event a
3780        // native `LocalExecutor` populates that is not in the capability
3781        // contract's `emits` list must fail the whole execution, not be
3782        // silently dropped or published.
3783        struct UndeclaredEmitExecutor;
3784        impl super::LocalExecutor for UndeclaredEmitExecutor {
3785            fn execute(
3786                &self,
3787                _capability: &ResolvedCapability,
3788                _input: &Value,
3789            ) -> Result<super::LocalExecutionOutput, super::LocalExecutionFailure> {
3790                Ok(super::LocalExecutionOutput {
3791                    value: json!({ "draft_id": "native-1" }),
3792                    emitted_events: vec![native_traverse_event("dev.traverse.native.undeclared")],
3793                })
3794            }
3795        }
3796
3797        let (response, broker, subscription_id) =
3798            run_native_executor_through_placement_router(UndeclaredEmitExecutor);
3799        assert!(response.is_err());
3800        let poll = broker
3801            .poll(&subscription_id, 10)
3802            .expect("poll should succeed");
3803        assert!(poll.events.is_empty());
3804    }
3805
3806    #[test]
3807    fn bound_local_executor_rejects_native_event_from_non_subscribable_capability() {
3808        use serde_json::Value;
3809        // Spec 101-local-executor-event-emission FR-007/FR-008: a
3810        // non-`Subscribable` capability that populates `emitted_events` must
3811        // fail the whole execution, mirroring the WASM ABI's FR-003 gate.
3812        struct NonSubscribableEmitExecutor;
3813        impl super::LocalExecutor for NonSubscribableEmitExecutor {
3814            fn execute(
3815                &self,
3816                _capability: &ResolvedCapability,
3817                _input: &Value,
3818            ) -> Result<super::LocalExecutionOutput, super::LocalExecutionFailure> {
3819                Ok(super::LocalExecutionOutput {
3820                    value: json!({ "draft_id": "native-1" }),
3821                    emitted_events: vec![native_traverse_event("dev.traverse.native.live-emitted")],
3822                })
3823            }
3824        }
3825
3826        use super::events::{
3827            EventBroker, EventCatalog, EventCatalogEntry, InProcessBroker, LifecycleStatus,
3828        };
3829        use super::executor::ArtifactType;
3830        use super::placement::PlacementConstraintEvaluator;
3831        use super::router::{CapabilityExecutorRegistry, PlacementRouter, RouterRequest};
3832        use super::trace::TraceStore;
3833
3834        let event_type = "dev.traverse.native.live-emitted";
3835        let catalog = Arc::new(EventCatalog::new());
3836        catalog
3837            .register(EventCatalogEntry {
3838                event_type: event_type.to_string(),
3839                owner: "native.live".to_string(),
3840                version: "1.0.0".to_string(),
3841                lifecycle_status: LifecycleStatus::Active,
3842                consumer_count: 0,
3843            })
3844            .expect("catalog entry should register");
3845        let broker = Arc::new(InProcessBroker::new(catalog).expect("broker should construct"));
3846        let subscription = broker
3847            .subscribe(event_type, "0")
3848            .expect("subscribe should succeed");
3849        let trace_store = Arc::new(Mutex::new(TraceStore::new()));
3850
3851        let mut selected = resolved_capability(None, Lifecycle::Active);
3852        selected.contract.service_type = ServiceType::Stateless;
3853        selected.contract.emits = vec![traverse_contracts::EventReference {
3854            event_id: event_type.to_string(),
3855            version: "1.0.0".to_string(),
3856        }];
3857        selected.contract.permitted_targets = vec![ExecutionTarget::Local, ExecutionTarget::Cloud];
3858
3859        let mut registry = CapabilityExecutorRegistry::new();
3860        registry.insert(
3861            ArtifactType::Native,
3862            Box::new(super::BoundLocalExecutor {
3863                executor: Arc::new(NonSubscribableEmitExecutor),
3864                selected: selected.clone(),
3865            }),
3866        );
3867        let router = PlacementRouter::new(
3868            PlacementConstraintEvaluator,
3869            registry,
3870            Arc::clone(&trace_store),
3871            broker.clone(),
3872        );
3873
3874        let response = router.execute(RouterRequest {
3875            capability_id: selected.record.id.clone(),
3876            artifact_type: ArtifactType::Native,
3877            contract: selected.contract.clone(),
3878            target_hint: Some(ExecutionTarget::Local),
3879            runtime_snapshot: super::idle_runtime_snapshot(),
3880            input: json!({}),
3881            executor_capability: super::executor_capability_for(&selected, ArtifactType::Native),
3882            trace_id_override: Some("trace_native_live_non_subscribable".to_string()),
3883        });
3884
3885        assert!(response.is_err());
3886        let poll = broker
3887            .poll(&subscription.subscription_id, 10)
3888            .expect("poll should succeed");
3889        assert!(poll.events.is_empty());
3890    }
3891
3892    #[test]
3893    fn invalid_json_request_reports_parse_error_text() {
3894        let error = parse_runtime_request("{invalid").err();
3895
3896        assert!(error.is_some());
3897        let message = error.map(|item| item.to_string()).unwrap_or_default();
3898        assert!(!message.is_empty());
3899    }
3900
3901    #[test]
3902    fn runtime_loads_durable_workspace_app_state() {
3903        let workspace_root = unique_workspace_state_dir();
3904        write_runtime_workspace_app_state_fixture(&workspace_root, "local");
3905
3906        let runtime = Runtime::from_workspace_app_state(
3907            &workspace_root,
3908            "local",
3909            NoopExecutor,
3910            "test-runtime",
3911        )
3912        .expect("workspace app state should load");
3913
3914        assert!(
3915            runtime
3916                .capability_registry()
3917                .find_exact(
3918                    traverse_registry::LookupScope::PreferPrivate,
3919                    "expedition.planning.validate-team-readiness",
3920                    "1.0.0"
3921                )
3922                .is_some()
3923        );
3924        assert!(
3925            runtime
3926                .workflow_registry()
3927                .find_exact(
3928                    traverse_registry::LookupScope::PreferPrivate,
3929                    "expedition.planning.plan-expedition",
3930                    "1.0.0"
3931                )
3932                .is_some()
3933        );
3934        assert_eq!(
3935            runtime.workspace_applications()[0].model_dependencies[0].interface_id,
3936            "traverse.inference.generate"
3937        );
3938    }
3939
3940    #[test]
3941    fn governed_model_execution_resolves_from_loaded_app_declaration() {
3942        let workspace_root = unique_workspace_state_dir();
3943        write_runtime_workspace_app_state_fixture(&workspace_root, "local");
3944        let runtime = Runtime::from_workspace_app_state(
3945            &workspace_root,
3946            "local",
3947            NoopExecutor,
3948            "test-runtime",
3949        )
3950        .expect("workspace app state should load");
3951        let mut provider_configs = BTreeMap::new();
3952        provider_configs.insert(
3953            "ollama.local.generate".to_string(),
3954            crate::inference::OllamaProviderConfig {
3955                base_url: "http://127.0.0.1:9".to_string(),
3956                request_timeout_ms: Some(50),
3957                max_response_bytes: None,
3958            },
3959        );
3960
3961        let error = runtime
3962            .execute_governed_model_dependency(
3963                "expedition.readiness",
3964                "1.0.0",
3965                &crate::inference::GovernedModelExecutionRequest {
3966                    interface_id: "traverse.inference.generate".to_string(),
3967                    prompt: "Summarize readiness.".to_string(),
3968                    system_prompt: None,
3969                    options: json!({}),
3970                    requested_placement: ExecutionTarget::Local,
3971                    provider_configs,
3972                },
3973            )
3974            .expect_err("unavailable local provider should fail before output");
3975
3976        assert_eq!(
3977            error.code,
3978            crate::inference::GovernedModelExecutionErrorCode::ModelDependencyUnsatisfied
3979        );
3980        let evidence = error
3981            .model_resolution
3982            .expect("failed model execution should include resolution evidence");
3983        assert_eq!(
3984            evidence.requested_interface_id,
3985            "traverse.inference.generate"
3986        );
3987        assert_eq!(
3988            evidence.machine_failure_code(),
3989            Some("model_dependency_unsatisfied")
3990        );
3991    }
3992
3993    #[test]
3994    fn governed_model_execution_rejects_missing_app_or_interface() {
3995        let workspace_root = unique_workspace_state_dir();
3996        write_runtime_workspace_app_state_fixture(&workspace_root, "local");
3997        let runtime = Runtime::from_workspace_app_state(
3998            &workspace_root,
3999            "local",
4000            NoopExecutor,
4001            "test-runtime",
4002        )
4003        .expect("workspace app state should load");
4004        let request = crate::inference::GovernedModelExecutionRequest {
4005            interface_id: "traverse.inference.embed".to_string(),
4006            prompt: "Summarize readiness.".to_string(),
4007            system_prompt: None,
4008            options: json!({}),
4009            requested_placement: ExecutionTarget::Local,
4010            provider_configs: BTreeMap::new(),
4011        };
4012
4013        let missing_app = runtime
4014            .execute_governed_model_dependency("missing.app", "1.0.0", &request)
4015            .expect_err("unknown app should fail");
4016        assert_eq!(
4017            missing_app.code,
4018            crate::inference::GovernedModelExecutionErrorCode::InterfaceNotDeclared
4019        );
4020
4021        let missing_interface = runtime
4022            .execute_governed_model_dependency("expedition.readiness", "1.0.0", &request)
4023            .expect_err("undeclared interface should fail");
4024        assert_eq!(
4025            missing_interface.code,
4026            crate::inference::GovernedModelExecutionErrorCode::InterfaceNotDeclared
4027        );
4028    }
4029
4030    #[test]
4031    fn runtime_reports_missing_workspace_app_state() {
4032        let workspace_root = unique_workspace_state_dir();
4033
4034        let failure = Runtime::from_workspace_app_state(
4035            &workspace_root,
4036            "local",
4037            NoopExecutor,
4038            "test-runtime",
4039        )
4040        .expect_err("missing workspace app state should fail");
4041
4042        assert_eq!(
4043            failure.errors[0].code,
4044            WorkspaceAppStateErrorCode::MissingWorkspaceState
4045        );
4046    }
4047
4048    #[test]
4049    fn request_validation_rejects_all_invalid_request_guards() {
4050        let mut request = valid_request();
4051        request.kind = "wrong".to_string();
4052        assert_eq!(
4053            validate_request(&request).map(|error| error.code),
4054            Some(super::RuntimeErrorCode::RequestInvalid)
4055        );
4056
4057        let mut request = valid_request();
4058        request.schema_version = "9.9.9".to_string();
4059        assert_eq!(
4060            validate_request(&request).map(|error| error.code),
4061            Some(super::RuntimeErrorCode::RequestInvalid)
4062        );
4063
4064        let mut request = valid_request();
4065        request.governing_spec = "wrong-spec".to_string();
4066        assert_eq!(
4067            validate_request(&request).map(|error| error.code),
4068            Some(super::RuntimeErrorCode::RequestInvalid)
4069        );
4070
4071        let mut request = valid_request();
4072        request.request_id.clear();
4073        assert_eq!(
4074            validate_request(&request).map(|error| error.code),
4075            Some(super::RuntimeErrorCode::RequestInvalid)
4076        );
4077
4078        let mut request = valid_request();
4079        request.lookup.allow_ambiguity = true;
4080        assert_eq!(
4081            validate_request(&request).map(|error| error.code),
4082            Some(super::RuntimeErrorCode::RequestInvalid)
4083        );
4084
4085        let mut request = valid_request();
4086        request.context.requested_target = PlacementTarget::Local;
4087        request.intent.capability_version = Some("bad".to_string());
4088        assert_eq!(
4089            validate_request(&request).map(|error| error.code),
4090            Some(super::RuntimeErrorCode::RequestInvalid)
4091        );
4092
4093        let mut request = valid_request();
4094        request.intent.capability_id = None;
4095        request.intent.intent_key = None;
4096        request.intent.capability_version = None;
4097        assert_eq!(
4098            validate_request(&request).map(|error| error.code),
4099            Some(super::RuntimeErrorCode::RequestInvalid)
4100        );
4101
4102        let mut request = valid_request();
4103        request.intent.capability_id = None;
4104        request.intent.capability_version = Some("1.0.0".to_string());
4105        assert_eq!(
4106            validate_request(&request).map(|error| error.code),
4107            Some(super::RuntimeErrorCode::RequestInvalid)
4108        );
4109    }
4110
4111    #[test]
4112    fn candidate_evaluation_covers_local_runnability_branches() {
4113        let mut capability = resolved_capability(
4114            Some(traverse_registry::BinaryReference {
4115                format: traverse_registry::BinaryFormat::Wasm,
4116                location: "artifact.wasm".to_string(),
4117                signature: None,
4118            }),
4119            Lifecycle::Active,
4120        );
4121        capability.record.implementation_kind = ImplementationKind::Workflow;
4122        assert!(matches!(
4123            evaluate_candidate(capability.clone()),
4124            CandidateEvaluation::Rejected(_, RejectedCandidateReason::ArtifactMissing)
4125        ));
4126        capability.artifact.workflow_ref = Some(traverse_registry::WorkflowReference {
4127            workflow_id: "workflow".to_string(),
4128            workflow_version: "1.0.0".to_string(),
4129        });
4130        assert!(matches!(
4131            evaluate_candidate(capability),
4132            CandidateEvaluation::Eligible(_)
4133        ));
4134
4135        let capability = resolved_capability(
4136            Some(traverse_registry::BinaryReference {
4137                format: traverse_registry::BinaryFormat::Wasm,
4138                location: String::new(),
4139                signature: None,
4140            }),
4141            Lifecycle::Active,
4142        );
4143        assert!(matches!(
4144            evaluate_candidate(capability),
4145            CandidateEvaluation::Rejected(_, RejectedCandidateReason::ArtifactMissing)
4146        ));
4147
4148        let mut capability = resolved_capability(
4149            Some(traverse_registry::BinaryReference {
4150                format: traverse_registry::BinaryFormat::Wasm,
4151                location: "artifact.wasm".to_string(),
4152                signature: None,
4153            }),
4154            Lifecycle::Active,
4155        );
4156        capability.contract.execution.preferred_targets = vec![ExecutionTarget::Cloud];
4157        assert!(matches!(
4158            evaluate_candidate(capability),
4159            CandidateEvaluation::Rejected(_, RejectedCandidateReason::NotRunnableLocally)
4160        ));
4161
4162        let mut capability = resolved_capability(
4163            Some(traverse_registry::BinaryReference {
4164                format: traverse_registry::BinaryFormat::Wasm,
4165                location: "artifact.wasm".to_string(),
4166                signature: None,
4167            }),
4168            Lifecycle::Active,
4169        );
4170        capability.contract.execution.constraints.host_api_access =
4171            HostApiAccess::ExceptionRequired;
4172        assert!(matches!(
4173            evaluate_candidate(capability),
4174            CandidateEvaluation::Rejected(_, RejectedCandidateReason::NotRunnableLocally)
4175        ));
4176
4177        let mut capability = resolved_capability(
4178            Some(traverse_registry::BinaryReference {
4179                format: traverse_registry::BinaryFormat::Wasm,
4180                location: "artifact.wasm".to_string(),
4181                signature: None,
4182            }),
4183            Lifecycle::Active,
4184        );
4185        capability.contract.execution.constraints.network_access = NetworkAccess::Required;
4186        assert!(matches!(
4187            evaluate_candidate(capability),
4188            CandidateEvaluation::Rejected(_, RejectedCandidateReason::NotRunnableLocally)
4189        ));
4190
4191        let capability = resolved_capability(
4192            Some(traverse_registry::BinaryReference {
4193                format: traverse_registry::BinaryFormat::Wasm,
4194                location: "artifact.wasm".to_string(),
4195                signature: None,
4196            }),
4197            Lifecycle::Active,
4198        );
4199        assert!(matches!(
4200            evaluate_candidate(capability),
4201            CandidateEvaluation::Eligible(_)
4202        ));
4203    }
4204
4205    #[test]
4206    fn payload_validation_covers_schema_branches() {
4207        let invalid_schema = validate_payload_against_contract(
4208            &json!({"field": "value"}),
4209            &json!("bad-schema"),
4210            super::RuntimeErrorCode::RequestInvalid,
4211            "invalid schema",
4212        );
4213        assert!(invalid_schema.is_err());
4214
4215        let wrong_object = validate_payload_against_contract(
4216            &json!("value"),
4217            &json!({"type": "object"}),
4218            super::RuntimeErrorCode::RequestInvalid,
4219            "wrong object",
4220        );
4221        assert!(wrong_object.is_err());
4222
4223        let wrong_array = validate_payload_against_contract(
4224            &json!("value"),
4225            &json!({"type": "array"}),
4226            super::RuntimeErrorCode::RequestInvalid,
4227            "wrong array",
4228        );
4229        assert!(wrong_array.is_err());
4230
4231        let typed_array = validate_payload_against_contract(
4232            &json!(["value", 2]),
4233            &json!({"type": "array", "items": {"type": "string"}}),
4234            super::RuntimeErrorCode::RequestInvalid,
4235            "typed array",
4236        );
4237        assert!(typed_array.is_err());
4238
4239        for (value, schema) in [
4240            (json!("value"), json!({"type": "integer"})),
4241            (json!("value"), json!({"type": "number"})),
4242            (json!("value"), json!({"type": "boolean"})),
4243            (json!("value"), json!({"type": "null"})),
4244        ] {
4245            let result = validate_payload_against_contract(
4246                &value,
4247                &schema,
4248                super::RuntimeErrorCode::RequestInvalid,
4249                "typed validation",
4250            );
4251            assert!(result.is_err());
4252        }
4253
4254        let missing_required = validate_payload_against_contract(
4255            &json!({}),
4256            &json!({"type": "object", "required": ["draft_id"]}),
4257            super::RuntimeErrorCode::RequestInvalid,
4258            "required field",
4259        );
4260        assert!(missing_required.is_err());
4261
4262        let property_mismatch = validate_payload_against_contract(
4263            &json!({"draft_id": 3}),
4264            &json!({"type": "object", "properties": {"draft_id": {"type": "string"}}}),
4265            super::RuntimeErrorCode::RequestInvalid,
4266            "property mismatch",
4267        );
4268        assert!(property_mismatch.is_err());
4269
4270        let array_without_item_schema = validate_payload_against_contract(
4271            &json!(["draft-1"]),
4272            &json!({"type": "array"}),
4273            super::RuntimeErrorCode::RequestInvalid,
4274            "array without item schema",
4275        );
4276        assert!(array_without_item_schema.is_ok());
4277
4278        let object_without_type = validate_payload_against_contract(
4279            &json!({"draft_id": "draft-1"}),
4280            &json!({}),
4281            super::RuntimeErrorCode::RequestInvalid,
4282            "object without type",
4283        );
4284        assert!(object_without_type.is_ok());
4285    }
4286
4287    #[test]
4288    fn runtime_mapping_helpers_cover_all_variants() {
4289        assert_eq!(
4290            map_registry_scope(RegistryScope::Public),
4291            super::RuntimeRegistryScope::Public
4292        );
4293        assert_eq!(
4294            map_registry_scope(RegistryScope::Private),
4295            super::RuntimeRegistryScope::Private
4296        );
4297        assert_eq!(
4298            map_implementation_kind(ImplementationKind::Executable),
4299            super::RuntimeImplementationKind::Executable
4300        );
4301        assert_eq!(
4302            map_implementation_kind(ImplementationKind::Workflow),
4303            super::RuntimeImplementationKind::Workflow
4304        );
4305        assert_eq!(
4306            map_lifecycle(&Lifecycle::Draft),
4307            super::RuntimeLifecycle::Draft
4308        );
4309        assert_eq!(
4310            map_lifecycle(&Lifecycle::Active),
4311            super::RuntimeLifecycle::Active
4312        );
4313        assert_eq!(
4314            map_lifecycle(&Lifecycle::Deprecated),
4315            super::RuntimeLifecycle::Deprecated
4316        );
4317        assert_eq!(
4318            map_lifecycle(&Lifecycle::Retired),
4319            super::RuntimeLifecycle::Retired
4320        );
4321        assert_eq!(
4322            map_lifecycle(&Lifecycle::Archived),
4323            super::RuntimeLifecycle::Archived
4324        );
4325    }
4326
4327    #[test]
4328    fn runtime_candidate_helper_copies_registry_shape() {
4329        let capability = resolved_capability(
4330            Some(traverse_registry::BinaryReference {
4331                format: traverse_registry::BinaryFormat::Wasm,
4332                location: "artifact.wasm".to_string(),
4333                signature: None,
4334            }),
4335            Lifecycle::Deprecated,
4336        );
4337
4338        let candidate = runtime_candidate(&capability, CandidateReason::IntentMatch);
4339
4340        assert_eq!(candidate.reason, CandidateReason::IntentMatch);
4341        assert_eq!(candidate.lifecycle, super::RuntimeLifecycle::Deprecated);
4342        assert_eq!(
4343            candidate.implementation_kind,
4344            super::RuntimeImplementationKind::Executable
4345        );
4346    }
4347
4348    #[test]
4349    fn successful_runtime_execution_reports_completed_result_status() {
4350        let mut events = super::StateEmitter::new("exec_1", "req_1");
4351        events.push(
4352            RuntimeState::LoadingRegistry,
4353            RuntimeTransitionReasonCode::RuntimeInitializationStarted,
4354            json!({}),
4355        );
4356        events.push(
4357            RuntimeState::Ready,
4358            RuntimeTransitionReasonCode::RegistryLoaded,
4359            json!({}),
4360        );
4361        events.push(
4362            RuntimeState::Discovering,
4363            RuntimeTransitionReasonCode::RequestStarted,
4364            json!({}),
4365        );
4366        events.push(
4367            RuntimeState::EvaluatingConstraints,
4368            RuntimeTransitionReasonCode::CandidatesCollected,
4369            json!({"candidate_count": 1}),
4370        );
4371        events.push(
4372            RuntimeState::Selecting,
4373            RuntimeTransitionReasonCode::ConstraintsEvaluated,
4374            json!({"eligible_candidates": 1}),
4375        );
4376        events.push(
4377            RuntimeState::Executing,
4378            RuntimeTransitionReasonCode::CandidateSelected,
4379            json!({"capability_id": "content.comments.create-comment-draft"}),
4380        );
4381        let attempt = super::AttemptContext {
4382            request: valid_request(),
4383            execution_id: "exec_1".to_string(),
4384            trace_id: "trace_exec_1".to_string(),
4385            observability: super::RuntimeObservabilityConfig::default(),
4386            artifact_verification: None,
4387            warnings: Vec::new(),
4388        };
4389        let capability = resolved_capability(
4390            Some(traverse_registry::BinaryReference {
4391                format: traverse_registry::BinaryFormat::Wasm,
4392                location: "artifact.wasm".to_string(),
4393                signature: None,
4394            }),
4395            Lifecycle::Active,
4396        );
4397
4398        let outcome = super::successful_execution_outcome(
4399            super::ExecutionContext {
4400                attempt,
4401                emitter: events,
4402                candidate_collection: super::CandidateCollectionRecord {
4403                    lookup_scope: PreferPrivate,
4404                    candidates: vec![runtime_candidate(&capability, CandidateReason::ExactMatch)],
4405                    rejected_candidates: Vec::new(),
4406                },
4407                selection: super::SelectionRecord {
4408                    status: super::SelectionStatus::Selected,
4409                    selected_capability_id: Some(capability.record.id.clone()),
4410                    selected_capability_version: Some(capability.record.version.clone()),
4411                    failure_reason: None,
4412                    remaining_candidates: Vec::new(),
4413                },
4414            },
4415            &capability,
4416            super::StartedExecution {
4417                started_at: "1970-01-01T00:00:00Z".to_string(),
4418                placement: super::resolve_placement(PlacementTarget::Local)
4419                    .unwrap_or_else(|_| unreachable!("local placement should resolve")),
4420            },
4421            json!({"draft_id": "draft-1"}),
4422            capability.contract.emits.clone(),
4423            None,
4424        );
4425
4426        assert_eq!(outcome.result.status, RuntimeResultStatus::Completed);
4427        assert_eq!(
4428            outcome.state_events.last().map(|event| event.state),
4429            Some(RuntimeState::Ready)
4430        );
4431        assert_eq!(
4432            outcome.trace.decision_evidence.selection.status,
4433            super::SelectionStatus::Selected
4434        );
4435        assert_eq!(
4436            outcome.trace.state_progression.state_events,
4437            outcome.state_events
4438        );
4439        assert_eq!(
4440            outcome.trace.terminal_outcome.runtime_status,
4441            RuntimeResultStatus::Completed
4442        );
4443        assert_eq!(outcome.trace.emitted_events, capability.contract.emits);
4444        assert_eq!(
4445            outcome.trace.state_machine_validation.status,
4446            super::RuntimeStateMachineValidationStatus::Passed
4447        );
4448    }
4449
4450    #[test]
4451    fn runtime_emits_a_token_free_terminal_event_for_invalid_requests() {
4452        let sink = Arc::new(RecordingEventSink::default());
4453        let mut request = valid_request();
4454        request.kind = "unsupported_runtime_request".to_string();
4455        request.context.identity = Some(super::security::RuntimeIdentity {
4456            subject_id: "subject_123".to_string(),
4457            actor_id: Some("actor_456".to_string()),
4458            token_reference_hash: "must-not-leak".to_string(),
4459        });
4460
4461        let outcome = Runtime::new(CapabilityRegistry::new(), NoopExecutor)
4462            .with_event_sink(sink.clone())
4463            .execute(request);
4464
4465        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4466        let events = sink
4467            .events
4468            .lock()
4469            .expect("recording sink lock must not be poisoned");
4470        assert_eq!(events.len(), 1);
4471        assert_eq!(events[0].event_type, super::RUNTIME_EXECUTION_EVENT_TYPE);
4472        assert_eq!(events[0].subject_id.as_deref(), Some("subject_123"));
4473        assert_eq!(events[0].actor_id.as_deref(), Some("actor_456"));
4474        let serialized = serde_json::to_string(&events[0]).expect("event must serialize");
4475        assert!(!serialized.contains("must-not-leak"));
4476    }
4477
4478    #[test]
4479    fn runtime_records_sink_delivery_failures_without_changing_execution_status() {
4480        let outcome = Runtime::new(CapabilityRegistry::new(), NoopExecutor)
4481            .with_event_sink(Arc::new(FailingEventSink))
4482            .execute(valid_request());
4483
4484        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4485        assert_eq!(outcome.result.warnings.len(), 1);
4486        assert_eq!(
4487            outcome.result.warnings[0].code,
4488            "runtime_event_sink_delivery_failed"
4489        );
4490        assert_eq!(outcome.trace.result.warnings, outcome.result.warnings);
4491    }
4492
4493    #[test]
4494    fn runtime_execution_produces_otel_phase_spans() {
4495        let mut registry = CapabilityRegistry::new();
4496        assert!(registry.register(public_registration()).is_ok());
4497        let runtime = Runtime::new(registry, NoopExecutor)
4498            .with_security_config(RuntimeSecurityConfig::development());
4499        let outcome = runtime.execute(valid_request());
4500        let spans = &outcome.trace.otel_trace.spans;
4501        let names: Vec<&str> = spans.iter().map(|span| span.name.as_str()).collect();
4502
4503        assert_eq!(spans.len(), 6);
4504        assert!(names.contains(&"traverse.runtime.request"));
4505        assert!(names.contains(&"traverse.request.intake"));
4506        assert!(names.contains(&"traverse.registry.lookup"));
4507        assert!(names.contains(&"traverse.contract.validation"));
4508        assert!(names.contains(&"traverse.capability.execution"));
4509        assert!(names.contains(&"traverse.trace.assembly"));
4510        assert!(
4511            spans
4512                .iter()
4513                .all(|span| span.status == super::OTelSpanStatus::Ok)
4514        );
4515        assert!(spans.iter().all(|span| {
4516            span.attributes
4517                .iter()
4518                .all(|attr| attr.key.starts_with("traverse.") || attr.key == "service.name")
4519        }));
4520    }
4521
4522    #[test]
4523    fn runtime_otel_trace_propagates_w3c_context_and_exporter_config() {
4524        let mut registry = CapabilityRegistry::new();
4525        assert!(registry.register(public_registration()).is_ok());
4526        let runtime = Runtime::new(registry, NoopExecutor)
4527            .with_security_config(RuntimeSecurityConfig::development())
4528            .with_observability_config(super::RuntimeObservabilityConfig {
4529                exporter: super::OTelExporterConfig {
4530                    endpoint: Some("http://collector:4318".to_string()),
4531                    protocol: super::OtlpProtocol::Http,
4532                },
4533                ..super::RuntimeObservabilityConfig::deterministic_test("seed-1")
4534            });
4535        let mut request = valid_request();
4536        request.context.traceparent =
4537            Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string());
4538        request.context.tracestate = Some("vendor=value".to_string());
4539
4540        let first = runtime.execute(request.clone()).trace.otel_trace;
4541        let second = runtime.execute(request).trace.otel_trace;
4542
4543        assert_eq!(first.trace_id, second.trace_id);
4544        assert_eq!(first.spans[0].span_id, second.spans[0].span_id);
4545        assert_eq!(
4546            first.parent_traceparent.as_deref(),
4547            Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
4548        );
4549        assert_eq!(first.tracestate.as_deref(), Some("vendor=value"));
4550        assert!(first.exporter.enabled);
4551        assert_eq!(
4552            first.exporter.endpoint.as_deref(),
4553            Some("http://collector:4318")
4554        );
4555        assert_eq!(
4556            runtime.observability_config().exporter.endpoint.as_deref(),
4557            Some("http://collector:4318")
4558        );
4559    }
4560
4561    #[test]
4562    fn governed_artifact_with_valid_ed25519_signature_executes() {
4563        let artifact_bytes = b"governed wasm bytes";
4564        let path = temp_artifact_path("ed25519-valid");
4565        assert!(fs::write(&path, artifact_bytes).is_ok());
4566        let signature = ed25519_signature_for(artifact_bytes);
4567        let mut registry = CapabilityRegistry::new();
4568        assert!(
4569            registry
4570                .register(governed_registration(&path, Some(signature)))
4571                .is_ok()
4572        );
4573        let runtime = Runtime::new(registry, NoopExecutor)
4574            .with_security_config(RuntimeSecurityConfig::development());
4575
4576        let outcome = runtime.execute(valid_request());
4577
4578        assert_eq!(outcome.result.status, RuntimeResultStatus::Completed);
4579        assert_eq!(
4580            outcome
4581                .trace
4582                .execution
4583                .artifact_verification
4584                .as_ref()
4585                .map(|record| record.status),
4586            Some(ArtifactVerificationStatus::Verified)
4587        );
4588        assert_eq!(
4589            outcome
4590                .trace
4591                .execution
4592                .artifact_verification
4593                .as_ref()
4594                .and_then(|record| record.scheme),
4595            Some(ArtifactVerificationScheme::Ed25519)
4596        );
4597    }
4598
4599    #[test]
4600    fn governed_artifact_without_signature_is_rejected_in_production() {
4601        let path = temp_artifact_path("missing-signature");
4602        assert!(fs::write(&path, b"unsigned governed bytes").is_ok());
4603        let mut registry = CapabilityRegistry::new();
4604        assert!(
4605            registry
4606                .register(governed_registration(&path, None))
4607                .is_ok()
4608        );
4609        let runtime = Runtime::new(registry, NoopExecutor)
4610            .with_security_config(RuntimeSecurityConfig::development());
4611
4612        let outcome = runtime.execute(valid_request());
4613
4614        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4615        assert_eq!(
4616            outcome
4617                .result
4618                .error
4619                .as_ref()
4620                .and_then(|error| error.details.get("code"))
4621                .and_then(serde_json::Value::as_str),
4622            Some("missing_signature")
4623        );
4624        assert_eq!(
4625            outcome
4626                .trace
4627                .execution
4628                .artifact_verification
4629                .as_ref()
4630                .and_then(|record| record.error_code.as_deref()),
4631            Some("missing_signature")
4632        );
4633    }
4634
4635    #[test]
4636    fn governed_artifact_without_checksum_is_rejected_before_execution() {
4637        let bytes = b"governed checksum missing";
4638        let path = temp_artifact_path("missing-checksum");
4639        assert!(fs::write(&path, bytes).is_ok());
4640        let mut registration = governed_registration(&path, Some(ed25519_signature_for(bytes)));
4641        registration.artifact.digests.binary_digest = None;
4642        let mut registry = CapabilityRegistry::new();
4643        assert!(registry.register(registration).is_ok());
4644
4645        let outcome = Runtime::new(registry, NoopExecutor).execute(valid_request());
4646
4647        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4648        assert_eq!(
4649            outcome
4650                .result
4651                .error
4652                .as_ref()
4653                .and_then(|error| error.details.get("code"))
4654                .and_then(serde_json::Value::as_str),
4655            Some("missing_checksum")
4656        );
4657    }
4658
4659    #[test]
4660    fn governed_artifact_with_mismatched_checksum_is_rejected_before_execution() {
4661        let bytes = b"governed checksum mismatch";
4662        let path = temp_artifact_path("checksum-mismatch");
4663        assert!(fs::write(&path, bytes).is_ok());
4664        let mut registration = governed_registration(&path, Some(ed25519_signature_for(bytes)));
4665        registration.artifact.digests.binary_digest = Some(
4666            "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_string(),
4667        );
4668        let mut registry = CapabilityRegistry::new();
4669        assert!(registry.register(registration).is_ok());
4670
4671        let outcome = Runtime::new(registry, NoopExecutor).execute(valid_request());
4672
4673        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4674        assert_eq!(
4675            outcome
4676                .result
4677                .error
4678                .as_ref()
4679                .and_then(|error| error.details.get("code"))
4680                .and_then(serde_json::Value::as_str),
4681            Some("checksum_mismatch")
4682        );
4683    }
4684
4685    #[test]
4686    fn local_artifact_checksum_does_not_override_local_development_policy() {
4687        let bytes = b"local checksum is advisory";
4688        let path = temp_artifact_path("local-checksum");
4689        assert!(fs::write(&path, bytes).is_ok());
4690        let mut registration = governed_registration(&path, Some(ed25519_signature_for(bytes)));
4691        registration.artifact.source.kind = SourceKind::Local;
4692        registration.artifact.digests.binary_digest = Some(
4693            "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_string(),
4694        );
4695        let mut registry = CapabilityRegistry::new();
4696        assert!(registry.register(registration).is_ok());
4697
4698        let outcome = Runtime::new(registry, NoopExecutor).execute(valid_request());
4699
4700        assert_eq!(outcome.result.status, RuntimeResultStatus::Completed);
4701    }
4702
4703    #[test]
4704    fn local_dev_unsigned_artifact_warns_and_executes_in_development_mode() {
4705        let mut registry = CapabilityRegistry::new();
4706        assert!(registry.register(public_registration()).is_ok());
4707        let runtime = Runtime::new(registry, NoopExecutor)
4708            .with_security_config(RuntimeSecurityConfig::development());
4709
4710        let outcome = runtime.execute(valid_request());
4711
4712        assert_eq!(outcome.result.status, RuntimeResultStatus::Completed);
4713        assert_eq!(
4714            outcome
4715                .result
4716                .warnings
4717                .first()
4718                .map(|warning| warning.code.as_str()),
4719            Some("unsigned_local_dev_artifact")
4720        );
4721        assert_eq!(
4722            outcome
4723                .trace
4724                .execution
4725                .artifact_verification
4726                .as_ref()
4727                .map(|record| record.status),
4728            Some(ArtifactVerificationStatus::Warning)
4729        );
4730    }
4731
4732    #[test]
4733    fn default_security_config_is_production() {
4734        assert_eq!(
4735            RuntimeSecurityConfig::default(),
4736            RuntimeSecurityConfig::production()
4737        );
4738        assert_ne!(
4739            RuntimeSecurityConfig::default(),
4740            RuntimeSecurityConfig::development()
4741        );
4742    }
4743
4744    #[test]
4745    fn unsigned_local_artifact_rejected_under_default_security_config() {
4746        let mut registry = CapabilityRegistry::new();
4747        assert!(registry.register(public_registration()).is_ok());
4748        // No explicit security config: the default (Production) posture must
4749        // reject the unsigned local artifact before execution.
4750        let runtime = Runtime::new(registry, NoopExecutor);
4751
4752        let outcome = runtime.execute(valid_request());
4753
4754        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4755        assert_eq!(
4756            outcome
4757                .result
4758                .error
4759                .as_ref()
4760                .and_then(|error| error.details.get("code"))
4761                .and_then(serde_json::Value::as_str),
4762            Some("missing_signature")
4763        );
4764    }
4765
4766    #[test]
4767    fn governed_artifact_rejects_placeholder_sigstore_bundle() {
4768        let path = temp_artifact_path("sigstore-valid");
4769        assert!(fs::write(&path, b"sigstore governed bytes").is_ok());
4770        let signature = ArtifactSignature {
4771            scheme: ArtifactSignatureScheme::Sigstore,
4772            public_key_hex: None,
4773            signature_hex: None,
4774            sigstore_bundle_ref: Some("verified://bundle/comment-draft".to_string()),
4775        };
4776        let mut registry = CapabilityRegistry::new();
4777        assert!(
4778            registry
4779                .register(governed_registration(&path, Some(signature)))
4780                .is_ok()
4781        );
4782        let runtime = Runtime::new(registry, NoopExecutor)
4783            .with_security_config(RuntimeSecurityConfig::production());
4784
4785        let outcome = runtime.execute(valid_request());
4786
4787        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4788        assert_eq!(
4789            outcome
4790                .result
4791                .error
4792                .as_ref()
4793                .and_then(|error| error.details.get("code"))
4794                .and_then(serde_json::Value::as_str),
4795            Some("sigstore_unreachable")
4796        );
4797    }
4798
4799    #[test]
4800    fn jwt_identity_is_derived_and_raw_token_is_not_traced() {
4801        let mut registry = CapabilityRegistry::new();
4802        assert!(registry.register(public_registration()).is_ok());
4803        let runtime = Runtime::new(registry, NoopExecutor)
4804            .with_security_config(RuntimeSecurityConfig::development());
4805        let mut request = valid_request();
4806        let token = make_jwt_with_actor("alice", "workflow-agent");
4807        request.context.caller = Some(token.clone());
4808
4809        let outcome = runtime.execute(request);
4810        let trace_json = serde_json::to_string(&outcome.trace).unwrap_or_default();
4811
4812        assert_eq!(outcome.result.status, RuntimeResultStatus::Completed);
4813        assert!(!trace_json.contains(&token));
4814        assert_eq!(
4815            outcome
4816                .trace
4817                .request
4818                .context
4819                .identity
4820                .as_ref()
4821                .map(|identity| identity.subject_id.as_str()),
4822            Some("alice")
4823        );
4824        assert_eq!(
4825            outcome
4826                .trace
4827                .execution
4828                .identity
4829                .as_ref()
4830                .and_then(|identity| identity.actor_id.as_deref()),
4831            Some("workflow-agent")
4832        );
4833        assert!(
4834            outcome
4835                .state_events
4836                .iter()
4837                .any(|event| event.details.to_string().contains("alice"))
4838        );
4839    }
4840
4841    #[test]
4842    #[allow(clippy::too_many_lines)]
4843    fn security_branch_guards_cover_malformed_signatures_sigstore_and_jwts() {
4844        let capability = governed_resolved_capability(None);
4845        let missing_public_key = ArtifactSignature {
4846            scheme: ArtifactSignatureScheme::Ed25519,
4847            public_key_hex: None,
4848            signature_hex: Some("00".to_string()),
4849            sigstore_bundle_ref: None,
4850        };
4851        let missing_signature = ArtifactSignature {
4852            scheme: ArtifactSignatureScheme::Ed25519,
4853            public_key_hex: Some("00".to_string()),
4854            signature_hex: None,
4855            sigstore_bundle_ref: None,
4856        };
4857        let bad_public_hex = ArtifactSignature {
4858            scheme: ArtifactSignatureScheme::Ed25519,
4859            public_key_hex: Some("abc".to_string()),
4860            signature_hex: Some("00".to_string()),
4861            sigstore_bundle_ref: None,
4862        };
4863        let bad_signature_hex = ArtifactSignature {
4864            scheme: ArtifactSignatureScheme::Ed25519,
4865            public_key_hex: Some("00".to_string()),
4866            signature_hex: Some("zz".to_string()),
4867            sigstore_bundle_ref: None,
4868        };
4869        let short_public_key = ArtifactSignature {
4870            scheme: ArtifactSignatureScheme::Ed25519,
4871            public_key_hex: Some("00".to_string()),
4872            signature_hex: Some("00".repeat(64)),
4873            sigstore_bundle_ref: None,
4874        };
4875        let short_signature = ArtifactSignature {
4876            scheme: ArtifactSignatureScheme::Ed25519,
4877            public_key_hex: Some("00".repeat(32)),
4878            signature_hex: Some("00".to_string()),
4879            sigstore_bundle_ref: None,
4880        };
4881        let invalid_public_key = ArtifactSignature {
4882            scheme: ArtifactSignatureScheme::Ed25519,
4883            public_key_hex: Some("ff".repeat(32)),
4884            signature_hex: Some("00".repeat(64)),
4885            sigstore_bundle_ref: None,
4886        };
4887        let mismatch = ed25519_signature_for(b"other bytes");
4888        for signature in [
4889            missing_public_key,
4890            missing_signature,
4891            bad_public_hex,
4892            bad_signature_hex,
4893            short_public_key,
4894            short_signature,
4895            invalid_public_key,
4896            mismatch,
4897        ] {
4898            let mut capability = capability.clone();
4899            capability.artifact.binary = Some(BinaryReference {
4900                format: BinaryFormat::Wasm,
4901                location: "unused.wasm".to_string(),
4902                signature: Some(signature),
4903            });
4904            let error = verify_artifact(
4905                &capability,
4906                b"artifact bytes",
4907                &RuntimeSecurityConfig::production(),
4908            );
4909            assert!(matches!(
4910                error,
4911                Err(ArtifactVerificationFailure::SignatureVerificationFailed(_))
4912            ));
4913            let failure = error.err();
4914            assert_eq!(
4915                failure.as_ref().map(ArtifactVerificationFailure::code),
4916                Some("signature_verification_failed")
4917            );
4918            assert_eq!(
4919                failure
4920                    .as_ref()
4921                    .and_then(|item| item.record().error_code.as_deref()),
4922                Some("signature_verification_failed")
4923            );
4924        }
4925
4926        let mut capability = capability.clone();
4927        capability.artifact.binary = Some(BinaryReference {
4928            format: BinaryFormat::Wasm,
4929            location: "unused.wasm".to_string(),
4930            signature: Some(ArtifactSignature {
4931                scheme: ArtifactSignatureScheme::Sigstore,
4932                public_key_hex: None,
4933                signature_hex: None,
4934                sigstore_bundle_ref: Some("https://rekor.example/bundle".to_string()),
4935            }),
4936        });
4937        let error = verify_artifact(
4938            &capability,
4939            b"artifact bytes",
4940            &RuntimeSecurityConfig::production(),
4941        );
4942        assert!(matches!(
4943            error,
4944            Err(ArtifactVerificationFailure::SigstoreUnreachable(_))
4945        ));
4946        assert_eq!(
4947            error.err().as_ref().map(ArtifactVerificationFailure::code),
4948            Some("sigstore_unreachable")
4949        );
4950
4951        let bad_payload = base64url_encode(b"{");
4952        let no_subject = base64url_encode(b"{}");
4953        assert!(derive_identity_from_jwt("not-a-jwt").is_none());
4954        assert!(derive_identity_from_jwt("a.b.c.d").is_none());
4955        assert!(derive_identity_from_jwt("a.abc=.c").is_none());
4956        assert!(derive_identity_from_jwt("a.*.c").is_none());
4957        assert!(derive_identity_from_jwt("a.a.c").is_none());
4958        assert!(derive_identity_from_jwt("a.-___.c").is_none());
4959        assert!(derive_identity_from_jwt(&format!("a.{bad_payload}.c")).is_none());
4960        assert!(derive_identity_from_jwt(&format!("a.{no_subject}.c")).is_none());
4961        assert_eq!(base64url_encode(b""), "");
4962    }
4963
4964    #[test]
4965    fn runtime_security_config_accessor_returns_current_config() {
4966        let runtime = Runtime::new(CapabilityRegistry::new(), NoopExecutor)
4967            .with_security_config(RuntimeSecurityConfig::production());
4968
4969        assert_eq!(
4970            runtime.security_config(),
4971            &RuntimeSecurityConfig::production()
4972        );
4973    }
4974
4975    #[test]
4976    fn signed_artifact_missing_from_disk_fails_before_execution() {
4977        let path = temp_artifact_path("missing-from-disk");
4978        let signature = ed25519_signature_for(b"governed wasm bytes");
4979        let mut registry = CapabilityRegistry::new();
4980        assert!(
4981            registry
4982                .register(governed_registration(&path, Some(signature)))
4983                .is_ok()
4984        );
4985        let runtime = Runtime::new(registry, NoopExecutor)
4986            .with_security_config(RuntimeSecurityConfig::production());
4987
4988        let outcome = runtime.execute(valid_request());
4989
4990        assert_eq!(outcome.result.status, RuntimeResultStatus::Error);
4991        assert_eq!(
4992            outcome
4993                .result
4994                .error
4995                .as_ref()
4996                .and_then(|error| error.details.get("code"))
4997                .and_then(serde_json::Value::as_str),
4998            Some("artifact_load_failed")
4999        );
5000    }
5001
5002    #[test]
5003    fn otel_timestamp_helpers_default_without_transitions() {
5004        let transitions = Vec::new();
5005
5006        assert_eq!(
5007            super::first_transition_time(&transitions),
5008            "1970-01-01T00:00:00Z"
5009        );
5010        assert_eq!(
5011            super::last_transition_time(&transitions),
5012            "1970-01-01T00:00:00Z"
5013        );
5014        assert_eq!(
5015            super::phase_started_at(&transitions, 0),
5016            "1970-01-01T00:00:00Z"
5017        );
5018        assert_eq!(
5019            super::phase_ended_at(&transitions, 0),
5020            "1970-01-01T00:00:00Z"
5021        );
5022    }
5023
5024    #[test]
5025    fn state_emitter_records_transition_validation_and_rejects_invalid_moves() {
5026        let mut events = super::StateEmitter::new("exec_1", "req_1");
5027
5028        assert!(events.try_push(
5029            RuntimeState::LoadingRegistry,
5030            RuntimeTransitionReasonCode::RuntimeInitializationStarted,
5031            json!({})
5032        ));
5033        assert!(!events.try_push(
5034            RuntimeState::Completed,
5035            RuntimeTransitionReasonCode::ExecutionSucceeded,
5036            json!({})
5037        ));
5038
5039        let finished = events.finish();
5040
5041        assert_eq!(finished.events.len(), 1);
5042        assert_eq!(finished.transitions.len(), 1);
5043        assert_eq!(
5044            finished.validation.status,
5045            super::RuntimeStateMachineValidationStatus::Failed
5046        );
5047        assert_eq!(finished.validation.violations.len(), 1);
5048    }
5049
5050    #[test]
5051    fn pre_execution_failure_from_constraint_phase_uses_constraint_reason() {
5052        let mut events = super::StateEmitter::new("exec_1", "req_1");
5053        events.push(
5054            RuntimeState::LoadingRegistry,
5055            RuntimeTransitionReasonCode::RuntimeInitializationStarted,
5056            json!({}),
5057        );
5058        events.push(
5059            RuntimeState::Ready,
5060            RuntimeTransitionReasonCode::RegistryLoaded,
5061            json!({}),
5062        );
5063        events.push(
5064            RuntimeState::Discovering,
5065            RuntimeTransitionReasonCode::RequestStarted,
5066            json!({}),
5067        );
5068        events.push(
5069            RuntimeState::EvaluatingConstraints,
5070            RuntimeTransitionReasonCode::CandidatesCollected,
5071            json!({"candidate_count": 1}),
5072        );
5073
5074        let outcome = super::pre_execution_failure_outcome(
5075            super::ExecutionContext {
5076                attempt: super::AttemptContext {
5077                    request: valid_request(),
5078                    execution_id: "exec_1".to_string(),
5079                    trace_id: "trace_exec_1".to_string(),
5080                    observability: super::RuntimeObservabilityConfig::default(),
5081                    artifact_verification: None,
5082                    warnings: Vec::new(),
5083                },
5084                emitter: events,
5085                candidate_collection: super::CandidateCollectionRecord {
5086                    lookup_scope: PreferPrivate,
5087                    candidates: Vec::new(),
5088                    rejected_candidates: Vec::new(),
5089                },
5090                selection: super::SelectionRecord {
5091                    status: super::SelectionStatus::NoMatch,
5092                    selected_capability_id: None,
5093                    selected_capability_version: None,
5094                    failure_reason: Some(super::SelectionFailureReason::NotRunnable),
5095                    remaining_candidates: Vec::new(),
5096                },
5097            },
5098            super::PreExecutionFailure {
5099                artifact_ref: None,
5100                failure_reason: super::ExecutionFailureReason::ArtifactMissing,
5101                placement: super::placement_not_attempted(
5102                    PlacementTarget::Local,
5103                    super::PlacementDecisionReason::SelectionNotReached,
5104                ),
5105                error: super::runtime_error(
5106                    super::RuntimeErrorCode::CapabilityNotRunnable,
5107                    "not runnable",
5108                    json!({}),
5109                ),
5110                artifact_verification: None,
5111            },
5112        );
5113
5114        assert_eq!(
5115            outcome.trace.state_transitions[4].reason_code,
5116            RuntimeTransitionReasonCode::ConstraintValidationFailed
5117        );
5118    }
5119
5120    #[test]
5121    fn detail_object_wraps_non_object_values() {
5122        let wrapped = super::detail_object(json!("value"));
5123
5124        assert_eq!(wrapped.get("value"), Some(&json!("value")));
5125    }
5126
5127    #[test]
5128    fn collect_candidates_handles_missing_target_and_public_discovery() {
5129        let runtime = super::Runtime::new(CapabilityRegistry::new(), NoopExecutor)
5130            .with_security_config(RuntimeSecurityConfig::development());
5131        let mut request = valid_request();
5132        request.intent.capability_id = None;
5133        request.intent.capability_version = None;
5134        request.intent.intent_key = None;
5135
5136        assert!(
5137            runtime
5138                .collect_candidates(&request, CandidateReason::IntentMatch)
5139                .is_empty()
5140        );
5141
5142        let mut registry = CapabilityRegistry::new();
5143        let outcome = registry.register(public_registration());
5144        assert!(outcome.is_ok());
5145
5146        let runtime = super::Runtime::new(registry, NoopExecutor)
5147            .with_security_config(RuntimeSecurityConfig::development());
5148        let mut request = valid_request();
5149        request.lookup.scope = PublicOnly;
5150        request.intent.capability_id = None;
5151        request.intent.capability_version = None;
5152        request.intent.intent_key = Some("content.comments.create-comment-draft".to_string());
5153
5154        let candidates = runtime.collect_candidates(&request, CandidateReason::IntentMatch);
5155
5156        assert_eq!(candidates.len(), 1);
5157        assert_eq!(candidates[0].record.scope, RegistryScope::Public);
5158    }
5159
5160    #[test]
5161    fn noop_executor_returns_structured_output() {
5162        let executor = NoopExecutor;
5163        let capability = resolved_capability(
5164            Some(BinaryReference {
5165                format: BinaryFormat::Wasm,
5166                location: "artifact.wasm".to_string(),
5167                signature: None,
5168            }),
5169            Lifecycle::Active,
5170        );
5171
5172        let result = executor.execute(&capability, &json!({}));
5173
5174        assert_eq!(
5175            result,
5176            Ok(super::LocalExecutionOutput {
5177                value: json!({"draft_id": "draft"}),
5178                emitted_events: Vec::new(),
5179            })
5180        );
5181    }
5182
5183    #[test]
5184    fn browser_subscription_validation_covers_guard_branches() {
5185        let mut request = valid_browser_subscription_request();
5186        request.kind = "wrong".to_string();
5187        assert_eq!(
5188            validate_browser_subscription_request(&request).map(|error| error.code),
5189            Some(BrowserRuntimeSubscriptionErrorCode::InvalidRequest)
5190        );
5191
5192        let mut request = valid_browser_subscription_request();
5193        request.schema_version = "9.9.9".to_string();
5194        assert_eq!(
5195            validate_browser_subscription_request(&request).map(|error| error.code),
5196            Some(BrowserRuntimeSubscriptionErrorCode::InvalidRequest)
5197        );
5198
5199        let mut request = valid_browser_subscription_request();
5200        request.governing_spec = "wrong-spec".to_string();
5201        assert_eq!(
5202            validate_browser_subscription_request(&request).map(|error| error.code),
5203            Some(BrowserRuntimeSubscriptionErrorCode::InvalidRequest)
5204        );
5205    }
5206
5207    #[test]
5208    fn browser_subscription_reports_not_found_for_mismatched_target() {
5209        let outcome = runtime_outcome_for_browser_subscription();
5210        let request = BrowserRuntimeSubscriptionRequest {
5211            request_id: Some("req_other".to_string()),
5212            execution_id: None,
5213            ..valid_browser_subscription_request()
5214        };
5215
5216        let messages = browser_subscription_messages(&request, &outcome);
5217        assert_eq!(
5218            messages,
5219            vec![BrowserRuntimeSubscriptionMessage::Error(
5220                super::BrowserRuntimeSubscriptionErrorMessage {
5221                    kind: "browser_runtime_subscription_error".to_string(),
5222                    schema_version: "1.0.0".to_string(),
5223                    sequence: 0,
5224                    code: BrowserRuntimeSubscriptionErrorCode::NotFound,
5225                    message: "subscription target did not match the supplied execution outcome"
5226                        .to_string(),
5227                }
5228            )]
5229        );
5230    }
5231
5232    #[test]
5233    fn browser_subscription_target_helper_covers_fallback_branch() {
5234        let outcome = runtime_outcome_for_browser_subscription();
5235        let invalid_request = BrowserRuntimeSubscriptionRequest {
5236            request_id: Some("req_123".to_string()),
5237            execution_id: Some(outcome.result.execution_id.clone()),
5238            ..valid_browser_subscription_request()
5239        };
5240
5241        assert!(!subscription_targets_outcome(&invalid_request, &outcome));
5242    }
5243
5244    fn valid_request() -> RuntimeRequest {
5245        RuntimeRequest {
5246            kind: "runtime_request".to_string(),
5247            schema_version: "1.0.0".to_string(),
5248            request_id: "req_123".to_string(),
5249            intent: RuntimeIntent {
5250                capability_id: Some("content.comments.create-comment-draft".to_string()),
5251                capability_version: Some("1.0.0".to_string()),
5252                version_range: None,
5253                intent_key: Some("content.comments.create-comment-draft".to_string()),
5254            },
5255            input: json!({"comment_text": "Hello", "resource_id": "res-1"}),
5256            lookup: RuntimeLookup {
5257                scope: RuntimeLookupScope::PreferPrivate,
5258                allow_ambiguity: false,
5259            },
5260            context: RuntimeContext {
5261                requested_target: PlacementTarget::Local,
5262                correlation_id: None,
5263                caller: None,
5264                traceparent: None,
5265                tracestate: None,
5266                metadata: None,
5267                identity: None,
5268            },
5269            governing_spec: "006-runtime-request-execution".to_string(),
5270        }
5271    }
5272
5273    fn valid_browser_subscription_request() -> BrowserRuntimeSubscriptionRequest {
5274        BrowserRuntimeSubscriptionRequest {
5275            kind: "browser_runtime_subscription_request".to_string(),
5276            schema_version: "1.0.0".to_string(),
5277            governing_spec: "013-browser-runtime-subscription".to_string(),
5278            request_id: Some("req_123".to_string()),
5279            execution_id: None,
5280        }
5281    }
5282
5283    fn runtime_outcome_for_browser_subscription() -> super::RuntimeExecutionOutcome {
5284        let mut registry = CapabilityRegistry::new();
5285        assert!(registry.register(public_registration()).is_ok());
5286        let runtime = Runtime::new(registry, NoopExecutor)
5287            .with_security_config(RuntimeSecurityConfig::development());
5288        runtime.execute(valid_request())
5289    }
5290
5291    fn governed_registration(
5292        path: &std::path::Path,
5293        signature: Option<ArtifactSignature>,
5294    ) -> CapabilityRegistration {
5295        let mut registration = public_registration();
5296        registration.contract_path = "contracts/approved/comment-draft.json".to_string();
5297        registration.artifact.source = SourceReference {
5298            kind: SourceKind::Git,
5299            location: "https://github.com/enricopiovesan/Traverse".to_string(),
5300        };
5301        registration.artifact.binary = Some(BinaryReference {
5302            format: BinaryFormat::Wasm,
5303            location: path.display().to_string(),
5304            signature,
5305        });
5306        let bytes = fs::read(path).unwrap_or_default();
5307        let hex_digest = Sha256::digest(bytes)
5308            .iter()
5309            .fold(String::new(), |mut acc, byte| {
5310                let _ = write!(acc, "{byte:02x}");
5311                acc
5312            });
5313        registration.artifact.digests.binary_digest = Some(format!("sha256:{hex_digest}"));
5314        registration
5315    }
5316
5317    fn governed_resolved_capability(signature: Option<ArtifactSignature>) -> ResolvedCapability {
5318        let mut capability = resolved_capability(
5319            Some(BinaryReference {
5320                format: BinaryFormat::Wasm,
5321                location: "unused.wasm".to_string(),
5322                signature,
5323            }),
5324            Lifecycle::Active,
5325        );
5326        capability.record.contract_path = "contracts/approved/comment-draft.json".to_string();
5327        capability.artifact.source = SourceReference {
5328            kind: SourceKind::Git,
5329            location: "https://github.com/enricopiovesan/Traverse".to_string(),
5330        };
5331        capability
5332    }
5333
5334    fn ed25519_signature_for(bytes: &[u8]) -> ArtifactSignature {
5335        let signing_key = SigningKey::from_bytes(&[7_u8; 32]);
5336        let signature = signing_key.sign(bytes);
5337        ArtifactSignature {
5338            scheme: ArtifactSignatureScheme::Ed25519,
5339            public_key_hex: Some(hex_encode(signing_key.verifying_key().as_bytes())),
5340            signature_hex: Some(hex_encode(&signature.to_bytes())),
5341            sigstore_bundle_ref: None,
5342        }
5343    }
5344
5345    fn temp_artifact_path(name: &str) -> std::path::PathBuf {
5346        std::env::temp_dir().join(format!(
5347            "traverse-runtime-{name}-{}-{}.wasm",
5348            std::process::id(),
5349            "req_123"
5350        ))
5351    }
5352
5353    fn make_jwt_with_actor(subject_id: &str, actor_id: &str) -> String {
5354        let header = base64url_encode(br#"{"alg":"none","typ":"JWT"}"#);
5355        let payload = serde_json::json!({
5356            "sub": subject_id,
5357            "act": {"sub": actor_id}
5358        });
5359        format!(
5360            "{}.{}.signature",
5361            header,
5362            base64url_encode(payload.to_string().as_bytes())
5363        )
5364    }
5365
5366    fn hex_encode(bytes: &[u8]) -> String {
5367        let mut output = String::with_capacity(bytes.len() * 2);
5368        for byte in bytes {
5369            output.push(char::from(HEX_TABLE[(byte >> 4) as usize]));
5370            output.push(char::from(HEX_TABLE[(byte & 0x0f) as usize]));
5371        }
5372        output
5373    }
5374
5375    fn base64url_encode(bytes: &[u8]) -> String {
5376        const TABLE: &[u8; 64] =
5377            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
5378        let mut out = String::new();
5379        let mut index = 0;
5380        while index + 3 <= bytes.len() {
5381            let chunk = &bytes[index..index + 3];
5382            let n = (u32::from(chunk[0]) << 16) | (u32::from(chunk[1]) << 8) | u32::from(chunk[2]);
5383            out.push(char::from(TABLE[((n >> 18) & 0x3f) as usize]));
5384            out.push(char::from(TABLE[((n >> 12) & 0x3f) as usize]));
5385            out.push(char::from(TABLE[((n >> 6) & 0x3f) as usize]));
5386            out.push(char::from(TABLE[(n & 0x3f) as usize]));
5387            index += 3;
5388        }
5389        match bytes.len() - index {
5390            1 => {
5391                let n = u32::from(bytes[index]) << 16;
5392                out.push(char::from(TABLE[((n >> 18) & 0x3f) as usize]));
5393                out.push(char::from(TABLE[((n >> 12) & 0x3f) as usize]));
5394            }
5395            2 => {
5396                let n = (u32::from(bytes[index]) << 16) | (u32::from(bytes[index + 1]) << 8);
5397                out.push(char::from(TABLE[((n >> 18) & 0x3f) as usize]));
5398                out.push(char::from(TABLE[((n >> 12) & 0x3f) as usize]));
5399                out.push(char::from(TABLE[((n >> 6) & 0x3f) as usize]));
5400            }
5401            _ => {}
5402        }
5403        out
5404    }
5405
5406    fn public_registration() -> CapabilityRegistration {
5407        CapabilityRegistration {
5408            scope: RegistryScope::Public,
5409            contract: test_contract(Lifecycle::Active),
5410            contract_path: "registry/contract.json".to_string(),
5411            artifact: test_artifact(Some(BinaryReference {
5412                format: BinaryFormat::Wasm,
5413                location: "artifact.wasm".to_string(),
5414                signature: None,
5415            })),
5416            registered_at: "2026-03-27T00:00:00Z".to_string(),
5417            tags: vec!["comments".to_string()],
5418            composability: ComposabilityMetadata {
5419                kind: CompositionKind::Atomic,
5420                patterns: vec![CompositionPattern::Sequential],
5421                provides: vec!["draft".to_string()],
5422                requires: vec!["authenticated-user".to_string()],
5423            },
5424            governing_spec: "005-capability-registry".to_string(),
5425            validator_version: "0.1.0".to_string(),
5426        }
5427    }
5428
5429    fn resolved_capability(
5430        binary: Option<traverse_registry::BinaryReference>,
5431        lifecycle: Lifecycle,
5432    ) -> ResolvedCapability {
5433        ResolvedCapability {
5434            contract: test_contract(lifecycle.clone()),
5435            record: test_record(lifecycle.clone()),
5436            artifact: test_artifact(binary),
5437            index_entry: test_index_entry(lifecycle),
5438        }
5439    }
5440
5441    fn test_contract(lifecycle: Lifecycle) -> traverse_contracts::CapabilityContract {
5442        traverse_contracts::CapabilityContract {
5443            kind: "capability_contract".to_string(),
5444            schema_version: "1.0.0".to_string(),
5445            id: "content.comments.create-comment-draft".to_string(),
5446            namespace: "content.comments".to_string(),
5447            name: "create-comment-draft".to_string(),
5448            version: "1.0.0".to_string(),
5449            lifecycle,
5450            owner: Owner {
5451                team: "comments".to_string(),
5452                contact: "comments@example.com".to_string(),
5453            },
5454            summary: "Create a comment draft for a resource".to_string(),
5455            description: "Creates a draft comment and returns the generated draft identifier."
5456                .to_string(),
5457            inputs: SchemaContainer {
5458                schema: json!({"type": "object"}),
5459            },
5460            outputs: SchemaContainer {
5461                schema: json!({"type": "object"}),
5462            },
5463            preconditions: Vec::new(),
5464            postconditions: Vec::new(),
5465            side_effects: vec![traverse_contracts::SideEffect {
5466                kind: traverse_contracts::SideEffectKind::MemoryOnly,
5467                description: "Produces a draft representation in memory.".to_string(),
5468            }],
5469            emits: Vec::new(),
5470            consumes: Vec::new(),
5471            permissions: Vec::new(),
5472            execution: Execution {
5473                binary_format: ContractBinaryFormat::Wasm,
5474                entrypoint: Entrypoint {
5475                    kind: EntrypointKind::WasiCommand,
5476                    command: "run".to_string(),
5477                },
5478                preferred_targets: vec![ExecutionTarget::Local],
5479                constraints: ExecutionConstraints {
5480                    host_api_access: HostApiAccess::None,
5481                    network_access: NetworkAccess::Forbidden,
5482                    filesystem_access: FilesystemAccess::None,
5483                },
5484            },
5485            policies: Vec::new(),
5486            dependencies: Vec::new(),
5487            provenance: Provenance {
5488                source: ProvenanceSource::Greenfield,
5489                author: "Enrico Piovesan".to_string(),
5490                created_at: "2026-03-27T00:00:00Z".to_string(),
5491                spec_ref: Some("006-runtime-request-execution".to_string()),
5492                adr_refs: Vec::new(),
5493                exception_refs: Vec::new(),
5494            },
5495            evidence: Vec::new(),
5496            service_type: ServiceType::Stateless,
5497            permitted_targets: vec![
5498                ExecutionTarget::Local,
5499                ExecutionTarget::Cloud,
5500                ExecutionTarget::Edge,
5501                ExecutionTarget::Device,
5502            ],
5503            event_trigger: None,
5504            connector_requirements: Vec::new(),
5505            state_schema: None,
5506        }
5507    }
5508
5509    fn test_record(lifecycle: Lifecycle) -> CapabilityRegistryRecord {
5510        CapabilityRegistryRecord {
5511            scope: RegistryScope::Private,
5512            id: "content.comments.create-comment-draft".to_string(),
5513            version: "1.0.0".to_string(),
5514            lifecycle,
5515            owner: Owner {
5516                team: "comments".to_string(),
5517                contact: "comments@example.com".to_string(),
5518            },
5519            contract_path: "registry/contract.json".to_string(),
5520            contract_digest: "digest".to_string(),
5521            implementation_kind: ImplementationKind::Executable,
5522            artifact_ref: "artifact:content.comments.create-comment-draft:1.0.0".to_string(),
5523            registered_at: "2026-03-27T00:00:00Z".to_string(),
5524            provenance: RegistryProvenance {
5525                source: "test".to_string(),
5526                author: "Enrico Piovesan".to_string(),
5527                created_at: "2026-03-27T00:00:00Z".to_string(),
5528            },
5529            evidence: traverse_registry::RegistrationEvidence {
5530                evidence_id: "evidence".to_string(),
5531                artifact_ref: "artifact:content.comments.create-comment-draft:1.0.0".to_string(),
5532                capability_id: "content.comments.create-comment-draft".to_string(),
5533                capability_version: "1.0.0".to_string(),
5534                scope: RegistryScope::Private,
5535                governing_spec: "005-capability-registry".to_string(),
5536                validator_version: "0.1.0".to_string(),
5537                produced_at: "2026-03-27T00:00:00Z".to_string(),
5538                result: traverse_registry::RegistrationResult::Passed,
5539            },
5540        }
5541    }
5542
5543    fn test_artifact(
5544        binary: Option<traverse_registry::BinaryReference>,
5545    ) -> CapabilityArtifactRecord {
5546        CapabilityArtifactRecord {
5547            artifact_ref: "artifact:content.comments.create-comment-draft:1.0.0".to_string(),
5548            implementation_kind: ImplementationKind::Executable,
5549            source: SourceReference {
5550                kind: SourceKind::Git,
5551                location: "https://github.com/enricopiovesan/cogolo".to_string(),
5552            },
5553            binary,
5554            workflow_ref: None,
5555            digests: ArtifactDigests {
5556                source_digest: "src-digest".to_string(),
5557                binary_digest: Some("bin-digest".to_string()),
5558            },
5559            provenance: RegistryProvenance {
5560                source: "test".to_string(),
5561                author: "Enrico Piovesan".to_string(),
5562                created_at: "2026-03-27T00:00:00Z".to_string(),
5563            },
5564        }
5565    }
5566
5567    fn test_index_entry(lifecycle: Lifecycle) -> DiscoveryIndexEntry {
5568        DiscoveryIndexEntry {
5569            scope: RegistryScope::Private,
5570            id: "content.comments.create-comment-draft".to_string(),
5571            version: "1.0.0".to_string(),
5572            lifecycle,
5573            owner: Owner {
5574                team: "comments".to_string(),
5575                contact: "comments@example.com".to_string(),
5576            },
5577            summary: "Create a comment draft for a resource".to_string(),
5578            tags: vec!["comments".to_string()],
5579            permissions: Vec::new(),
5580            emits: Vec::new(),
5581            consumes: Vec::new(),
5582            implementation_kind: ImplementationKind::Executable,
5583            composability: traverse_registry::ComposabilityMetadata {
5584                kind: traverse_registry::CompositionKind::Atomic,
5585                patterns: vec![traverse_registry::CompositionPattern::Sequential],
5586                provides: vec!["draft".to_string()],
5587                requires: vec!["authenticated-user".to_string()],
5588            },
5589            artifact_ref: "artifact:content.comments.create-comment-draft:1.0.0".to_string(),
5590            registered_at: "2026-03-27T00:00:00Z".to_string(),
5591        }
5592    }
5593
5594    fn write_runtime_workspace_app_state_fixture(workspace_root: &Path, workspace_id: &str) {
5595        let repo = repo_root();
5596        let state_path = workspace_root
5597            .join(".traverse/workspaces")
5598            .join(workspace_id)
5599            .join("apps/expedition.readiness/1.0.0/registration.json");
5600        fs::create_dir_all(state_path.parent().expect("state path must have parent"))
5601            .expect("workspace state parent should create");
5602        fs::write(
5603            state_path,
5604            serde_json::to_string_pretty(&serde_json::json!({
5605                "status": "registered",
5606                "workspace_id": workspace_id,
5607                "app_id": "expedition.readiness",
5608                "app_version": "1.0.0",
5609                "schema_version": "1.0.0",
5610                "manifest_path": repo.join("examples/applications/expedition-readiness/app.manifest.json").display().to_string(),
5611                "manifest_digest": "sha256:test-manifest",
5612                "bundle_digest": "sha256:test-bundle",
5613                "component_ids": [
5614                    "expedition.readiness.capture-expedition-objective-component",
5615                    "expedition.readiness.interpret-expedition-intent-component",
5616                    "expedition.readiness.assess-conditions-summary-component",
5617                    "expedition.readiness.validate-team-readiness-component",
5618                    "expedition.readiness.assemble-expedition-plan-component"
5619                ],
5620                "workflow_ids": ["expedition.planning.plan-expedition"],
5621                "components": runtime_workspace_components_json(&repo),
5622                "workflows": [{
5623                    "workflow_id": "expedition.planning.plan-expedition",
5624                    "workflow_version": "1.0.0",
5625                    "workflow_digest": "sha256:test-workflow",
5626                    "path": repo.join("workflows/examples/expedition/plan-expedition/workflow.json").display().to_string()
5627                }],
5628                "model_dependencies": [{
5629                    "interface_id": "traverse.inference.generate",
5630                    "version_range": "^1.0",
5631                    "selection_policy": {
5632                        "strategy": "priority",
5633                        "allow_fallback": true
5634                    },
5635                    "required_capabilities": ["text_generation"],
5636                    "minimum_context_window": 8192,
5637                    "candidates": [{
5638                        "candidate_id": "ollama-llama-3-2-readiness",
5639                        "provider_capability_id": "traverse.inference.generate",
5640                        "provider_implementation_id": "ollama.local.generate",
5641                        "model_identifier": "llama3.2:3b",
5642                        "placement_target": "local",
5643                        "priority": 10,
5644                        "required_provider_config_keys": ["ollama_base_url"],
5645                        "metadata": {
5646                            "implementation_kind": "real_local_provider",
5647                            "provider": "ollama",
5648                            "model_context_window": 8192
5649                        }
5650                    }]
5651                }],
5652                "effective_config": {
5653                    "values": {
5654                        "workspace_id": "expedition-local",
5655                        "readiness_mode": "deterministic"
5656                    },
5657                    "redacted_secret_keys": []
5658                },
5659                "state_scope": "workspace_persisted",
5660                "registration_fingerprint": {
5661                    "app_id": "expedition.readiness",
5662                    "app_version": "1.0.0",
5663                    "manifest_digest": "sha256:test-manifest"
5664                }
5665            }))
5666            .expect("workspace app state should serialize"),
5667        )
5668        .expect("workspace app state should write");
5669    }
5670
5671    fn runtime_workspace_components_json(repo: &Path) -> Vec<serde_json::Value> {
5672        [
5673            (
5674                "capture-expedition-objective",
5675                "expedition.planning.capture-expedition-objective",
5676            ),
5677            (
5678                "interpret-expedition-intent",
5679                "expedition.planning.interpret-expedition-intent",
5680            ),
5681            (
5682                "assess-conditions-summary",
5683                "expedition.planning.assess-conditions-summary",
5684            ),
5685            (
5686                "validate-team-readiness",
5687                "expedition.planning.validate-team-readiness",
5688            ),
5689            (
5690                "assemble-expedition-plan",
5691                "expedition.planning.assemble-expedition-plan",
5692            ),
5693        ]
5694        .into_iter()
5695        .map(|(leaf, capability_id)| {
5696            serde_json::json!({
5697                "component_id": format!("expedition.readiness.{leaf}-component"),
5698                "component_version": "1.0.0",
5699                "capability_id": capability_id,
5700                "capability_version": "1.0.0",
5701                "wasm_digest": "sha256:5647c39a1d25d8728350f9619025292a62e78a602068a2ad9b6f075751c93d99",
5702                "manifest_path": repo.join("examples/applications/expedition-readiness/components/validate-team-readiness/component.manifest.json").display().to_string(),
5703                "contract_path": repo.join(format!("contracts/examples/expedition/capabilities/{leaf}/contract.json")).display().to_string(),
5704                "artifact_ref": repo.join("examples/capabilities/team-readiness-agent/artifacts/validate-team-readiness-agent.wasm").display().to_string()
5705            })
5706        })
5707        .collect()
5708    }
5709
5710    fn repo_root() -> PathBuf {
5711        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
5712    }
5713
5714    fn unique_workspace_state_dir() -> PathBuf {
5715        let nanos = std::time::SystemTime::now()
5716            .duration_since(std::time::UNIX_EPOCH)
5717            .unwrap_or_default()
5718            .as_nanos();
5719        let counter = TEMP_COUNTER.fetch_add(1, Ordering::SeqCst);
5720        let path = std::env::temp_dir().join(format!(
5721            "traverse-runtime-workspace-state-test-{}-{nanos}-{counter}",
5722            std::process::id()
5723        ));
5724        fs::create_dir_all(&path).expect("temporary workspace should create");
5725        path
5726    }
5727
5728    #[derive(Debug, Clone)]
5729    struct NoopExecutor;
5730
5731    impl super::LocalExecutor for NoopExecutor {
5732        fn execute(
5733            &self,
5734            _capability: &ResolvedCapability,
5735            _input: &serde_json::Value,
5736        ) -> Result<super::LocalExecutionOutput, super::LocalExecutionFailure> {
5737            Ok(super::LocalExecutionOutput {
5738                value: json!({"draft_id": "draft"}),
5739                emitted_events: Vec::new(),
5740            })
5741        }
5742    }
5743
5744    struct FailingExecutor;
5745
5746    impl super::LocalExecutor for FailingExecutor {
5747        fn execute(
5748            &self,
5749            _capability: &ResolvedCapability,
5750            _input: &serde_json::Value,
5751        ) -> Result<super::LocalExecutionOutput, super::LocalExecutionFailure> {
5752            Err(super::LocalExecutionFailure {
5753                code: super::LocalExecutionFailureCode::ExecutionFailed,
5754                message: "forced failure".to_string(),
5755            })
5756        }
5757    }
5758
5759    fn successful_trace() -> super::RuntimeTrace {
5760        let mut registry = CapabilityRegistry::new();
5761        assert!(registry.register(public_registration()).is_ok());
5762        let runtime = Runtime::new(registry, NoopExecutor)
5763            .with_security_config(RuntimeSecurityConfig::development());
5764        runtime.execute(valid_request()).trace
5765    }
5766
5767    fn failed_trace() -> super::RuntimeTrace {
5768        let mut registry = CapabilityRegistry::new();
5769        assert!(registry.register(public_registration()).is_ok());
5770        let runtime = Runtime::new(registry, FailingExecutor)
5771            .with_security_config(RuntimeSecurityConfig::development());
5772        runtime.execute(valid_request()).trace
5773    }
5774
5775    #[test]
5776    fn selected_capability_id_returns_id_on_success() {
5777        let trace = successful_trace();
5778        assert_eq!(
5779            trace.selected_capability_id(),
5780            Some("content.comments.create-comment-draft")
5781        );
5782    }
5783
5784    #[test]
5785    fn selected_capability_id_returns_none_when_no_selection() {
5786        let registry = CapabilityRegistry::new();
5787        // empty registry — no capability matches
5788        let runtime = Runtime::new(registry, NoopExecutor)
5789            .with_security_config(RuntimeSecurityConfig::development());
5790        let trace = runtime.execute(valid_request()).trace;
5791        assert!(trace.selected_capability_id().is_none());
5792    }
5793
5794    #[test]
5795    fn errors_returns_none_on_success() {
5796        let trace = successful_trace();
5797        assert!(trace.errors().is_none());
5798    }
5799
5800    #[test]
5801    fn errors_returns_error_on_failure() {
5802        let trace = failed_trace();
5803        assert!(trace.errors().is_some());
5804    }
5805
5806    #[test]
5807    fn emitted_events_returns_slice() {
5808        let trace = successful_trace();
5809        // NoopExecutor emits no events; method must not panic and slice is valid
5810        let _ = trace.emitted_events();
5811    }
5812
5813    #[test]
5814    fn runtime_trace_exposes_non_sensitive_model_resolution_evidence() {
5815        let trace = successful_trace().with_model_resolution(vec![model_resolution_evidence()]);
5816        let serialized = serde_json::to_string(&trace).unwrap_or_default();
5817
5818        assert_eq!(trace.model_resolution.len(), 1);
5819        assert_eq!(
5820            trace.decision_evidence.model_resolution,
5821            trace.model_resolution
5822        );
5823        assert!(serialized.contains("model_resolution"));
5824        assert!(serialized.contains("ollama.local.generate"));
5825        assert!(serialized.contains("llama3.2:3b"));
5826        assert!(!serialized.contains("private prompt"));
5827        assert!(!serialized.contains("raw source text"));
5828        assert!(!serialized.contains("sk-local-secret"));
5829    }
5830
5831    #[test]
5832    fn output_returns_value_on_success() {
5833        let trace = successful_trace();
5834        assert_eq!(trace.output(), Some(&json!({"draft_id": "draft"})));
5835    }
5836
5837    #[test]
5838    fn output_returns_none_on_failure() {
5839        let trace = failed_trace();
5840        assert!(trace.output().is_none());
5841    }
5842
5843    #[test]
5844    fn is_success_true_on_completed() {
5845        let trace = successful_trace();
5846        assert!(trace.is_success());
5847    }
5848
5849    #[test]
5850    fn is_success_false_on_error() {
5851        let trace = failed_trace();
5852        assert!(!trace.is_success());
5853    }
5854
5855    fn model_resolution_evidence() -> ModelResolutionEvidence {
5856        ModelResolutionEvidence {
5857            phase: ModelResolutionPhase::Execution,
5858            interface_id: "traverse.inference.generate".to_string(),
5859            requested_interface_id: "traverse.inference.generate".to_string(),
5860            requested_placement: ExecutionTarget::Local,
5861            selected: Some(SelectedModelCandidate {
5862                candidate_id: "ollama-llama-3-2".to_string(),
5863                provider_capability_id: "traverse.inference.generate".to_string(),
5864                provider_implementation_id: "ollama.local.generate".to_string(),
5865                model_identifier: "llama3.2:3b".to_string(),
5866                placement_target: ExecutionTarget::Local,
5867                priority: 10,
5868                selection_reason: "selected highest-priority passing candidate".to_string(),
5869            }),
5870            candidates: vec![traverse_registry::ModelCandidateEvaluation {
5871                candidate_id: "ollama-llama-3-2".to_string(),
5872                provider_capability_id: "traverse.inference.generate".to_string(),
5873                provider_implementation_id: "ollama.local.generate".to_string(),
5874                model_identifier: "llama3.2:3b".to_string(),
5875                placement_target: ExecutionTarget::Local,
5876                priority: 10,
5877                readiness: ModelCandidateReadiness::Ready,
5878                rejection_code: Option::<ModelCandidateRejectionCode>::None,
5879                reason: "candidate passed availability, interface, placement, and context checks"
5880                    .to_string(),
5881                manifest_order: 0,
5882            }],
5883            failure_code: Option::<ModelCandidateRejectionCode>::None,
5884        }
5885    }
5886}