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}