agent_sdk_core/package/hooks.rs
1//! Runtime-package records and builders. Use these items to describe the immutable
2//! per-run package that freezes provider route, capabilities, policies, sidecars,
3//! catalogs, and fingerprints. Builders are data-only and must not perform discovery
4//! or execution side effects. This file contains the hooks portion of that contract.
5//!
6use std::{collections::BTreeSet, num::NonZeroUsize};
7
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11use crate::{
12 capability::PackageSidecarRef,
13 domain::{
14 AgentError, AgentErrorKind, AgentId, AttemptId, ContextItemId, DestinationRef, EntityRef,
15 PolicyKind, PolicyRef, PrivacyClass, RunId, SourceRef, TurnId,
16 },
17 error::RetryClassification,
18 package::{PackageSidecarSnapshot, RuntimePackageFingerprint},
19};
20
21/// Constant value for the package::hooks contract. Use it to keep SDK
22/// records and tests aligned on the same stable value.
23pub const HOOK_SIDECAR_KIND: &str = "hook_spec";
24/// Constant value for the package::hooks contract. Use it to keep SDK
25/// records and tests aligned on the same stable value.
26pub const HOOK_SIDECAR_VERSION: &str = "v1";
27
28#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
29#[serde(transparent)]
30/// Describes the hook id portion of a runtime package snapshot.
31/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
32pub struct HookId(String);
33
34impl HookId {
35 /// Creates a new package::hooks value with explicit caller-provided
36 /// inputs. This constructor is data-only and performs no I/O or
37 /// external side effects.
38 pub fn new(value: impl Into<String>) -> Self {
39 let value = value.into();
40 assert!(!value.is_empty(), "HookId must not be empty");
41 Self(value)
42 }
43
44 /// Returns this value as str. The accessor is side-effect free and
45 /// keeps ownership with the caller.
46 pub fn as_str(&self) -> &str {
47 &self.0
48 }
49}
50
51#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
52#[serde(transparent)]
53/// Describes the hook executor ref portion of a runtime package snapshot.
54/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
55pub struct HookExecutorRef(String);
56
57impl HookExecutorRef {
58 /// Creates a new package::hooks value with explicit caller-provided
59 /// inputs. This constructor is data-only and performs no I/O or
60 /// external side effects.
61 pub fn new(value: impl Into<String>) -> Self {
62 let value = value.into();
63 assert!(!value.is_empty(), "HookExecutorRef must not be empty");
64 Self(value)
65 }
66
67 /// Returns this value as str. The accessor is side-effect free and
68 /// keeps ownership with the caller.
69 pub fn as_str(&self) -> &str {
70 &self.0
71 }
72}
73
74#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
75#[serde(rename_all = "snake_case")]
76/// Enumerates the finite hook point cases.
77/// Serialized names are part of the SDK contract; update fixtures when variants change.
78pub enum HookPoint {
79 /// Use this variant when the contract needs to represent run starting; selecting it has no side effect by itself.
80 RunStarting,
81 /// Use this variant when the contract needs to represent before context assembly; selecting it has no side effect by itself.
82 BeforeContextAssembly,
83 /// Use this variant when the contract needs to represent after context assembly; selecting it has no side effect by itself.
84 AfterContextAssembly,
85 /// Use this variant when the contract needs to represent before provider projection; selecting it has no side effect by itself.
86 BeforeProviderProjection,
87 /// Use this variant when the contract needs to represent before model call; selecting it has no side effect by itself.
88 BeforeModelCall,
89 /// Use this variant when the contract needs to represent on model delta; selecting it has no side effect by itself.
90 OnModelDelta,
91 /// Use this variant when the contract needs to represent after model call; selecting it has no side effect by itself.
92 AfterModelCall,
93 /// Use this variant when the contract needs to represent before structured output validation; selecting it has no side effect by itself.
94 BeforeStructuredOutputValidation,
95 /// Use this variant when the contract needs to represent after structured output validation; selecting it has no side effect by itself.
96 AfterStructuredOutputValidation,
97 /// Use this variant when the contract needs to represent before tool call; selecting it has no side effect by itself.
98 BeforeToolCall,
99 /// Use this variant when the contract needs to represent after tool call; selecting it has no side effect by itself.
100 AfterToolCall,
101 /// Use this variant when the contract needs to represent before approval request; selecting it has no side effect by itself.
102 BeforeApprovalRequest,
103 /// Use this variant when the contract needs to represent after approval decision; selecting it has no side effect by itself.
104 AfterApprovalDecision,
105 /// Use this variant when the contract needs to represent before subagent start; selecting it has no side effect by itself.
106 BeforeSubagentStart,
107 /// Use this variant when the contract needs to represent after subagent terminal; selecting it has no side effect by itself.
108 AfterSubagentTerminal,
109 /// Use this variant when the contract needs to represent before isolation process start; selecting it has no side effect by itself.
110 BeforeIsolationProcessStart,
111 /// Use this variant when the contract needs to represent after isolation process exit; selecting it has no side effect by itself.
112 AfterIsolationProcessExit,
113 /// Use this variant when the contract needs to represent on run cancel requested; selecting it has no side effect by itself.
114 OnRunCancelRequested,
115 /// Use this variant when the contract needs to represent before run complete; selecting it has no side effect by itself.
116 BeforeRunComplete,
117 /// Use this variant when the contract needs to represent after run terminal; selecting it has no side effect by itself.
118 AfterRunTerminal,
119 /// Use this variant when the contract needs to represent before compaction; selecting it has no side effect by itself.
120 BeforeCompaction,
121 /// Use this variant when the contract needs to represent after compaction; selecting it has no side effect by itself.
122 AfterCompaction,
123}
124
125impl HookPoint {
126 /// Computes or returns allowed response classes for the package::hooks
127 /// contract without external I/O or side effects.
128 pub fn allowed_response_classes(&self) -> BTreeSet<HookResponseClass> {
129 use HookResponseClass as Class;
130 match self {
131 Self::RunStarting => set([Class::Observe, Class::InjectContext, Class::StopRun]),
132 Self::BeforeContextAssembly => set([Class::Observe, Class::InjectContext]),
133 Self::AfterContextAssembly => {
134 set([Class::Observe, Class::RequestCompaction, Class::StopRun])
135 }
136 Self::BeforeProviderProjection => {
137 set([Class::Observe, Class::ModifyProjection, Class::StopRun])
138 }
139 Self::BeforeModelCall => set([
140 Class::Observe,
141 Class::ModifyProjection,
142 Class::RequestApproval,
143 Class::StopRun,
144 ]),
145 Self::OnModelDelta => set([Class::Observe]),
146 Self::AfterModelCall => set([Class::Observe, Class::RequestRetry, Class::StopRun]),
147 Self::BeforeStructuredOutputValidation => {
148 set([Class::Observe, Class::ModifyValidationHints])
149 }
150 Self::AfterStructuredOutputValidation => set([Class::Observe, Class::RequestRetry]),
151 Self::BeforeToolCall => set([
152 Class::Observe,
153 Class::Deny,
154 Class::ModifyToolRequest,
155 Class::RequestApproval,
156 ]),
157 Self::AfterToolCall => set([
158 Class::Observe,
159 Class::RequestRetry,
160 Class::RewriteToolResult,
161 ]),
162 Self::BeforeApprovalRequest => {
163 set([Class::Observe, Class::ModifyApprovalRequest, Class::Deny])
164 }
165 Self::AfterApprovalDecision => set([Class::Observe]),
166 Self::BeforeSubagentStart => {
167 set([Class::Observe, Class::Deny, Class::ModifySubagentRequest])
168 }
169 Self::AfterSubagentTerminal => set([Class::Observe, Class::RequestUsageRollupRepair]),
170 Self::BeforeIsolationProcessStart => {
171 set([Class::Observe, Class::Deny, Class::ModifyProcessRequest])
172 }
173 Self::AfterIsolationProcessExit => set([Class::Observe, Class::RequestCleanupRepair]),
174 Self::OnRunCancelRequested => set([Class::Observe, Class::RequestCleanupRepair]),
175 Self::BeforeRunComplete => set([
176 Class::Observe,
177 Class::RequestRetry,
178 Class::ValidateDetach,
179 Class::StopCompletionWithRepairNeeded,
180 ]),
181 Self::AfterRunTerminal => set([Class::Observe]),
182 Self::BeforeCompaction => set([Class::Observe, Class::MarkProtectedContext]),
183 Self::AfterCompaction => set([Class::Observe, Class::RequestProjectionAuditRepair]),
184 }
185 }
186
187 /// Reports whether this value is security critical. The check is
188 /// pure and does not mutate SDK or host state.
189 pub fn is_security_critical(&self) -> bool {
190 matches!(
191 self,
192 Self::BeforeToolCall
193 | Self::BeforeApprovalRequest
194 | Self::BeforeSubagentStart
195 | Self::BeforeIsolationProcessStart
196 | Self::BeforeRunComplete
197 )
198 }
199}
200
201#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
202#[serde(rename_all = "snake_case")]
203/// Enumerates the finite hook source cases.
204/// Serialized names are part of the SDK contract; update fixtures when variants change.
205pub enum HookSource {
206 /// Use this variant when the contract needs to represent host config; selecting it has no side effect by itself.
207 HostConfig,
208 /// Use this variant when the contract needs to represent in process; selecting it has no side effect by itself.
209 InProcess,
210 /// Use this variant when the contract needs to represent extension; selecting it has no side effect by itself.
211 Extension,
212 /// Use this variant when the contract needs to represent sdk default; selecting it has no side effect by itself.
213 SdkDefault,
214 /// Use this variant when the contract needs to represent test only fake; selecting it has no side effect by itself.
215 TestOnlyFake,
216}
217
218#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
219/// Describes the hook ordering portion of a runtime package snapshot.
220/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
221pub struct HookOrdering {
222 /// Phase used by this record or request.
223 pub phase: HookOrderingPhase,
224 /// Order used by this record or request.
225 pub order: i32,
226}
227
228impl HookOrdering {
229 /// Returns an updated package::hooks value with early applied. This is
230 /// data construction only and does not execute the configured behavior.
231 pub fn early(order: i32) -> Self {
232 Self {
233 phase: HookOrderingPhase::Early,
234 order,
235 }
236 }
237
238 /// Returns an updated package::hooks value with normal applied. This is
239 /// data construction only and does not execute the configured behavior.
240 pub fn normal(order: i32) -> Self {
241 Self {
242 phase: HookOrderingPhase::Normal,
243 order,
244 }
245 }
246
247 /// Returns an updated package::hooks value with late applied. This is
248 /// data construction only and does not execute the configured behavior.
249 pub fn late(order: i32) -> Self {
250 Self {
251 phase: HookOrderingPhase::Late,
252 order,
253 }
254 }
255}
256
257#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
258#[serde(rename_all = "snake_case")]
259/// Enumerates the finite hook ordering phase cases.
260/// Serialized names are part of the SDK contract; update fixtures when variants change.
261pub enum HookOrderingPhase {
262 /// Use this variant when the contract needs to represent early; selecting it has no side effect by itself.
263 Early,
264 /// Use this variant when the contract needs to represent normal; selecting it has no side effect by itself.
265 Normal,
266 /// Use this variant when the contract needs to represent late; selecting it has no side effect by itself.
267 Late,
268}
269
270#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
271#[serde(tag = "mode", rename_all = "snake_case")]
272/// Enumerates the finite hook execution mode cases.
273/// Serialized names are part of the SDK contract; update fixtures when variants change.
274pub enum HookExecutionMode {
275 /// Use this variant when the contract needs to represent blocking; selecting it has no side effect by itself.
276 Blocking,
277 /// Use this variant when the contract needs to represent non blocking; selecting it has no side effect by itself.
278 NonBlocking {
279 /// Subscriber queue settings used for streams created with this filter.
280 /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
281 queue: HookQueueConfig,
282 /// Overflow policy applied when a subscriber queue reaches capacity.
283 /// It decides whether to drop, summarize, backpressure, or fail the subscriber.
284 overflow: HookOverflowPolicy,
285 },
286}
287
288impl HookExecutionMode {
289 /// Builds the nonblocking observe default value with the documented defaults.
290 /// This is data-only and does not perform I/O, call host ports, append journals, publish
291 /// events, or start processes.
292 pub fn nonblocking_observe_default() -> Self {
293 Self::NonBlocking {
294 queue: HookQueueConfig::new(64, 4),
295 overflow: HookOverflowPolicy::DropObserveOnly,
296 }
297 }
298
299 /// Reports whether this value is blocking. The check is pure and
300 /// does not mutate SDK or host state.
301 pub fn is_blocking(&self) -> bool {
302 matches!(self, Self::Blocking)
303 }
304}
305
306#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
307/// Describes the hook queue config portion of a runtime package snapshot.
308/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
309pub struct HookQueueConfig {
310 /// Total subscriber queue capacity.
311 /// This bounds buffered event frames for a live subscriber.
312 pub capacity: NonZeroUsize,
313 /// Queue slots reserved for terminal frames.
314 /// This keeps important terminal events available even when non-terminal frames overflow.
315 pub terminal_reserve: NonZeroUsize,
316}
317
318impl HookQueueConfig {
319 /// Creates a new package::hooks value with explicit caller-provided
320 /// inputs. This constructor is data-only and performs no I/O or
321 /// external side effects.
322 ///
323 /// # Panics
324 ///
325 /// Panics if constructor invariants fail, such as invalid identifier
326 /// text or constructor-specific bounds. Use a fallible constructor such as
327 /// `try_new` when one is available for untrusted input.
328 pub fn new(capacity: usize, terminal_reserve: usize) -> Self {
329 Self {
330 capacity: NonZeroUsize::new(capacity).expect("hook queue capacity must be nonzero"),
331 terminal_reserve: NonZeroUsize::new(terminal_reserve)
332 .expect("hook terminal reserve must be nonzero"),
333 }
334 }
335}
336
337#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
338#[serde(rename_all = "snake_case")]
339/// Enumerates the finite hook overflow policy cases.
340/// Serialized names are part of the SDK contract; update fixtures when variants change.
341pub enum HookOverflowPolicy {
342 /// Use this variant when the contract needs to represent drop observe only; selecting it has no side effect by itself.
343 DropObserveOnly,
344 /// Use this variant when the contract needs to represent summarize and continue; selecting it has no side effect by itself.
345 SummarizeAndContinue,
346 /// Use this variant when the contract needs to represent fail hook invocation; selecting it has no side effect by itself.
347 FailHookInvocation,
348}
349
350#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
351/// Describes the hook timeout policy portion of a runtime package snapshot.
352/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
353pub struct HookTimeoutPolicy {
354 /// Timeout budget in milliseconds for the requested operation.
355 pub timeout_ms: u64,
356}
357
358impl HookTimeoutPolicy {
359 /// Returns an updated package::hooks value with bounded ms applied. This
360 /// is data construction only and does not execute the configured
361 /// behavior.
362 pub fn bounded_ms(timeout_ms: u64) -> Self {
363 Self { timeout_ms }
364 }
365}
366
367#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
368#[serde(rename_all = "snake_case")]
369/// Enumerates the finite hook failure policy cases.
370/// Serialized names are part of the SDK contract; update fixtures when variants change.
371pub enum HookFailurePolicy {
372 /// Use this variant when the contract needs to represent fail open observe only; selecting it has no side effect by itself.
373 FailOpenObserveOnly,
374 /// Use this variant when the contract needs to represent deny; selecting it has no side effect by itself.
375 Deny,
376 /// Use this variant when the contract needs to represent interrupt run; selecting it has no side effect by itself.
377 InterruptRun,
378 /// Use this variant when the contract needs to represent fail run; selecting it has no side effect by itself.
379 FailRun,
380}
381
382impl HookFailurePolicy {
383 /// Returns an updated package::hooks value with fails closed applied.
384 /// This is data construction only and does not execute the configured
385 /// behavior.
386 pub fn fails_closed(&self) -> bool {
387 !matches!(self, Self::FailOpenObserveOnly)
388 }
389}
390
391#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
392/// Describes the hook mutation rights portion of a runtime package snapshot.
393/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
394pub struct HookMutationRights {
395 /// Collection of rights values.
396 /// Ordering and membership should be treated as part of the serialized contract when
397 /// relevant.
398 pub rights: BTreeSet<HookMutationRight>,
399}
400
401impl HookMutationRights {
402 /// Observe only.
403 /// This is data-only and does not perform I/O, call host ports, append journals, publish
404 /// events, or start processes.
405 pub fn observe_only() -> Self {
406 Self {
407 rights: set([HookMutationRight::Observe]),
408 }
409 }
410
411 /// Builds the deny or request approval value with the documented defaults.
412 /// This is data-only and does not perform I/O, call host ports, append journals, publish
413 /// events, or start processes.
414 pub fn deny_or_request_approval() -> Self {
415 Self {
416 rights: set([
417 HookMutationRight::Observe,
418 HookMutationRight::Deny,
419 HookMutationRight::RequestApproval,
420 ]),
421 }
422 }
423
424 /// Constructs this value from rights. Use it when adapting
425 /// canonical SDK records without introducing a second behavior
426 /// path.
427 pub fn from_rights(rights: impl IntoIterator<Item = HookMutationRight>) -> Self {
428 let mut rights = rights.into_iter().collect::<BTreeSet<_>>();
429 rights.insert(HookMutationRight::Observe);
430 Self { rights }
431 }
432
433 /// Returns whether allows response class applies for this contract.
434 /// This is data-only and does not perform I/O, call host ports, append journals, publish
435 /// events, or start processes.
436 pub fn allows_response_class(&self, response_class: HookResponseClass) -> bool {
437 self.rights
438 .contains(&HookMutationRight::from_response_class(response_class))
439 }
440
441 /// Validates the package::hooks invariants and returns a typed
442 /// error on failure. Validation is pure and does not perform I/O,
443 /// dispatch, journal appends, or adapter calls.
444 pub fn validate_for_point(&self, point: &HookPoint) -> Result<(), AgentError> {
445 let allowed = point.allowed_response_classes();
446 for right in &self.rights {
447 let response_class = right.response_class();
448 if !allowed.contains(&response_class) {
449 return Err(invalid_package(format!(
450 "hook mutation right {:?} is not allowed at {:?}",
451 right, point
452 )));
453 }
454 }
455 Ok(())
456 }
457
458 /// Reports whether this value is observe only. The check is pure
459 /// and does not mutate SDK or host state.
460 pub fn is_observe_only(&self) -> bool {
461 self.rights == set([HookMutationRight::Observe])
462 }
463
464 /// Returns whether can change behavior applies for this state.
465 /// This is data-only and does not perform I/O, call host ports, append journals, publish
466 /// events, or start processes.
467 pub fn can_change_behavior(&self) -> bool {
468 self.rights
469 .iter()
470 .any(|right| right.response_class().changes_behavior())
471 }
472}
473
474#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
475#[serde(rename_all = "snake_case")]
476/// Enumerates the finite hook mutation right cases.
477/// Serialized names are part of the SDK contract; update fixtures when variants change.
478pub enum HookMutationRight {
479 /// Use this variant when the contract needs to represent observe; selecting it has no side effect by itself.
480 Observe,
481 /// Use this variant when the contract needs to represent inject context; selecting it has no side effect by itself.
482 InjectContext,
483 /// Use this variant when the contract needs to represent modify projection; selecting it has no side effect by itself.
484 ModifyProjection,
485 /// Use this variant when the contract needs to represent request compaction; selecting it has no side effect by itself.
486 RequestCompaction,
487 /// Use this variant when the contract needs to represent modify validation hints; selecting it has no side effect by itself.
488 ModifyValidationHints,
489 /// Use this variant when the contract needs to represent modify tool request; selecting it has no side effect by itself.
490 ModifyToolRequest,
491 /// Use this variant when the contract needs to represent modify approval request; selecting it has no side effect by itself.
492 ModifyApprovalRequest,
493 /// Use this variant when the contract needs to represent deny; selecting it has no side effect by itself.
494 Deny,
495 /// Use this variant when the contract needs to represent request approval; selecting it has no side effect by itself.
496 RequestApproval,
497 /// Use this variant when the contract needs to represent request retry; selecting it has no side effect by itself.
498 RequestRetry,
499 /// Use this variant when the contract needs to represent rewrite tool result; selecting it has no side effect by itself.
500 RewriteToolResult,
501 /// Use this variant when the contract needs to represent modify subagent request; selecting it has no side effect by itself.
502 ModifySubagentRequest,
503 /// Use this variant when the contract needs to represent modify process request; selecting it has no side effect by itself.
504 ModifyProcessRequest,
505 /// Use this variant when the contract needs to represent validate detach; selecting it has no side effect by itself.
506 ValidateDetach,
507 /// Use this variant when the contract needs to represent request usage rollup repair; selecting it has no side effect by itself.
508 RequestUsageRollupRepair,
509 /// Use this variant when the contract needs to represent request cleanup repair; selecting it has no side effect by itself.
510 RequestCleanupRepair,
511 /// Use this variant when the contract needs to represent mark protected context; selecting it has no side effect by itself.
512 MarkProtectedContext,
513 /// Use this variant when the contract needs to represent request projection audit repair; selecting it has no side effect by itself.
514 RequestProjectionAuditRepair,
515 /// Use this variant when the contract needs to represent stop completion with repair needed; selecting it has no side effect by itself.
516 StopCompletionWithRepairNeeded,
517 /// Use this variant when the contract needs to represent stop run; selecting it has no side effect by itself.
518 StopRun,
519}
520
521impl HookMutationRight {
522 /// Returns the response class currently held by this value.
523 /// This is data-only and does not perform I/O, call host ports, append journals, publish
524 /// events, or start processes.
525 pub fn response_class(&self) -> HookResponseClass {
526 match self {
527 Self::Observe => HookResponseClass::Observe,
528 Self::InjectContext => HookResponseClass::InjectContext,
529 Self::ModifyProjection => HookResponseClass::ModifyProjection,
530 Self::RequestCompaction => HookResponseClass::RequestCompaction,
531 Self::ModifyValidationHints => HookResponseClass::ModifyValidationHints,
532 Self::ModifyToolRequest => HookResponseClass::ModifyToolRequest,
533 Self::ModifyApprovalRequest => HookResponseClass::ModifyApprovalRequest,
534 Self::Deny => HookResponseClass::Deny,
535 Self::RequestApproval => HookResponseClass::RequestApproval,
536 Self::RequestRetry => HookResponseClass::RequestRetry,
537 Self::RewriteToolResult => HookResponseClass::RewriteToolResult,
538 Self::ModifySubagentRequest => HookResponseClass::ModifySubagentRequest,
539 Self::ModifyProcessRequest => HookResponseClass::ModifyProcessRequest,
540 Self::ValidateDetach => HookResponseClass::ValidateDetach,
541 Self::RequestUsageRollupRepair => HookResponseClass::RequestUsageRollupRepair,
542 Self::RequestCleanupRepair => HookResponseClass::RequestCleanupRepair,
543 Self::MarkProtectedContext => HookResponseClass::MarkProtectedContext,
544 Self::RequestProjectionAuditRepair => HookResponseClass::RequestProjectionAuditRepair,
545 Self::StopCompletionWithRepairNeeded => {
546 HookResponseClass::StopCompletionWithRepairNeeded
547 }
548 Self::StopRun => HookResponseClass::StopRun,
549 }
550 }
551
552 /// Constructs this value from response class. Use it when adapting
553 /// canonical SDK records without introducing a second behavior
554 /// path.
555 pub fn from_response_class(response_class: HookResponseClass) -> Self {
556 match response_class {
557 HookResponseClass::Observe => Self::Observe,
558 HookResponseClass::InjectContext => Self::InjectContext,
559 HookResponseClass::ModifyProjection => Self::ModifyProjection,
560 HookResponseClass::RequestCompaction => Self::RequestCompaction,
561 HookResponseClass::ModifyValidationHints => Self::ModifyValidationHints,
562 HookResponseClass::ModifyToolRequest => Self::ModifyToolRequest,
563 HookResponseClass::ModifyApprovalRequest => Self::ModifyApprovalRequest,
564 HookResponseClass::Deny => Self::Deny,
565 HookResponseClass::RequestApproval => Self::RequestApproval,
566 HookResponseClass::RequestRetry => Self::RequestRetry,
567 HookResponseClass::RewriteToolResult => Self::RewriteToolResult,
568 HookResponseClass::ModifySubagentRequest => Self::ModifySubagentRequest,
569 HookResponseClass::ModifyProcessRequest => Self::ModifyProcessRequest,
570 HookResponseClass::ValidateDetach => Self::ValidateDetach,
571 HookResponseClass::RequestUsageRollupRepair => Self::RequestUsageRollupRepair,
572 HookResponseClass::RequestCleanupRepair => Self::RequestCleanupRepair,
573 HookResponseClass::MarkProtectedContext => Self::MarkProtectedContext,
574 HookResponseClass::RequestProjectionAuditRepair => Self::RequestProjectionAuditRepair,
575 HookResponseClass::StopCompletionWithRepairNeeded => {
576 Self::StopCompletionWithRepairNeeded
577 }
578 HookResponseClass::StopRun => Self::StopRun,
579 }
580 }
581}
582
583#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
584#[serde(rename_all = "snake_case")]
585/// Enumerates the finite hook response class cases.
586/// Serialized names are part of the SDK contract; update fixtures when variants change.
587pub enum HookResponseClass {
588 /// Use this variant when the contract needs to represent observe; selecting it has no side effect by itself.
589 Observe,
590 /// Use this variant when the contract needs to represent inject context; selecting it has no side effect by itself.
591 InjectContext,
592 /// Use this variant when the contract needs to represent modify projection; selecting it has no side effect by itself.
593 ModifyProjection,
594 /// Use this variant when the contract needs to represent request compaction; selecting it has no side effect by itself.
595 RequestCompaction,
596 /// Use this variant when the contract needs to represent modify validation hints; selecting it has no side effect by itself.
597 ModifyValidationHints,
598 /// Use this variant when the contract needs to represent modify tool request; selecting it has no side effect by itself.
599 ModifyToolRequest,
600 /// Use this variant when the contract needs to represent modify approval request; selecting it has no side effect by itself.
601 ModifyApprovalRequest,
602 /// Use this variant when the contract needs to represent deny; selecting it has no side effect by itself.
603 Deny,
604 /// Use this variant when the contract needs to represent request approval; selecting it has no side effect by itself.
605 RequestApproval,
606 /// Use this variant when the contract needs to represent request retry; selecting it has no side effect by itself.
607 RequestRetry,
608 /// Use this variant when the contract needs to represent rewrite tool result; selecting it has no side effect by itself.
609 RewriteToolResult,
610 /// Use this variant when the contract needs to represent modify subagent request; selecting it has no side effect by itself.
611 ModifySubagentRequest,
612 /// Use this variant when the contract needs to represent modify process request; selecting it has no side effect by itself.
613 ModifyProcessRequest,
614 /// Use this variant when the contract needs to represent validate detach; selecting it has no side effect by itself.
615 ValidateDetach,
616 /// Use this variant when the contract needs to represent request usage rollup repair; selecting it has no side effect by itself.
617 RequestUsageRollupRepair,
618 /// Use this variant when the contract needs to represent request cleanup repair; selecting it has no side effect by itself.
619 RequestCleanupRepair,
620 /// Use this variant when the contract needs to represent mark protected context; selecting it has no side effect by itself.
621 MarkProtectedContext,
622 /// Use this variant when the contract needs to represent request projection audit repair; selecting it has no side effect by itself.
623 RequestProjectionAuditRepair,
624 /// Use this variant when the contract needs to represent stop completion with repair needed; selecting it has no side effect by itself.
625 StopCompletionWithRepairNeeded,
626 /// Use this variant when the contract needs to represent stop run; selecting it has no side effect by itself.
627 StopRun,
628}
629
630impl HookResponseClass {
631 /// Returns whether changes behavior applies for this state.
632 /// This is data-only and does not perform I/O, call host ports, append journals, publish
633 /// events, or start processes.
634 pub fn changes_behavior(&self) -> bool {
635 !matches!(self, Self::Observe)
636 }
637}
638
639#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
640#[serde(rename_all = "snake_case")]
641/// Enumerates the finite hook privacy policy cases.
642/// Serialized names are part of the SDK contract; update fixtures when variants change.
643pub enum HookPrivacyPolicy {
644 /// Use this variant when the contract needs to represent envelope and redacted summary; selecting it has no side effect by itself.
645 EnvelopeAndRedactedSummary,
646 /// Use this variant when the contract needs to represent content refs only; selecting it has no side effect by itself.
647 ContentRefsOnly,
648 /// Use this variant when the contract needs to represent content capture allowed; selecting it has no side effect by itself.
649 ContentCaptureAllowed,
650}
651
652#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
653/// Describes the hook spec portion of a runtime package snapshot.
654/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
655pub struct HookSpec {
656 /// Stable hook id used for typed lineage, lookup, or dedupe.
657 pub hook_id: HookId,
658 /// Point used by this record or request.
659 pub point: HookPoint,
660 /// Source label or ref for this item; it is metadata and does not fetch
661 /// content by itself.
662 pub source: HookSource,
663 /// Ordering used by this record or request.
664 pub ordering: HookOrdering,
665 /// Execution used by this record or request.
666 pub execution: HookExecutionMode,
667 /// Timeout used by this record or request.
668 pub timeout: HookTimeoutPolicy,
669 /// Failure used by this record or request.
670 pub failure: HookFailurePolicy,
671 /// Mutation rights used by this record or request.
672 pub mutation_rights: HookMutationRights,
673 /// Privacy class used for projection, telemetry, and raw-content access
674 /// decisions.
675 pub privacy: HookPrivacyPolicy,
676 /// Policy reference that must be resolved by the host or runtime before
677 /// execution.
678 pub policy_ref: PolicyRef,
679 /// Typed executor ref reference. Resolving or executing it is a separate
680 /// policy-gated step.
681 pub executor_ref: HookExecutorRef,
682}
683
684impl HookSpec {
685 /// Observe.
686 /// This is data-only and does not perform I/O, call host ports, append journals, publish
687 /// events, or start processes.
688 pub fn observe(
689 hook_id: impl Into<String>,
690 point: HookPoint,
691 source: HookSource,
692 executor_ref: impl Into<String>,
693 policy_ref: PolicyRef,
694 ) -> Self {
695 Self {
696 hook_id: HookId::new(hook_id),
697 point,
698 source,
699 ordering: HookOrdering::normal(100),
700 execution: HookExecutionMode::nonblocking_observe_default(),
701 timeout: HookTimeoutPolicy::bounded_ms(250),
702 failure: HookFailurePolicy::FailOpenObserveOnly,
703 mutation_rights: HookMutationRights::observe_only(),
704 privacy: HookPrivacyPolicy::EnvelopeAndRedactedSummary,
705 policy_ref,
706 executor_ref: HookExecutorRef::new(executor_ref),
707 }
708 }
709
710 /// Builds the blocking value.
711 /// This is data construction and performs no I/O, journal append, event publication, or
712 /// process work.
713 pub fn blocking(
714 hook_id: impl Into<String>,
715 point: HookPoint,
716 source: HookSource,
717 executor_ref: impl Into<String>,
718 policy_ref: PolicyRef,
719 mutation_rights: HookMutationRights,
720 ) -> Self {
721 Self {
722 hook_id: HookId::new(hook_id),
723 point,
724 source,
725 ordering: HookOrdering::normal(100),
726 execution: HookExecutionMode::Blocking,
727 timeout: HookTimeoutPolicy::bounded_ms(250),
728 failure: HookFailurePolicy::InterruptRun,
729 mutation_rights,
730 privacy: HookPrivacyPolicy::EnvelopeAndRedactedSummary,
731 policy_ref,
732 executor_ref: HookExecutorRef::new(executor_ref),
733 }
734 }
735
736 /// Validates the package::hooks invariants and returns a typed
737 /// error on failure. Validation is pure and does not perform I/O,
738 /// dispatch, journal appends, or adapter calls.
739 pub fn validate(&self) -> Result<(), AgentError> {
740 if self.timeout.timeout_ms == 0 {
741 return Err(invalid_package("hook timeout_ms must be greater than zero"));
742 }
743 if self.policy_ref.as_str().is_empty() {
744 return Err(invalid_package("hook policy_ref is required"));
745 }
746 self.mutation_rights.validate_for_point(&self.point)?;
747 if !self.execution.is_blocking() && self.mutation_rights.can_change_behavior() {
748 return Err(invalid_package(
749 "nonblocking hooks must be observe-only in the first Rust slice",
750 ));
751 }
752 if self.is_security_relevant()
753 && matches!(self.failure, HookFailurePolicy::FailOpenObserveOnly)
754 {
755 return Err(invalid_package(
756 "security-relevant hooks cannot use fail-open failure policy",
757 ));
758 }
759 Ok(())
760 }
761
762 /// Reports whether this value is security relevant. The check is
763 /// pure and does not mutate SDK or host state.
764 pub fn is_security_relevant(&self) -> bool {
765 self.point.is_security_critical() || self.mutation_rights.can_change_behavior()
766 }
767
768 /// Builds the sort key value.
769 /// This is data construction and performs no I/O, journal append, event publication, or
770 /// process work.
771 pub fn sort_key(&self) -> (HookPoint, HookOrderingPhase, i32, String) {
772 (
773 self.point.clone(),
774 self.ordering.phase.clone(),
775 self.ordering.order,
776 self.hook_id.as_str().to_string(),
777 )
778 }
779
780 /// Builds the sidecar id value.
781 /// This is data construction and performs no I/O, journal append, event publication, or
782 /// process work.
783 pub fn sidecar_id(&self) -> String {
784 format!("hook.{}", self.hook_id.as_str())
785 }
786
787 /// Computes the stable spec hash for this package::hooks value. The
788 /// computation is deterministic and side-effect free so it can be
789 /// used in package, journal, or test evidence.
790 pub fn spec_hash(&self) -> Result<String, AgentError> {
791 self.validate()?;
792 let bytes = serde_json::to_vec(self).map_err(|error| {
793 AgentError::contract_violation(format!("hook spec serialization failed: {error}"))
794 })?;
795 let digest = Sha256::digest(bytes);
796 Ok(format!("sha256:{}", hex_lower(&digest)))
797 }
798
799 /// Builds the sidecar snapshot value.
800 /// This is data construction and performs no I/O, journal append, event publication, or
801 /// process work.
802 pub fn sidecar_snapshot(&self) -> Result<PackageSidecarSnapshot, AgentError> {
803 let content_hash = self.spec_hash()?;
804 let mut sidecar_ref =
805 PackageSidecarRef::new(self.sidecar_id(), HOOK_SIDECAR_KIND, HOOK_SIDECAR_VERSION);
806 sidecar_ref.content_hash = Some(content_hash.clone());
807 Ok(PackageSidecarSnapshot {
808 sidecar_id: self.sidecar_id(),
809 kind: HOOK_SIDECAR_KIND.to_string(),
810 version: HOOK_SIDECAR_VERSION.to_string(),
811 refs: vec![sidecar_ref],
812 policy_refs: vec![self.policy_ref.clone()],
813 content_hash,
814 redacted_payload: None,
815 })
816 }
817}
818
819#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
820/// Describes the hook config portion of a runtime package snapshot.
821/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
822pub struct HookConfig {
823 /// Stable hook id used for typed lineage, lookup, or dedupe.
824 pub hook_id: HookId,
825 /// Point used by this record or request.
826 pub point: HookPoint,
827 /// Source label or ref for this item; it is metadata and does not fetch
828 /// content by itself.
829 pub source: HookSource,
830 /// Ordering used by this record or request.
831 pub ordering: HookOrdering,
832 /// Execution used by this record or request.
833 pub execution: HookExecutionMode,
834 /// Timeout used by this record or request.
835 pub timeout: HookTimeoutPolicy,
836 /// Failure used by this record or request.
837 pub failure: HookFailurePolicy,
838 /// Mutation rights used by this record or request.
839 pub mutation_rights: HookMutationRights,
840 /// Privacy class used for projection, telemetry, and raw-content access
841 /// decisions.
842 pub privacy: HookPrivacyPolicy,
843 /// Policy reference that must be resolved by the host or runtime before
844 /// execution.
845 pub policy_ref: PolicyRef,
846 /// Typed executor ref reference. Resolving or executing it is a separate
847 /// policy-gated step.
848 pub executor_ref: HookExecutorRef,
849}
850
851impl HookConfig {
852 /// Builds the lower value.
853 /// This is data construction and performs no I/O, journal append, event publication, or
854 /// process work.
855 pub fn lower(self) -> Result<HookSpec, AgentError> {
856 let spec = HookSpec {
857 hook_id: self.hook_id,
858 point: self.point,
859 source: self.source,
860 ordering: self.ordering,
861 execution: self.execution,
862 timeout: self.timeout,
863 failure: self.failure,
864 mutation_rights: self.mutation_rights,
865 privacy: self.privacy,
866 policy_ref: self.policy_ref,
867 executor_ref: self.executor_ref,
868 };
869 spec.validate()?;
870 Ok(spec)
871 }
872}
873
874/// Builds the lower code hook value.
875/// This is data construction and performs no I/O, journal append, event publication, or process
876pub fn lower_code_hook(
877 hook_id: impl Into<String>,
878 point: HookPoint,
879 executor_ref: impl Into<String>,
880 policy_ref: PolicyRef,
881) -> Result<HookSpec, AgentError> {
882 let spec = HookSpec::observe(
883 hook_id,
884 point,
885 HookSource::InProcess,
886 executor_ref,
887 policy_ref,
888 );
889 spec.validate()?;
890 Ok(spec)
891}
892
893/// Validates the package::hooks invariants and returns a typed error on
894/// failure. Validation is pure and does not perform I/O, dispatch,
895/// journal appends, or adapter calls.
896pub fn validate_hook_specs(specs: &[HookSpec]) -> Result<(), AgentError> {
897 for spec in specs {
898 spec.validate()?;
899 }
900 Ok(())
901}
902
903/// Returns the ordered hooks for point currently held by this value.
904/// This is data-only and does not perform I/O, call host ports, append journals, publish
905/// events, or start processes.
906pub fn ordered_hooks_for_point(specs: &[HookSpec], point: HookPoint) -> Vec<HookSpec> {
907 let mut hooks = specs
908 .iter()
909 .filter(|spec| spec.point == point)
910 .cloned()
911 .collect::<Vec<_>>();
912 hooks.sort_by_key(HookSpec::sort_key);
913 hooks
914}
915
916#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
917/// Describes the hook input portion of a runtime package snapshot.
918/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
919pub struct HookInput {
920 /// Stable hook id used for typed lineage, lookup, or dedupe.
921 pub hook_id: HookId,
922 /// Point used by this record or request.
923 pub point: HookPoint,
924 /// Run identifier used for lineage, filtering, replay, and dedupe.
925 pub run_id: RunId,
926 /// Agent identifier used for lineage, filtering, and ownership checks.
927 pub agent_id: AgentId,
928 #[serde(skip_serializing_if = "Option::is_none")]
929 /// Turn identifier for one loop turn within a run.
930 pub turn_id: Option<TurnId>,
931 #[serde(skip_serializing_if = "Option::is_none")]
932 /// Attempt identifier for retry, repair, provider, or tool execution
933 /// evidence.
934 pub attempt_id: Option<AttemptId>,
935 /// Source label or ref for this item; it is metadata and does not fetch
936 /// content by itself.
937 pub source: SourceRef,
938 #[serde(skip_serializing_if = "Option::is_none")]
939 /// Destination label or ref for this item; it is metadata and does not
940 /// deliver content by itself.
941 pub destination: Option<DestinationRef>,
942 /// Deterministic package fingerprint used for stale checks, package
943 /// evidence, or replay comparisons.
944 pub package_fingerprint: RuntimePackageFingerprint,
945 /// View used by this record or request.
946 pub view: HookView,
947 #[serde(default, skip_serializing_if = "Vec::is_empty")]
948 /// Policy references that govern admission, projection, execution, or
949 /// delivery.
950 pub policy_refs: Vec<PolicyRef>,
951 /// Cancellation used by this record or request.
952 pub cancellation: HookCancellationToken,
953}
954
955#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
956/// Describes the hook cancellation token portion of a runtime package snapshot.
957/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
958pub struct HookCancellationToken {
959 /// Whether cancelled is enabled.
960 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
961 pub cancelled: bool,
962}
963
964impl HookCancellationToken {
965 /// Cancelled.
966 /// This is data-only and does not perform I/O, call host ports, append journals, publish
967 /// events, or start processes.
968 pub fn cancelled() -> Self {
969 Self { cancelled: true }
970 }
971}
972
973#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
974/// Describes the hook view portion of a runtime package snapshot.
975/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
976pub struct HookView {
977 /// Privacy class used for projection, telemetry, and raw-content access
978 /// decisions.
979 pub privacy: PrivacyClass,
980 /// Redacted human-readable summary safe for events, telemetry, and logs.
981 pub redacted_summary: String,
982 #[serde(default, skip_serializing_if = "Vec::is_empty")]
983 /// Typed subject refs references. Resolving them is separate from
984 /// constructing this record.
985 pub subject_refs: Vec<EntityRef>,
986 #[serde(default, skip_serializing_if = "Vec::is_empty")]
987 /// Content references associated with this record; resolving them is a
988 /// separate policy-gated step.
989 pub content_refs: Vec<crate::domain::ContentRef>,
990}
991
992impl HookView {
993 /// Returns an updated value with redacted configured.
994 /// This is data-only and does not perform I/O, call host ports, append journals, publish
995 /// events, or start processes.
996 pub fn redacted(redacted_summary: impl Into<String>) -> Self {
997 Self {
998 privacy: PrivacyClass::ContentRefsOnly,
999 redacted_summary: redacted_summary.into(),
1000 subject_refs: Vec::new(),
1001 content_refs: Vec::new(),
1002 }
1003 }
1004}
1005
1006#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1007#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
1008/// Enumerates the finite hook response cases.
1009/// Serialized names are part of the SDK contract; update fixtures when variants change.
1010pub enum HookResponse {
1011 /// Use this variant when the contract needs to represent observe only; selecting it has no side effect by itself.
1012 ObserveOnly,
1013 /// Use this variant when the contract needs to represent inject context; selecting it has no side effect by itself.
1014 InjectContext(Vec<ContextInjectionRequest>),
1015 /// Use this variant when the contract needs to represent modify projection; selecting it has no side effect by itself.
1016 ModifyProjection(ProjectionPatch),
1017 /// Use this variant when the contract needs to represent request compaction; selecting it has no side effect by itself.
1018 RequestCompaction(CompactionRequest),
1019 /// Use this variant when the contract needs to represent modify validation hints; selecting it has no side effect by itself.
1020 ModifyValidationHints(ValidationHintPatch),
1021 /// Use this variant when the contract needs to represent modify tool request; selecting it has no side effect by itself.
1022 ModifyToolRequest(ToolRequestPatch),
1023 /// Use this variant when the contract needs to represent modify approval request; selecting it has no side effect by itself.
1024 ModifyApprovalRequest(ApprovalRequestPatch),
1025 /// Use this variant when the contract needs to represent deny; selecting it has no side effect by itself.
1026 Deny(DenyReason),
1027 /// Use this variant when the contract needs to represent request approval; selecting it has no side effect by itself.
1028 RequestApproval(ApprovalRequestPatch),
1029 /// Use this variant when the contract needs to represent request retry; selecting it has no side effect by itself.
1030 RequestRetry(RetryRequest),
1031 /// Use this variant when the contract needs to represent rewrite tool result; selecting it has no side effect by itself.
1032 RewriteToolResult(ToolResultPatch),
1033 /// Use this variant when the contract needs to represent modify subagent request; selecting it has no side effect by itself.
1034 ModifySubagentRequest(SubagentRequestPatch),
1035 /// Use this variant when the contract needs to represent modify process request; selecting it has no side effect by itself.
1036 ModifyProcessRequest(ProcessRequestPatch),
1037 /// Use this variant when the contract needs to represent validate detach; selecting it has no side effect by itself.
1038 ValidateDetach(DetachValidationRequest),
1039 /// Use this variant when the contract needs to represent request usage rollup repair; selecting it has no side effect by itself.
1040 RequestUsageRollupRepair(UsageRollupRepairRequest),
1041 /// Use this variant when the contract needs to represent request cleanup repair; selecting it has no side effect by itself.
1042 RequestCleanupRepair(CleanupRepairRequest),
1043 /// Use this variant when the contract needs to represent mark protected context; selecting it has no side effect by itself.
1044 MarkProtectedContext(Vec<ContextItemId>),
1045 /// Use this variant when the contract needs to represent request projection audit repair; selecting it has no side effect by itself.
1046 RequestProjectionAuditRepair(ProjectionAuditRepairRequest),
1047 /// Use this variant when the contract needs to represent stop completion with repair needed; selecting it has no side effect by itself.
1048 StopCompletionWithRepairNeeded(RepairNeededReason),
1049 /// Use this variant when the contract needs to represent stop run; selecting it has no side effect by itself.
1050 StopRun(StopReason),
1051}
1052
1053impl HookResponse {
1054 /// Returns the response class currently held by this value.
1055 /// This is data-only and does not perform I/O, call host ports, append journals, publish
1056 /// events, or start processes.
1057 pub fn response_class(&self) -> HookResponseClass {
1058 match self {
1059 Self::ObserveOnly => HookResponseClass::Observe,
1060 Self::InjectContext(_) => HookResponseClass::InjectContext,
1061 Self::ModifyProjection(_) => HookResponseClass::ModifyProjection,
1062 Self::RequestCompaction(_) => HookResponseClass::RequestCompaction,
1063 Self::ModifyValidationHints(_) => HookResponseClass::ModifyValidationHints,
1064 Self::ModifyToolRequest(_) => HookResponseClass::ModifyToolRequest,
1065 Self::ModifyApprovalRequest(_) => HookResponseClass::ModifyApprovalRequest,
1066 Self::Deny(_) => HookResponseClass::Deny,
1067 Self::RequestApproval(_) => HookResponseClass::RequestApproval,
1068 Self::RequestRetry(_) => HookResponseClass::RequestRetry,
1069 Self::RewriteToolResult(_) => HookResponseClass::RewriteToolResult,
1070 Self::ModifySubagentRequest(_) => HookResponseClass::ModifySubagentRequest,
1071 Self::ModifyProcessRequest(_) => HookResponseClass::ModifyProcessRequest,
1072 Self::ValidateDetach(_) => HookResponseClass::ValidateDetach,
1073 Self::RequestUsageRollupRepair(_) => HookResponseClass::RequestUsageRollupRepair,
1074 Self::RequestCleanupRepair(_) => HookResponseClass::RequestCleanupRepair,
1075 Self::MarkProtectedContext(_) => HookResponseClass::MarkProtectedContext,
1076 Self::RequestProjectionAuditRepair(_) => {
1077 HookResponseClass::RequestProjectionAuditRepair
1078 }
1079 Self::StopCompletionWithRepairNeeded(_) => {
1080 HookResponseClass::StopCompletionWithRepairNeeded
1081 }
1082 Self::StopRun(_) => HookResponseClass::StopRun,
1083 }
1084 }
1085
1086 /// Returns whether changes behavior applies for this state.
1087 /// This is data-only and does not perform I/O, call host ports, append journals, publish
1088 /// events, or start processes.
1089 pub fn changes_behavior(&self) -> bool {
1090 self.response_class().changes_behavior()
1091 }
1092}
1093
1094#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1095/// Describes the context injection request portion of a runtime package snapshot.
1096/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1097pub struct ContextInjectionRequest {
1098 /// Redacted human-readable summary safe for events, telemetry, and logs.
1099 pub redacted_summary: String,
1100 /// Policy references that govern admission, projection, execution, or
1101 /// delivery.
1102 pub policy_refs: Vec<PolicyRef>,
1103}
1104
1105#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1106/// Describes the projection patch portion of a runtime package snapshot.
1107/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1108pub struct ProjectionPatch {
1109 /// Redacted human-readable summary safe for events, telemetry, and logs.
1110 pub redacted_summary: String,
1111}
1112
1113#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1114/// Describes the compaction request portion of a runtime package snapshot.
1115/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1116pub struct CompactionRequest {
1117 /// Redacted human-readable summary safe for events, telemetry, and logs.
1118 pub redacted_summary: String,
1119}
1120
1121#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1122/// Describes the validation hint patch portion of a runtime package snapshot.
1123/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1124pub struct ValidationHintPatch {
1125 /// Redacted human-readable summary safe for events, telemetry, and logs.
1126 pub redacted_summary: String,
1127}
1128
1129#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1130/// Describes the tool request patch portion of a runtime package snapshot.
1131/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1132pub struct ToolRequestPatch {
1133 /// Redacted human-readable summary safe for events, telemetry, and logs.
1134 pub redacted_summary: String,
1135}
1136
1137#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1138/// Describes the approval request patch portion of a runtime package snapshot.
1139/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1140pub struct ApprovalRequestPatch {
1141 /// Redacted human-readable summary safe for events, telemetry, and logs.
1142 pub redacted_summary: String,
1143}
1144
1145#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1146/// Describes the deny reason portion of a runtime package snapshot.
1147/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1148pub struct DenyReason {
1149 /// Code used by this record or request.
1150 pub code: String,
1151 /// Redacted human-readable summary safe for events, telemetry, and logs.
1152 pub redacted_summary: String,
1153}
1154
1155#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1156/// Describes the retry request portion of a runtime package snapshot.
1157/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1158pub struct RetryRequest {
1159 /// Redacted human-readable summary safe for events, telemetry, and logs.
1160 pub redacted_summary: String,
1161}
1162
1163#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1164/// Describes the tool result patch portion of a runtime package snapshot.
1165/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1166pub struct ToolResultPatch {
1167 /// Redacted human-readable summary safe for events, telemetry, and logs.
1168 pub redacted_summary: String,
1169}
1170
1171#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1172/// Describes the subagent request patch portion of a runtime package snapshot.
1173/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1174pub struct SubagentRequestPatch {
1175 /// Redacted human-readable summary safe for events, telemetry, and logs.
1176 pub redacted_summary: String,
1177}
1178
1179#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1180/// Describes the process request patch portion of a runtime package snapshot.
1181/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1182pub struct ProcessRequestPatch {
1183 /// Redacted human-readable summary safe for events, telemetry, and logs.
1184 pub redacted_summary: String,
1185}
1186
1187#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1188/// Describes the detach validation request portion of a runtime package snapshot.
1189/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1190pub struct DetachValidationRequest {
1191 /// Redacted human-readable summary safe for events, telemetry, and logs.
1192 pub redacted_summary: String,
1193}
1194
1195#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1196/// Describes the usage rollup repair request portion of a runtime package snapshot.
1197/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1198pub struct UsageRollupRepairRequest {
1199 /// Redacted human-readable summary safe for events, telemetry, and logs.
1200 pub redacted_summary: String,
1201}
1202
1203#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1204/// Describes the cleanup repair request portion of a runtime package snapshot.
1205/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1206pub struct CleanupRepairRequest {
1207 /// Redacted human-readable summary safe for events, telemetry, and logs.
1208 pub redacted_summary: String,
1209}
1210
1211#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1212/// Describes the projection audit repair request portion of a runtime package snapshot.
1213/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1214pub struct ProjectionAuditRepairRequest {
1215 /// Redacted human-readable summary safe for events, telemetry, and logs.
1216 pub redacted_summary: String,
1217}
1218
1219#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1220/// Describes the repair needed reason portion of a runtime package snapshot.
1221/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1222pub struct RepairNeededReason {
1223 /// Code used by this record or request.
1224 pub code: String,
1225 /// Redacted human-readable summary safe for events, telemetry, and logs.
1226 pub redacted_summary: String,
1227}
1228
1229#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1230/// Describes the stop reason portion of a runtime package snapshot.
1231/// Use it when package authors or tests need explicit package configuration; validation and activation happen in package/runtime coordinators.
1232pub struct StopReason {
1233 /// Code used by this record or request.
1234 pub code: String,
1235 /// Redacted human-readable summary safe for events, telemetry, and logs.
1236 pub redacted_summary: String,
1237}
1238
1239/// Returns hook policy ref derived from the supplied state.
1240/// This is data-only and does not perform I/O, call host ports, append journals, publish
1241/// events, or start processes.
1242pub fn hook_policy_ref(id: impl Into<String>) -> PolicyRef {
1243 PolicyRef::with_kind(PolicyKind::RuntimePackage, id)
1244}
1245
1246fn set<T, const N: usize>(items: [T; N]) -> BTreeSet<T>
1247where
1248 T: Ord,
1249{
1250 items.into_iter().collect()
1251}
1252
1253fn invalid_package(message: impl Into<String>) -> AgentError {
1254 AgentError::new(
1255 AgentErrorKind::InvalidPackage,
1256 RetryClassification::HostConfigurationNeeded,
1257 message,
1258 )
1259}
1260
1261fn hex_lower(bytes: &[u8]) -> String {
1262 let mut output = String::with_capacity(bytes.len() * 2);
1263 for byte in bytes {
1264 output.push_str(&format!("{byte:02x}"));
1265 }
1266 output
1267}