Skip to main content

agent_sdk_core/package/
mod.rs

1//! Runtime package authority for one run. Use this module to freeze provider routes,
2//! capabilities, sidecars, catalogs, policies, output sinks, and fingerprints before
3//! execution. Package builders are data-only; applying deltas returns a new snapshot
4//! rather than mutating ambient state.
5//!
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::{
10    capability::{
11        CapabilityId, CapabilityKind, CapabilitySpec, ExecutableCapabilityRoute, PackageSidecarRef,
12        ProviderCapabilityProjection,
13    },
14    domain::{
15        AgentError, AgentId, OutputSchemaId, PolicyRef, RuntimePackageId, SourceRef, TrustClass,
16    },
17    output::{OutputContract, OutputMode, OutputSchemaDialect, ProviderHintPolicy, SchemaVersion},
18    package_hooks::{HookSpec, validate_hook_specs},
19};
20
21/// Public realtime namespace. Use it for the documented realtime API
22/// surface; prefer crate-root re-exports for common imports. Module
23/// items must preserve the core ownership and side-effect boundaries
24/// described in this file.
25pub mod realtime;
26/// Public stream namespace. Use it for the documented stream API
27/// surface; prefer crate-root re-exports for common imports. Module
28/// items must preserve the core ownership and side-effect boundaries
29/// described in this file.
30pub mod stream;
31/// Public subagent namespace. Use it for the documented subagent API
32/// surface; prefer crate-root re-exports for common imports. Module
33/// items must preserve the core ownership and side-effect boundaries
34/// described in this file.
35pub mod subagent;
36/// Public tool pack namespace. Use it for the documented tool pack API
37/// surface; prefer crate-root re-exports for common imports. Module
38/// items must preserve the core ownership and side-effect boundaries
39/// described in this file.
40pub mod tool_pack;
41
42pub use crate::package_isolation::IsolationRequirementSnapshot;
43pub use subagent::{
44    ChildPackageStripManifest, ChildRuntimePackage, ChildRuntimePackagePolicy,
45    ContextHandoffPolicy, DepthBudget, RouteInheritanceMode, SubagentRoutePolicy,
46    SubagentToolPolicy, build_child_runtime_package,
47};
48
49/// Constant value for the package contract. Use it to keep SDK records
50/// and tests aligned on the same stable value.
51pub const RUNTIME_PACKAGE_SCHEMA_VERSION: u16 = 1;
52/// Constant value for the package contract. Use it to keep SDK records
53/// and tests aligned on the same stable value.
54pub const RUNTIME_PACKAGE_FINGERPRINT_ALGORITHM: &str = "sha256:runtime-package-canonical-v1";
55
56#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
57/// Describes the runtime package portion of a runtime package snapshot.
58/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
59pub struct RuntimePackage {
60    /// Wire schema version used for compatibility checks.
61    pub schema_version: u16,
62    /// Runtime package identifier for the immutable per-run package snapshot.
63    pub package_id: RuntimePackageId,
64    /// Agent snapshot frozen into this package or record.
65    pub agent: AgentSnapshot,
66    /// Provider route snapshot selected for this runtime package.
67    pub provider_route: ProviderRouteSnapshot,
68    /// Provider capability hints frozen into this package snapshot.
69    pub provider_capabilities: ProviderCapabilitySnapshot,
70    #[serde(default)]
71    /// Output contracts frozen into this package or request.
72    pub output_contracts: Vec<OutputContractSnapshot>,
73    #[serde(default)]
74    /// Output sink snapshots available to this run.
75    pub output_sinks: Vec<OutputSinkSnapshot>,
76    #[serde(default)]
77    /// Capabilities frozen into the package or returned by an adapter health
78    /// check.
79    pub capabilities: Vec<CapabilitySpec>,
80    #[serde(default)]
81    /// Typed sidecar snapshots included in a package or delta.
82    pub sidecars: Vec<PackageSidecarSnapshot>,
83    #[serde(default)]
84    /// Hook specs frozen into this package snapshot.
85    pub hooks: Vec<HookSpec>,
86    #[serde(default)]
87    /// Isolation requirements frozen into the package snapshot.
88    pub isolation_requirements: Vec<IsolationRequirementSnapshot>,
89    #[serde(default)]
90    /// Catalog snapshots contributed to or returned with a runtime package
91    /// delta.
92    pub catalogs: Vec<CapabilityCatalogSnapshot>,
93    /// Child-run lifecycle policy frozen into the package snapshot.
94    pub child_lifecycle: ChildLifecyclePolicySnapshot,
95    /// Policies used by this record or request.
96    pub policies: PolicySnapshot,
97    /// Manifest describing which fields entered or were excluded from
98    /// fingerprinting.
99    pub fingerprint_manifest: FingerprintInputManifest,
100    #[serde(default, skip_serializing_if = "VolatileRuntimeFields::is_empty")]
101    /// Volatile used by this record or request.
102    pub volatile: VolatileRuntimeFields,
103}
104
105impl RuntimePackage {
106    /// Starts a builder for this package value. Building is data-only;
107    /// runtime side effects occur only when a later coordinator or host
108    /// port executes the built configuration.
109    pub fn builder(package_id: RuntimePackageId) -> RuntimePackageBuilder {
110        RuntimePackageBuilder::new(package_id)
111    }
112
113    /// Returns for agent for the current value.
114    /// This is a read-only or data-construction helper unless the method body explicitly calls
115    /// a port or store.
116    pub fn for_agent(agent_id: AgentId, agent_name: impl Into<String>) -> RuntimePackageBuilder {
117        RuntimePackageBuilder::new(RuntimePackageId::new(format!(
118            "package.{}",
119            agent_id.as_str()
120        )))
121        .agent(AgentSnapshot {
122            agent_id,
123            name: agent_name.into(),
124            default_behavior_refs: Vec::new(),
125        })
126    }
127
128    /// Computes the stable canonical snapshot for this package value.
129    /// The computation is deterministic and side-effect free so it can
130    /// be used in package, journal, or test evidence.
131    pub fn canonical_snapshot(&self) -> Result<RuntimePackageCanonicalV1, AgentError> {
132        self.validate()?;
133        Ok(RuntimePackageCanonicalV1 {
134            schema_version: self.schema_version,
135            package_id: self.package_id.clone(),
136            agent: canonical_agent(self.agent.clone()),
137            provider_route: self.provider_route.clone(),
138            provider_capabilities: self.provider_capabilities.clone(),
139            output_contracts: sorted_by_key(self.output_contracts.clone(), |item| {
140                item.schema_id.as_str().to_string()
141            }),
142            output_sinks: sorted_by_key(self.output_sinks.clone(), |item| item.sink_id.clone()),
143            capabilities: sorted_by_key(
144                self.capabilities
145                    .clone()
146                    .into_iter()
147                    .map(canonical_capability)
148                    .collect(),
149                |item| item.capability_id.as_str().to_string(),
150            ),
151            sidecars: sorted_by_key(
152                self.sidecars
153                    .clone()
154                    .into_iter()
155                    .map(canonical_sidecar)
156                    .collect(),
157                |item| item.sidecar_id.clone(),
158            ),
159            isolation_requirements: sorted_by_key(self.isolation_requirements.clone(), |item| {
160                item.requirement_ref.as_str().to_string()
161            }),
162            catalogs: sorted_by_key(
163                self.catalogs
164                    .clone()
165                    .into_iter()
166                    .map(canonical_catalog)
167                    .collect(),
168                |item| item.catalog_id.clone(),
169            ),
170            child_lifecycle: self.child_lifecycle.clone(),
171            policies: self.policies.clone_sorted(),
172            fingerprint_manifest: self.computed_fingerprint_manifest(),
173        })
174    }
175
176    /// Computes the stable fingerprint for this package value. The
177    /// computation is deterministic and side-effect free so it can be
178    /// used in package, journal, or test evidence.
179    pub fn fingerprint(&self) -> Result<RuntimePackageFingerprint, AgentError> {
180        let canonical = self.canonical_snapshot()?;
181        let preimage = serde_json::json!({
182            "algorithm": RUNTIME_PACKAGE_FINGERPRINT_ALGORITHM,
183            "canonical_schema_version": RUNTIME_PACKAGE_SCHEMA_VERSION,
184            "snapshot": canonical,
185        });
186        let bytes = serde_json::to_vec(&crate::domain::json::normalize_json_value(preimage))
187            .map_err(|error| {
188                AgentError::contract_violation(format!(
189                    "package fingerprint serialization failed: {error}"
190                ))
191            })?;
192        let digest = Sha256::digest(bytes);
193        Ok(RuntimePackageFingerprint(format!(
194            "{RUNTIME_PACKAGE_FINGERPRINT_ALGORITHM}:{}",
195            hex_lower(&digest)
196        )))
197    }
198
199    /// Validates the package invariants and returns a typed error on
200    /// failure. Validation is pure and does not perform I/O, dispatch,
201    /// journal appends, or adapter calls.
202    pub fn validate(&self) -> Result<(), AgentError> {
203        if self.schema_version != RUNTIME_PACKAGE_SCHEMA_VERSION {
204            return Err(AgentError::contract_violation(format!(
205                "unsupported runtime package schema version {}",
206                self.schema_version
207            )));
208        }
209        if self.provider_route.route_id.is_empty() {
210            return Err(AgentError::missing_required_field(
211                "provider_route.route_id",
212            ));
213        }
214        if self.provider_route.model_id.is_empty() {
215            return Err(AgentError::missing_required_field(
216                "provider_route.model_id",
217            ));
218        }
219
220        for capability in &self.capabilities {
221            capability.validate()?;
222        }
223        for output_contract in &self.output_contracts {
224            output_contract.validate()?;
225        }
226        validate_hook_specs(&self.hooks)?;
227        self.validate_hook_sidecars()?;
228        for isolation_requirement in &self.isolation_requirements {
229            isolation_requirement.validate()?;
230        }
231
232        let projected_ids = self
233            .provider_tool_specs()?
234            .into_iter()
235            .map(|projection| projection.capability_id)
236            .collect::<Vec<_>>();
237        let executable_ids = self
238            .executable_routes()?
239            .into_iter()
240            .map(|route| route.capability_id)
241            .collect::<Vec<_>>();
242        for projected_id in projected_ids {
243            if !executable_ids.contains(&projected_id) {
244                return Err(AgentError::contract_violation(format!(
245                    "projected capability {} has no executable route in the same runtime package",
246                    projected_id.as_str()
247                )));
248            }
249        }
250
251        Ok(())
252    }
253
254    /// Returns provider tool specs for the current value.
255    /// This is a read-only or data-construction helper unless the method body explicitly calls
256    /// a port or store.
257    pub fn provider_tool_specs(&self) -> Result<Vec<ProviderCapabilityProjection>, AgentError> {
258        self.capabilities
259            .iter()
260            .filter_map(|capability| capability.project_for_provider().transpose())
261            .collect()
262    }
263
264    /// Returns executable routes for the current value.
265    /// This is a read-only or data-construction helper unless the method body explicitly calls
266    /// a port or store.
267    pub fn executable_routes(&self) -> Result<Vec<ExecutableCapabilityRoute>, AgentError> {
268        self.capabilities
269            .iter()
270            .filter_map(|capability| capability.executable_route().transpose())
271            .collect()
272    }
273
274    /// Returns sidecar for the current value.
275    /// This is a read-only or data-construction helper unless the method body explicitly calls
276    /// a port or store.
277    pub fn sidecar(&self, sidecar_id: &str) -> Option<&PackageSidecarSnapshot> {
278        self.sidecars
279            .iter()
280            .find(|sidecar| sidecar.sidecar_id == sidecar_id)
281    }
282
283    /// Returns this value with its output contract setting replaced.
284    /// The method follows builder-style data construction and does not
285    /// execute external work.
286    pub fn with_output_contract(
287        mut self,
288        output_contract: &OutputContract,
289    ) -> Result<Self, AgentError> {
290        output_contract.validate_shape()?;
291        let snapshot = OutputContractSnapshot::from(output_contract);
292        self.output_contracts
293            .retain(|existing| existing.schema_id != snapshot.schema_id);
294        self.output_contracts.push(snapshot);
295        self.fingerprint_manifest = self.computed_fingerprint_manifest();
296        self.validate()?;
297        Ok(self)
298    }
299
300    /// Returns catalog for the current value.
301    /// This is a read-only or data-construction helper unless the method body explicitly calls
302    /// a port or store.
303    pub fn catalog(&self, catalog_id: &str) -> Option<&CapabilityCatalogSnapshot> {
304        self.catalogs
305            .iter()
306            .find(|catalog| catalog.catalog_id == catalog_id)
307    }
308
309    /// Validates a package delta against this snapshot and returns a new
310    /// runtime package. The method is pure with respect to the existing
311    /// package and does not mutate ambient registries or execute activated
312    /// capabilities.
313    pub fn apply_delta(&self, delta: PackageDelta) -> Result<Self, AgentError> {
314        if delta.previous_fingerprint != self.fingerprint()? {
315            return Err(AgentError::contract_violation(
316                "package delta previous fingerprint does not match current package",
317            ));
318        }
319
320        let mut next = self.clone();
321        for capability_id in delta.deactivated_capability_ids {
322            next.capabilities
323                .retain(|capability| capability.capability_id != capability_id);
324        }
325        next.capabilities.extend(delta.activated_capabilities);
326        next.catalogs.extend(delta.catalogs);
327        next.sidecars.extend(delta.sidecars);
328        next.fingerprint_manifest = next.computed_fingerprint_manifest();
329        next.validate()?;
330        Ok(next)
331    }
332
333    /// Returns conformance report for the current value.
334    /// This is a read-only or data-construction helper unless the method body explicitly calls
335    /// a port or store.
336    pub fn conformance_report(&self) -> Result<RuntimePackageConformanceReport, AgentError> {
337        let fingerprint = self.fingerprint()?;
338        Ok(RuntimePackageConformanceReport {
339            fingerprint,
340            provider_projection_count: self.provider_tool_specs()?.len(),
341            executable_route_count: self.executable_routes()?.len(),
342            reserved_inactive_count: self
343                .capabilities
344                .iter()
345                .filter(|capability| capability.kind.is_reserved())
346                .count(),
347            catalog_count: self.catalogs.len(),
348            sidecar_count: self.sidecars.len(),
349        })
350    }
351
352    fn validate_hook_sidecars(&self) -> Result<(), AgentError> {
353        for spec in &self.hooks {
354            let expected = canonical_sidecar(spec.sidecar_snapshot()?);
355            let actual = self.sidecar(&expected.sidecar_id).ok_or_else(|| {
356                AgentError::contract_violation(format!(
357                    "hook sidecar {} is missing from runtime package",
358                    expected.sidecar_id
359                ))
360            })?;
361            if canonical_sidecar(actual.clone()) != expected {
362                return Err(AgentError::contract_violation(format!(
363                    "hook sidecar {} does not match hook spec {}",
364                    actual.sidecar_id,
365                    spec.hook_id.as_str()
366                )));
367            }
368        }
369        Ok(())
370    }
371
372    fn sync_hook_sidecars(&mut self) -> Result<(), AgentError> {
373        for spec in &self.hooks {
374            let sidecar = spec.sidecar_snapshot()?;
375            self.sidecars
376                .retain(|existing| existing.sidecar_id != sidecar.sidecar_id);
377            self.sidecars.push(sidecar);
378        }
379        Ok(())
380    }
381
382    fn computed_fingerprint_manifest(&self) -> FingerprintInputManifest {
383        FingerprintInputManifest {
384            algorithm: RUNTIME_PACKAGE_FINGERPRINT_ALGORITHM.to_string(),
385            canonical_schema_version: RUNTIME_PACKAGE_SCHEMA_VERSION,
386            readiness_profile: self.fingerprint_manifest.readiness_profile.clone(),
387            included_groups: vec![
388                FingerprintInputGroup::Agent,
389                FingerprintInputGroup::ProviderRoute,
390                FingerprintInputGroup::OutputContracts,
391                FingerprintInputGroup::OutputSinks,
392                FingerprintInputGroup::Capabilities,
393                FingerprintInputGroup::Sidecars,
394                FingerprintInputGroup::IsolationRequirements,
395                FingerprintInputGroup::Catalogs,
396                FingerprintInputGroup::ChildLifecycle,
397                FingerprintInputGroup::Policies,
398            ],
399            excluded_groups: vec![
400                FingerprintExclusionGroup::RunIds,
401                FingerprintExclusionGroup::EventIds,
402                FingerprintExclusionGroup::Timestamps,
403                FingerprintExclusionGroup::AdapterHealth,
404                FingerprintExclusionGroup::TemporaryPaths,
405                FingerprintExclusionGroup::TelemetrySinkHealth,
406            ],
407            reserved_feature_status: self
408                .capabilities
409                .iter()
410                .filter(|capability| capability.kind.is_reserved())
411                .map(|capability| ReservedFeatureFingerprintStatus {
412                    capability_id: capability.capability_id.clone(),
413                    kind: capability.kind.clone(),
414                    owner_role: capability.readiness.owner_role.clone(),
415                    status: "reserved_inactive".to_string(),
416                    reason: "owner workstream has not supplied sidecar, fingerprint, event, journal, and acceptance-test evidence".to_string(),
417                })
418                .collect::<Vec<_>>()
419                .canonicalized_reserved_status(),
420        }
421    }
422}
423
424#[derive(Clone, Debug)]
425/// Describes the runtime package builder portion of a runtime package snapshot.
426/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
427pub struct RuntimePackageBuilder {
428    package: RuntimePackage,
429}
430
431impl RuntimePackageBuilder {
432    /// Creates a new package value with explicit caller-provided
433    /// inputs. This constructor is data-only and performs no I/O or
434    /// external side effects.
435    pub fn new(package_id: RuntimePackageId) -> Self {
436        let package = RuntimePackage {
437            schema_version: RUNTIME_PACKAGE_SCHEMA_VERSION,
438            package_id,
439            agent: AgentSnapshot {
440                agent_id: AgentId::new("agent.default"),
441                name: "agent".to_string(),
442                default_behavior_refs: Vec::new(),
443            },
444            provider_route: ProviderRouteSnapshot::new("provider.fake", "model.fake"),
445            provider_capabilities: ProviderCapabilitySnapshot {
446                capability_version: "provider.capabilities.v1".to_string(),
447                realtime_capability_version: None,
448            },
449            output_contracts: Vec::new(),
450            output_sinks: Vec::new(),
451            capabilities: Vec::new(),
452            sidecars: Vec::new(),
453            hooks: Vec::new(),
454            isolation_requirements: Vec::new(),
455            catalogs: Vec::new(),
456            child_lifecycle: ChildLifecyclePolicySnapshot::safe_defaults(),
457            policies: PolicySnapshot::default(),
458            fingerprint_manifest: FingerprintInputManifest::p0_text(),
459            volatile: VolatileRuntimeFields::default(),
460        };
461        Self { package }
462    }
463
464    /// Returns agent for the current value.
465    /// This is a read-only or data-construction helper unless the method body explicitly calls
466    /// a port or store.
467    pub fn agent(mut self, agent: AgentSnapshot) -> Self {
468        self.package.agent = agent;
469        self
470    }
471
472    /// Returns provider route for the current value.
473    /// This is a read-only or data-construction helper unless the method body explicitly calls
474    /// a port or store.
475    pub fn provider_route(mut self, provider_route: ProviderRouteSnapshot) -> Self {
476        self.package.provider_route = provider_route;
477        self
478    }
479
480    /// Returns output contract for the current value.
481    /// This is a read-only or data-construction helper unless the method body explicitly calls
482    /// a port or store.
483    pub fn output_contract(mut self, output_contract: OutputContractSnapshot) -> Self {
484        self.package.output_contracts.push(output_contract);
485        self
486    }
487
488    /// Returns output sink for the current value.
489    /// This is a read-only or data-construction helper unless the method body explicitly calls
490    /// a port or store.
491    pub fn output_sink(mut self, output_sink: OutputSinkSnapshot) -> Self {
492        self.package.output_sinks.push(output_sink);
493        self
494    }
495
496    /// Returns capability for the current value.
497    /// This is a read-only or data-construction helper unless the method body explicitly calls
498    /// a port or store.
499    pub fn capability(mut self, capability: CapabilitySpec) -> Self {
500        self.package.capabilities.push(capability);
501        self
502    }
503
504    /// Returns sidecar for the current value.
505    /// This is a read-only or data-construction helper unless the method body explicitly calls
506    /// a port or store.
507    pub fn sidecar(mut self, sidecar: PackageSidecarSnapshot) -> Self {
508        self.package.sidecars.push(sidecar);
509        self
510    }
511
512    /// Returns hook for the current value.
513    /// This records a typed hook spec and derives its canonical sidecar during build; it does
514    /// not resolve or invoke the hook executor.
515    pub fn hook(mut self, hook: HookSpec) -> Self {
516        self.package.hooks.push(hook);
517        self
518    }
519
520    /// Returns catalog for the current value.
521    /// This is a read-only or data-construction helper unless the method body explicitly calls
522    /// a port or store.
523    pub fn catalog(mut self, catalog: CapabilityCatalogSnapshot) -> Self {
524        self.package.catalogs.push(catalog);
525        self
526    }
527
528    /// Returns isolation requirement for the current value.
529    /// This is a read-only or data-construction helper unless the method body explicitly calls
530    /// a port or store.
531    pub fn isolation_requirement(mut self, snapshot: IsolationRequirementSnapshot) -> Self {
532        self.package.isolation_requirements.push(snapshot);
533        self
534    }
535
536    /// Returns policy for the current value.
537    /// This is a read-only or data-construction helper unless the method body explicitly calls
538    /// a port or store.
539    pub fn policy(mut self, policy_ref: PolicyRef) -> Self {
540        self.package.policies.policy_refs.push(policy_ref);
541        self
542    }
543
544    /// Finishes builder validation and returns the configured value.
545    /// This is data-only unless the surrounding builder explicitly
546    /// documents adapter or store access.
547    pub fn build(mut self) -> Result<RuntimePackage, AgentError> {
548        self.package.sync_hook_sidecars()?;
549        self.package.fingerprint_manifest = self.package.computed_fingerprint_manifest();
550        self.package.validate()?;
551        Ok(self.package)
552    }
553}
554
555#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
556/// Describes the runtime package canonical v1 portion of a runtime package snapshot.
557/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
558pub struct RuntimePackageCanonicalV1 {
559    /// Wire schema version used for compatibility checks.
560    pub schema_version: u16,
561    /// Runtime package identifier for the immutable per-run package snapshot.
562    pub package_id: RuntimePackageId,
563    /// Agent snapshot frozen into this package or record.
564    pub agent: AgentSnapshot,
565    /// Provider route snapshot selected for this runtime package.
566    pub provider_route: ProviderRouteSnapshot,
567    /// Provider capability hints frozen into this package snapshot.
568    pub provider_capabilities: ProviderCapabilitySnapshot,
569    /// Output contracts frozen into this package or request.
570    pub output_contracts: Vec<OutputContractSnapshot>,
571    /// Output sink snapshots available to this run.
572    pub output_sinks: Vec<OutputSinkSnapshot>,
573    /// Capabilities frozen into the package or returned by an adapter health
574    /// check.
575    pub capabilities: Vec<CapabilitySpec>,
576    /// Typed sidecar snapshots included in a package or delta.
577    pub sidecars: Vec<PackageSidecarSnapshot>,
578    /// Isolation requirements frozen into the package snapshot.
579    pub isolation_requirements: Vec<IsolationRequirementSnapshot>,
580    /// Catalog snapshots contributed to or returned with a runtime package
581    /// delta.
582    pub catalogs: Vec<CapabilityCatalogSnapshot>,
583    /// Child-run lifecycle policy frozen into the package snapshot.
584    pub child_lifecycle: ChildLifecyclePolicySnapshot,
585    /// Policies used by this record or request.
586    pub policies: PolicySnapshot,
587    /// Manifest describing which fields entered or were excluded from
588    /// fingerprinting.
589    pub fingerprint_manifest: FingerprintInputManifest,
590}
591
592#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
593/// Describes the runtime package fingerprint portion of a runtime package snapshot.
594/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
595pub struct RuntimePackageFingerprint(pub String);
596
597impl RuntimePackageFingerprint {
598    /// Returns this value as str. The accessor is side-effect free and
599    /// keeps ownership with the caller.
600    pub fn as_str(&self) -> &str {
601        &self.0
602    }
603}
604
605#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
606/// Describes the agent snapshot portion of a runtime package snapshot.
607/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
608pub struct AgentSnapshot {
609    /// Agent identifier used for lineage, filtering, and ownership checks.
610    pub agent_id: AgentId,
611    /// Human-readable or protocol-visible name for this SDK item.
612    pub name: String,
613    #[serde(default)]
614    /// Typed default behavior refs references. Resolving them is separate
615    /// from constructing this record.
616    pub default_behavior_refs: Vec<PolicyRef>,
617}
618
619#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
620/// Describes the provider route snapshot portion of a runtime package snapshot.
621/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
622pub struct ProviderRouteSnapshot {
623    /// Stable route id used for typed lineage, lookup, or dedupe.
624    pub route_id: String,
625    /// Stable model id used for typed lineage, lookup, or dedupe.
626    pub model_id: String,
627    #[serde(skip_serializing_if = "Option::is_none")]
628    /// Policy reference governing provider projection or calls.
629    pub provider_policy_ref: Option<PolicyRef>,
630}
631
632impl ProviderRouteSnapshot {
633    /// Creates a new package value with explicit caller-provided
634    /// inputs. This constructor is data-only and performs no I/O or
635    /// external side effects.
636    pub fn new(route_id: impl Into<String>, model_id: impl Into<String>) -> Self {
637        Self {
638            route_id: route_id.into(),
639            model_id: model_id.into(),
640            provider_policy_ref: None,
641        }
642    }
643}
644
645#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
646/// Describes the provider capability snapshot portion of a runtime package snapshot.
647/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
648pub struct ProviderCapabilitySnapshot {
649    /// Capability version advertised by the provider or package.
650    /// Use it to match compatible feature contracts during package resolution.
651    pub capability_version: String,
652    #[serde(skip_serializing_if = "Option::is_none")]
653    /// Optional realtime capability version advertised by the package.
654    /// Package resolution can use it to match compatible realtime sidecars and adapters.
655    pub realtime_capability_version: Option<String>,
656}
657
658#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
659/// Describes the output contract snapshot portion of a runtime package snapshot.
660/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
661pub struct OutputContractSnapshot {
662    /// Stable schema id used for typed lineage, lookup, or dedupe.
663    pub schema_id: OutputSchemaId,
664    /// Wire schema version used for compatibility checks.
665    pub schema_version: SchemaVersion,
666    /// Deterministic schema fingerprint used for stale checks, package
667    /// evidence, or replay comparisons.
668    pub schema_fingerprint: String,
669    /// Schema dialect used to interpret the output schema.
670    /// Validators use it to select the supported JSON-schema subset and compatibility rules.
671    pub dialect: OutputSchemaDialect,
672    /// Mode that selects how this operation or contract should behave.
673    /// Callers use it to choose the explicit execution path instead of relying on hidden
674    /// defaults.
675    pub mode: OutputMode,
676    /// Typed validation policy ref reference. Resolving or executing it is a
677    /// separate policy-gated step.
678    pub validation_policy_ref: PolicyRef,
679    /// Typed repair policy ref reference. Resolving or executing it is a
680    /// separate policy-gated step.
681    pub repair_policy_ref: PolicyRef,
682    /// Version of the local validator contract used for this output policy.
683    /// Use it to keep validation and replay behavior stable across releases.
684    pub local_validator_version: String,
685    /// Policy for provider-side structured-output hints.
686    /// Hints may guide prompting but cannot replace SDK-owned validation.
687    pub provider_hint_policy: ProviderHintPolicy,
688    /// Validation policy applied before output is accepted as typed data.
689    /// It controls validator selection, bounds, failure visibility, and local validation
690    /// behavior.
691    pub validation: crate::output::ValidationPolicy,
692    /// Repair policy used after structured output validation fails.
693    /// It controls whether repair is attempted and which policy gates must approve it.
694    pub repair: crate::output::RepairPolicy,
695    /// Retry budget for validation, repair, or adapter attempts.
696    /// Runtimes use it to stop bounded loops deterministically.
697    pub retry_budget: crate::output::RetryBudget,
698    /// Content-capture policy that governs raw content, summaries, redaction, and retention.
699    /// Projection, telemetry, and delivery paths must honor it before exposing content.
700    pub content_policy: crate::policy::ContentCapturePolicy,
701    /// Provider-facing projection hint for structured output requests.
702    /// It can guide model prompting but does not replace local validation policy.
703    pub projection_hint: crate::output::OutputProjectionHint,
704}
705
706impl OutputContractSnapshot {
707    /// Validates the package invariants and returns a typed error on
708    /// failure. Validation is pure and does not perform I/O, dispatch,
709    /// journal appends, or adapter calls.
710    pub fn validate(&self) -> Result<(), AgentError> {
711        if !self.schema_fingerprint.starts_with("sha256:") {
712            return Err(AgentError::contract_violation(
713                "output contract snapshot schema_fingerprint must be sha256-prefixed",
714            ));
715        }
716        Ok(())
717    }
718}
719
720impl From<&OutputContract> for OutputContractSnapshot {
721    fn from(contract: &OutputContract) -> Self {
722        Self {
723            schema_id: contract.schema_id.clone(),
724            schema_version: contract.schema_version,
725            schema_fingerprint: contract.schema_fingerprint().as_str().to_string(),
726            dialect: contract.dialect.clone(),
727            mode: contract.mode.clone(),
728            validation_policy_ref: contract.validation.validator_ref_policy(),
729            repair_policy_ref: contract.repair.repair_adapter_ref_policy(),
730            local_validator_version: contract.validation.validator_ref.as_str().to_string(),
731            provider_hint_policy: contract.projection_hint.provider_hint_policy.clone(),
732            validation: contract.validation.clone(),
733            repair: contract.repair.clone(),
734            retry_budget: contract.retry_budget.clone(),
735            content_policy: contract.content_policy.clone(),
736            projection_hint: contract.projection_hint.clone(),
737        }
738    }
739}
740
741#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
742/// Describes the output sink snapshot portion of a runtime package snapshot.
743/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
744pub struct OutputSinkSnapshot {
745    /// Stable sink id used for typed lineage, lookup, or dedupe.
746    pub sink_id: String,
747    /// Typed delivery policy ref reference. Resolving or executing it is a
748    /// separate policy-gated step.
749    pub delivery_policy_ref: PolicyRef,
750    /// Dedupe policy or key for a side-effecting operation.
751    /// Replay and repair use it to avoid sending or executing the same effect twice.
752    pub dedupe_policy: String,
753    /// Capability version advertised by an output sink.
754    /// Delivery policy uses it to confirm that the sink can receive the requested payload
755    /// shape.
756    pub sink_capability_version: String,
757}
758
759#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
760/// Describes the package sidecar snapshot portion of a runtime package snapshot.
761/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
762pub struct PackageSidecarSnapshot {
763    /// Identifier for the typed package sidecar.
764    pub sidecar_id: String,
765    /// Kind/category for this record, capability, event, or detected
766    /// resource.
767    pub kind: String,
768    /// Version string for this capability, package, or protocol surface.
769    /// Use it for compatibility checks during package or adapter resolution.
770    pub version: String,
771    /// References associated with refs.
772    /// Resolve them through the appropriate registry or content store before using referenced
773    /// data.
774    pub refs: Vec<PackageSidecarRef>,
775    /// Policy references that govern admission, projection, execution, or
776    /// delivery.
777    pub policy_refs: Vec<PolicyRef>,
778    /// Stable hash for the bytes or canonical payload used for stale checks
779    /// and fingerprints.
780    pub content_hash: String,
781}
782
783#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
784/// Describes the child lifecycle policy snapshot portion of a runtime package snapshot.
785/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
786pub struct ChildLifecyclePolicySnapshot {
787    /// Policy reference that must be resolved by the host or runtime before
788    /// execution.
789    pub policy_ref: PolicyRef,
790    /// Manual cancellation policy for child or run lifecycle.
791    /// Use it to decide whether cancellation cascades, detaches, or requires explicit cleanup.
792    pub manual_cancel: String,
793    /// Typed detach policy ref reference. Resolving or executing it is a
794    /// separate policy-gated step.
795    pub detach_policy_ref: PolicyRef,
796    /// cleanup timeout ms duration in milliseconds.
797    pub cleanup_timeout_ms: u64,
798}
799
800impl ChildLifecyclePolicySnapshot {
801    /// Returns an updated value with safe defaults configured.
802    /// This is data-only and does not perform I/O, call host ports, append journals, publish
803    /// events, or start processes.
804    pub fn safe_defaults() -> Self {
805        Self {
806            policy_ref: PolicyRef::with_kind(
807                crate::domain::PolicyKind::RuntimePackage,
808                "policy.child.safe-defaults",
809            ),
810            manual_cancel: "cancel_agent_owned_children".to_string(),
811            detach_policy_ref: PolicyRef::with_kind(
812                crate::domain::PolicyKind::RuntimePackage,
813                "policy.detach.deny-by-default",
814            ),
815            cleanup_timeout_ms: 30_000,
816        }
817    }
818}
819
820#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
821/// Describes the policy snapshot portion of a runtime package snapshot.
822/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
823pub struct PolicySnapshot {
824    #[serde(default)]
825    /// Policy references that govern admission, projection, execution, or
826    /// delivery.
827    pub policy_refs: Vec<PolicyRef>,
828}
829
830impl PolicySnapshot {
831    fn clone_sorted(&self) -> Self {
832        Self {
833            policy_refs: sorted_policy_refs(self.policy_refs.clone()),
834        }
835    }
836}
837
838#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
839/// Describes the capability catalog snapshot portion of a runtime package snapshot.
840/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
841pub struct CapabilityCatalogSnapshot {
842    /// Stable catalog id used for typed lineage, lookup, or dedupe.
843    pub catalog_id: String,
844    /// Kind discriminator for source kind.
845    /// Use it to route finite match arms without parsing display text.
846    pub source_kind: crate::capability::CapabilitySourceKind,
847    /// Typed source reference that records where this item originated.
848    pub source_ref: SourceRef,
849    #[serde(skip_serializing_if = "Option::is_none")]
850    /// Version string for this capability, package, or protocol surface.
851    /// Use it for compatibility checks during package or adapter resolution.
852    pub version: Option<String>,
853    #[serde(skip_serializing_if = "Option::is_none")]
854    /// Stable hash for the bytes or canonical payload used for stale checks
855    /// and fingerprints.
856    pub content_hash: Option<String>,
857    /// Trust state used by this record or request.
858    pub trust_state: TrustClass,
859    /// Typed activation policy ref reference. Resolving or executing it is a
860    /// separate policy-gated step.
861    pub activation_policy_ref: PolicyRef,
862    #[serde(default)]
863    /// Candidate capabilities, tools, resources, or package entries exposed
864    /// for host-approved selection.
865    pub candidates: Vec<CapabilityId>,
866}
867
868#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
869/// Describes the package delta portion of a runtime package snapshot.
870/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
871pub struct PackageDelta {
872    /// Fingerprint of the package snapshot that a delta was computed against.
873    pub previous_fingerprint: RuntimePackageFingerprint,
874    /// Source that requested this package delta, activation, or side effect.
875    pub requested_by: SourceRef,
876    /// Redacted explanation for a denial, failure, status, or package delta.
877    pub reason: String,
878    #[serde(default)]
879    /// Capabilities that a package delta proposes to add to the next
880    /// snapshot.
881    pub activated_capabilities: Vec<CapabilitySpec>,
882    #[serde(default)]
883    /// Capability identifiers that a package delta proposes to remove from
884    /// the next snapshot.
885    pub deactivated_capability_ids: Vec<CapabilityId>,
886    #[serde(default)]
887    /// Catalog snapshots contributed to or returned with a runtime package
888    /// delta.
889    pub catalogs: Vec<CapabilityCatalogSnapshot>,
890    #[serde(default)]
891    /// Typed sidecar snapshots included in a package or delta.
892    pub sidecars: Vec<PackageSidecarSnapshot>,
893}
894
895#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
896/// Describes the runtime package conformance report portion of a runtime package snapshot.
897/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
898pub struct RuntimePackageConformanceReport {
899    /// Deterministic fingerprint for package, event, telemetry, or validation
900    /// evidence.
901    pub fingerprint: RuntimePackageFingerprint,
902    /// Count of provider projection items observed or included in this
903    /// record.
904    pub provider_projection_count: usize,
905    /// Count of executable route items observed or included in this record.
906    pub executable_route_count: usize,
907    /// Count of reserved inactive items observed or included in this record.
908    pub reserved_inactive_count: usize,
909    /// Count of catalog items observed or included in this record.
910    pub catalog_count: usize,
911    /// Count of sidecar items observed or included in this record.
912    pub sidecar_count: usize,
913}
914
915#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
916/// Describes the fingerprint input manifest portion of a runtime package snapshot.
917/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
918pub struct FingerprintInputManifest {
919    /// Algorithm name used for hashing or fingerprint generation.
920    pub algorithm: String,
921    /// Version of the canonical schema format used for fingerprinting.
922    /// Use it to detect incompatible fingerprint inputs.
923    pub canonical_schema_version: u16,
924    /// Readiness state for a capability or package feature.
925    /// Launch and package validation use it to distinguish active, reserved, and blocked
926    /// surfaces.
927    pub readiness_profile: ReadinessProfile,
928    /// Fingerprint input groups included for this readiness profile.
929    pub included_groups: Vec<FingerprintInputGroup>,
930    /// Fingerprint input groups deliberately excluded for this readiness
931    /// profile.
932    pub excluded_groups: Vec<FingerprintExclusionGroup>,
933    /// Readiness status for reserved feature capabilities.
934    pub reserved_feature_status: Vec<ReservedFeatureFingerprintStatus>,
935}
936
937impl FingerprintInputManifest {
938    /// Returns p0 text for the current value.
939    /// This is a read-only or data-construction helper unless the method body explicitly calls
940    /// a port or store.
941    pub fn p0_text() -> Self {
942        Self {
943            algorithm: RUNTIME_PACKAGE_FINGERPRINT_ALGORITHM.to_string(),
944            canonical_schema_version: RUNTIME_PACKAGE_SCHEMA_VERSION,
945            readiness_profile: ReadinessProfile::P0Text,
946            included_groups: Vec::new(),
947            excluded_groups: Vec::new(),
948            reserved_feature_status: Vec::new(),
949        }
950    }
951}
952
953#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
954#[serde(rename_all = "snake_case")]
955/// Enumerates the finite readiness profile cases.
956/// Serialized names are part of the SDK contract; update fixtures when variants change.
957pub enum ReadinessProfile {
958    /// Use this variant when the contract needs to represent p0 text; selecting it has no side effect by itself.
959    P0Text,
960    /// Use this variant when the contract needs to represent p1 typed output; selecting it has no side effect by itself.
961    P1TypedOutput,
962    /// Use this variant when the contract needs to represent p2 side effects; selecting it has no side effect by itself.
963    P2SideEffects,
964}
965
966#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
967#[serde(rename_all = "snake_case")]
968/// Enumerates the finite fingerprint input group cases.
969/// Serialized names are part of the SDK contract; update fixtures when variants change.
970pub enum FingerprintInputGroup {
971    /// Use this variant when the contract needs to represent agent; selecting it has no side effect by itself.
972    Agent,
973    /// Use this variant when the contract needs to represent provider route; selecting it has no side effect by itself.
974    ProviderRoute,
975    /// Use this variant when the contract needs to represent output contracts; selecting it has no side effect by itself.
976    OutputContracts,
977    /// Use this variant when the contract needs to represent output sinks; selecting it has no side effect by itself.
978    OutputSinks,
979    /// Use this variant when the contract needs to represent capabilities; selecting it has no side effect by itself.
980    Capabilities,
981    /// Use this variant when the contract needs to represent sidecars; selecting it has no side effect by itself.
982    Sidecars,
983    /// Use this variant when the contract needs to represent isolation requirements; selecting it has no side effect by itself.
984    IsolationRequirements,
985    /// Use this variant when the contract needs to represent catalogs; selecting it has no side effect by itself.
986    Catalogs,
987    /// Use this variant when the contract needs to represent child lifecycle; selecting it has no side effect by itself.
988    ChildLifecycle,
989    /// Use this variant when the contract needs to represent policies; selecting it has no side effect by itself.
990    Policies,
991}
992
993#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
994#[serde(rename_all = "snake_case")]
995/// Enumerates the finite fingerprint exclusion group cases.
996/// Serialized names are part of the SDK contract; update fixtures when variants change.
997pub enum FingerprintExclusionGroup {
998    /// Use this variant when the contract needs to represent run ids; selecting it has no side effect by itself.
999    RunIds,
1000    /// Use this variant when the contract needs to represent event ids; selecting it has no side effect by itself.
1001    EventIds,
1002    /// Use this variant when the contract needs to represent timestamps; selecting it has no side effect by itself.
1003    Timestamps,
1004    /// Use this variant when the contract needs to represent adapter health; selecting it has no side effect by itself.
1005    AdapterHealth,
1006    /// Use this variant when the contract needs to represent temporary paths; selecting it has no side effect by itself.
1007    TemporaryPaths,
1008    /// Use this variant when the contract needs to represent telemetry sink health; selecting it has no side effect by itself.
1009    TelemetrySinkHealth,
1010}
1011
1012#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1013/// Describes the reserved feature fingerprint status portion of a runtime package snapshot.
1014/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1015pub struct ReservedFeatureFingerprintStatus {
1016    /// Stable capability identifier used for package projection and
1017    /// executable routing.
1018    pub capability_id: CapabilityId,
1019    /// Kind/category for this record, capability, event, or detected
1020    /// resource.
1021    pub kind: CapabilityKind,
1022    /// Implementation owner responsible for this capability surface.
1023    /// Use it to route follow-up work and validation ownership.
1024    pub owner_role: String,
1025    /// Finite status for this record or lifecycle stage.
1026    pub status: String,
1027    /// Redacted explanation for a denial, failure, status, or package delta.
1028    pub reason: String,
1029}
1030
1031#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1032/// Describes the volatile runtime fields portion of a runtime package snapshot.
1033/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1034pub struct VolatileRuntimeFields {
1035    #[serde(skip_serializing_if = "Option::is_none")]
1036    /// Run identifier used for lineage, filtering, replay, and dedupe.
1037    pub run_id: Option<String>,
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    /// Event identifier used to correlate live events with journal or replay
1040    /// evidence.
1041    pub event_id: Option<String>,
1042    #[serde(skip_serializing_if = "Option::is_none")]
1043    /// timestamp ms duration in milliseconds.
1044    pub timestamp_ms: Option<u64>,
1045    #[serde(skip_serializing_if = "Option::is_none")]
1046    /// Adapter health snapshot used to decide whether host support is
1047    /// available.
1048    pub adapter_health: Option<String>,
1049    #[serde(skip_serializing_if = "Option::is_none")]
1050    /// Temporary host path used only for diagnostics or adapter handoff; it
1051    /// should not become durable SDK authority.
1052    pub temporary_path: Option<String>,
1053}
1054
1055impl VolatileRuntimeFields {
1056    fn is_empty(&self) -> bool {
1057        self.run_id.is_none()
1058            && self.event_id.is_none()
1059            && self.timestamp_ms.is_none()
1060            && self.adapter_health.is_none()
1061            && self.temporary_path.is_none()
1062    }
1063}
1064
1065fn sorted_by_key<T, F>(mut items: Vec<T>, mut key: F) -> Vec<T>
1066where
1067    F: FnMut(&T) -> String,
1068{
1069    items.sort_by_key(|item| key(item));
1070    items
1071}
1072
1073fn canonical_agent(mut agent: AgentSnapshot) -> AgentSnapshot {
1074    agent.default_behavior_refs = sorted_policy_refs(agent.default_behavior_refs);
1075    agent
1076}
1077
1078fn canonical_capability(mut capability: CapabilitySpec) -> CapabilitySpec {
1079    capability.projection = canonical_projection(capability.projection);
1080    capability.sidecar_refs = sorted_sidecar_refs(capability.sidecar_refs);
1081    capability
1082}
1083
1084fn canonical_projection(
1085    projection: crate::capability::ProjectionMode,
1086) -> crate::capability::ProjectionMode {
1087    match projection {
1088        crate::capability::ProjectionMode::ProducesContextItems { mut allowed_kinds } => {
1089            allowed_kinds.sort();
1090            crate::capability::ProjectionMode::ProducesContextItems { allowed_kinds }
1091        }
1092        crate::capability::ProjectionMode::ProjectsContextRefs {
1093            mut allowed_ref_kinds,
1094        } => {
1095            allowed_ref_kinds.sort();
1096            crate::capability::ProjectionMode::ProjectsContextRefs { allowed_ref_kinds }
1097        }
1098        other => other,
1099    }
1100}
1101
1102fn canonical_sidecar(mut sidecar: PackageSidecarSnapshot) -> PackageSidecarSnapshot {
1103    sidecar.refs = sorted_sidecar_refs(sidecar.refs);
1104    sidecar.policy_refs = sorted_policy_refs(sidecar.policy_refs);
1105    sidecar
1106}
1107
1108fn canonical_catalog(mut catalog: CapabilityCatalogSnapshot) -> CapabilityCatalogSnapshot {
1109    catalog.candidates = sorted_by_key(catalog.candidates, |capability| {
1110        capability.as_str().to_string()
1111    });
1112    catalog
1113}
1114
1115fn sorted_policy_refs(policy_refs: Vec<PolicyRef>) -> Vec<PolicyRef> {
1116    sorted_by_key(policy_refs, policy_ref_key)
1117}
1118
1119fn sorted_sidecar_refs(sidecar_refs: Vec<PackageSidecarRef>) -> Vec<PackageSidecarRef> {
1120    sorted_by_key(sidecar_refs, sidecar_ref_key)
1121}
1122
1123fn policy_ref_key(policy: &PolicyRef) -> String {
1124    format!(
1125        "{:?}:{}:{}",
1126        policy.kind,
1127        policy.as_str(),
1128        policy.version.as_deref().unwrap_or("")
1129    )
1130}
1131
1132fn sidecar_ref_key(sidecar: &PackageSidecarRef) -> String {
1133    format!(
1134        "{}:{}:{}:{}",
1135        sidecar.sidecar_id,
1136        sidecar.kind,
1137        sidecar.version,
1138        sidecar.content_hash.as_deref().unwrap_or("")
1139    )
1140}
1141
1142trait CanonicalReservedStatus {
1143    fn canonicalized_reserved_status(self) -> Self;
1144}
1145
1146impl CanonicalReservedStatus for Vec<ReservedFeatureFingerprintStatus> {
1147    fn canonicalized_reserved_status(mut self) -> Self {
1148        self.sort_by_key(|status| status.capability_id.as_str().to_string());
1149        self
1150    }
1151}
1152
1153fn hex_lower(bytes: &[u8]) -> String {
1154    let mut output = String::with_capacity(bytes.len() * 2);
1155    for byte in bytes {
1156        output.push_str(&format!("{byte:02x}"));
1157    }
1158    output
1159}