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