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