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