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}