Skip to main content

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}