Skip to main content

traverse_runtime/
lib.rs

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