Skip to main content

agent_sdk_core/records/
event.rs

1//! Canonical event records for live observation and telemetry projection. Use these
2//! records to describe what happened without requiring raw content capture.
3//! Constructors are data-only; delivery belongs to event buses and sinks.
4//!
5use std::num::NonZeroUsize;
6
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10use crate::{
11    domain::{
12        AgentError, AgentId, ArchiveCursorId, ContextItemId, CorrelationEntry, DestinationKind,
13        DestinationRef, EntityKind, EntityRef, EventId, JournalCursor, MessageId, PolicyRef,
14        PrivacyClass, RunId, SourceKind, SourceRef, SpanId, TraceId, TurnId,
15    },
16    ids::AttemptId,
17};
18
19macro_rules! typed_string {
20    ($name:ident, $debug:literal) => {
21        #[doc = concat!(
22                            "Typed event-string wrapper for `",
23                            stringify!($name),
24                            "`. Use it for stable event filter, cursor, or fingerprint fields; ",
25                            "constructing it is data-only and performs no side effects."
26                        )]
27        #[derive(Clone, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
28        #[serde(transparent)]
29        pub struct $name(String);
30
31        impl $name {
32            /// Creates a new records::event value with explicit
33            /// caller-provided inputs. This constructor is data-only
34            /// and performs no I/O or external side effects.
35            pub fn new(value: impl Into<String>) -> Self {
36                Self(value.into())
37            }
38
39            /// Returns this value as str. The accessor is side-effect
40            /// free and keeps ownership with the caller.
41            pub fn as_str(&self) -> &str {
42                &self.0
43            }
44        }
45
46        impl core::fmt::Debug for $name {
47            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48                formatter.write_str(concat!($debug, "(redacted)"))
49            }
50        }
51
52        impl core::fmt::Display for $name {
53            fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54                formatter.write_str(concat!($debug, "(redacted)"))
55            }
56        }
57    };
58}
59
60/// Constant value for the records::event contract. Use it to keep SDK
61/// records and tests aligned on the same stable value.
62pub const EVENT_SCHEMA_VERSION: u16 = 1;
63
64#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
65#[serde(rename_all = "snake_case")]
66/// Enumerates the finite event family cases.
67/// Serialized names are part of the SDK contract; update fixtures when variants change.
68pub enum EventFamily {
69    /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
70    Run,
71    /// Use this variant when the contract needs to represent turn; selecting it has no side effect by itself.
72    Turn,
73    /// Use this variant when the contract needs to represent message; selecting it has no side effect by itself.
74    Message,
75    /// Use this variant when the contract needs to represent model; selecting it has no side effect by itself.
76    Model,
77    /// Use this variant when the contract needs to represent tool; selecting it has no side effect by itself.
78    Tool,
79    /// Use this variant when the contract needs to represent approval; selecting it has no side effect by itself.
80    Approval,
81    /// Use this variant when the contract needs to represent hook; selecting it has no side effect by itself.
82    Hook,
83    /// Use this variant when the contract needs to represent context; selecting it has no side effect by itself.
84    Context,
85    /// Use this variant when the contract needs to represent stream rule; selecting it has no side effect by itself.
86    StreamRule,
87    /// Use this variant when the contract needs to represent realtime; selecting it has no side effect by itself.
88    Realtime,
89    /// Use this variant when the contract needs to represent isolation; selecting it has no side effect by itself.
90    Isolation,
91    /// Use this variant when the contract needs to represent child lifecycle; selecting it has no side effect by itself.
92    ChildLifecycle,
93    /// Use this variant when the contract needs to represent agent pool; selecting it has no side effect by itself.
94    AgentPool,
95    /// Use this variant when the contract needs to represent subagent; selecting it has no side effect by itself.
96    Subagent,
97    /// Use this variant when the contract needs to represent extension; selecting it has no side effect by itself.
98    Extension,
99    /// Use this variant when the contract needs to represent structured output; selecting it has no side effect by itself.
100    StructuredOutput,
101    /// Use this variant when the contract needs to represent output; selecting it has no side effect by itself.
102    Output,
103    /// Use this variant when the contract needs to represent output delivery; selecting it has no side effect by itself.
104    OutputDelivery,
105    /// Use this variant when the contract needs to represent telemetry; selecting it has no side effect by itself.
106    Telemetry,
107    /// Use this variant when the contract needs to represent recovery; selecting it has no side effect by itself.
108    Recovery,
109}
110
111#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
112#[serde(rename_all = "snake_case")]
113/// Enumerates the finite event kind cases.
114/// Serialized names are part of the SDK contract; update fixtures when variants change.
115pub enum EventKind {
116    /// Use this variant when the contract needs to represent run started; selecting it has no side effect by itself.
117    RunStarted,
118    /// Use this variant when the contract needs to represent run completed; selecting it has no side effect by itself.
119    RunCompleted,
120    /// Use this variant when the contract needs to represent run failed; selecting it has no side effect by itself.
121    RunFailed,
122    /// Use this variant when the contract needs to represent run cancelled; selecting it has no side effect by itself.
123    RunCancelled,
124    /// Use this variant when the contract needs to represent run checkpointed; selecting it has no side effect by itself.
125    RunCheckpointed,
126    /// Use this variant when the contract needs to represent run cancel requested; selecting it has no side effect by itself.
127    RunCancelRequested,
128    /// Use this variant when the contract needs to represent run resume requested; selecting it has no side effect by itself.
129    RunResumeRequested,
130    /// Use this variant when the contract needs to represent run resume failed; selecting it has no side effect by itself.
131    RunResumeFailed,
132    /// Use this variant when the contract needs to represent turn started; selecting it has no side effect by itself.
133    TurnStarted,
134    /// Use this variant when the contract needs to represent turn completed; selecting it has no side effect by itself.
135    TurnCompleted,
136    /// Use this variant when the contract needs to represent turn failed; selecting it has no side effect by itself.
137    TurnFailed,
138    /// Use this variant when the contract needs to represent message accepted; selecting it has no side effect by itself.
139    MessageAccepted,
140    /// Use this variant when the contract needs to represent message committed; selecting it has no side effect by itself.
141    MessageCommitted,
142    /// Use this variant when the contract needs to represent provider request projected; selecting it has no side effect by itself.
143    ProviderRequestProjected,
144    /// Use this variant when the contract needs to represent model attempt started; selecting it has no side effect by itself.
145    ModelAttemptStarted,
146    /// Use this variant when the contract needs to represent model stream delta; selecting it has no side effect by itself.
147    ModelStreamDelta,
148    /// Use this variant when the contract needs to represent model message completed; selecting it has no side effect by itself.
149    ModelMessageCompleted,
150    /// Use this variant when the contract needs to represent model attempt failed; selecting it has no side effect by itself.
151    ModelAttemptFailed,
152    /// Use this variant when the contract needs to represent tool requested; selecting it has no side effect by itself.
153    ToolRequested,
154    /// Use this variant when the contract needs to represent tool started; selecting it has no side effect by itself.
155    ToolStarted,
156    /// Use this variant when the contract needs to represent tool completed; selecting it has no side effect by itself.
157    ToolCompleted,
158    /// Use this variant when the contract needs to represent tool failed; selecting it has no side effect by itself.
159    ToolFailed,
160    /// Use this variant when the contract needs to represent tool denied; selecting it has no side effect by itself.
161    ToolDenied,
162    /// Use this variant when the contract needs to represent tool recovery required; selecting it has no side effect by itself.
163    ToolRecoveryRequired,
164    /// Use this variant when the contract needs to represent approval requested; selecting it has no side effect by itself.
165    ApprovalRequested,
166    /// Use this variant when the contract needs to represent approval dispatched; selecting it has no side effect by itself.
167    ApprovalDispatched,
168    /// Use this variant when the contract needs to represent approval dispatch unavailable; selecting it has no side effect by itself.
169    ApprovalDispatchUnavailable,
170    /// Use this variant when the contract needs to represent approval responded; selecting it has no side effect by itself.
171    ApprovalResponded,
172    /// Use this variant when the contract needs to represent approval denied; selecting it has no side effect by itself.
173    ApprovalDenied,
174    /// Use this variant when the contract needs to represent approval timed out; selecting it has no side effect by itself.
175    ApprovalTimedOut,
176    /// Use this variant when the contract needs to represent approval cancelled; selecting it has no side effect by itself.
177    ApprovalCancelled,
178    /// Use this variant when the contract needs to represent hook registered; selecting it has no side effect by itself.
179    HookRegistered,
180    /// Use this variant when the contract needs to represent hook invoked; selecting it has no side effect by itself.
181    HookInvoked,
182    /// Use this variant when the contract needs to represent hook completed; selecting it has no side effect by itself.
183    HookCompleted,
184    /// Use this variant when the contract needs to represent hook failed; selecting it has no side effect by itself.
185    HookFailed,
186    /// Use this variant when the contract needs to represent hook timed out; selecting it has no side effect by itself.
187    HookTimedOut,
188    /// Use this variant when the contract needs to represent hook cancelled; selecting it has no side effect by itself.
189    HookCancelled,
190    /// Use this variant when the contract needs to represent hook response applied; selecting it has no side effect by itself.
191    HookResponseApplied,
192    /// Use this variant when the contract needs to represent hook response rejected; selecting it has no side effect by itself.
193    HookResponseRejected,
194    /// Use this variant when the contract needs to represent context assembled; selecting it has no side effect by itself.
195    ContextAssembled,
196    /// Use this variant when the contract needs to represent stream rule registered; selecting it has no side effect by itself.
197    StreamRuleRegistered,
198    /// Use this variant when the contract needs to represent stream rule compile failed; selecting it has no side effect by itself.
199    StreamRuleCompileFailed,
200    /// Use this variant when the contract needs to represent stream rule matched; selecting it has no side effect by itself.
201    StreamRuleMatched,
202    /// Use this variant when the contract needs to represent stream intervention requested; selecting it has no side effect by itself.
203    StreamInterventionRequested,
204    /// Use this variant when the contract needs to represent stream intervention applied; selecting it has no side effect by itself.
205    StreamInterventionApplied,
206    /// Use this variant when the contract needs to represent stream rule repeat state recorded; selecting it has no side effect by itself.
207    StreamRuleRepeatStateRecorded,
208    /// Use this variant when the contract needs to represent realtime connect requested; selecting it has no side effect by itself.
209    RealtimeConnectRequested,
210    /// Use this variant when the contract needs to represent realtime connected; selecting it has no side effect by itself.
211    RealtimeConnected,
212    /// Use this variant when the contract needs to represent realtime input send requested; selecting it has no side effect by itself.
213    RealtimeInputSendRequested,
214    /// Use this variant when the contract needs to represent realtime input sent; selecting it has no side effect by itself.
215    RealtimeInputSent,
216    /// Use this variant when the contract needs to represent realtime output receive requested; selecting it has no side effect by itself.
217    RealtimeOutputReceiveRequested,
218    /// Use this variant when the contract needs to represent realtime output received; selecting it has no side effect by itself.
219    RealtimeOutputReceived,
220    /// Use this variant when the contract needs to represent realtime interrupted; selecting it has no side effect by itself.
221    RealtimeInterrupted,
222    /// Use this variant when the contract needs to represent realtime restart requested; selecting it has no side effect by itself.
223    RealtimeRestartRequested,
224    /// Use this variant when the contract needs to represent realtime restart started; selecting it has no side effect by itself.
225    RealtimeRestartStarted,
226    /// Use this variant when the contract needs to represent realtime restart completed; selecting it has no side effect by itself.
227    RealtimeRestartCompleted,
228    /// Use this variant when the contract needs to represent realtime restart failed; selecting it has no side effect by itself.
229    RealtimeRestartFailed,
230    /// Use this variant when the contract needs to represent realtime close requested; selecting it has no side effect by itself.
231    RealtimeCloseRequested,
232    /// Use this variant when the contract needs to represent realtime closed; selecting it has no side effect by itself.
233    RealtimeClosed,
234    /// Use this variant when the contract needs to represent realtime backpressure applied; selecting it has no side effect by itself.
235    RealtimeBackpressureApplied,
236    /// Use this variant when the contract needs to represent isolation requested; selecting it has no side effect by itself.
237    IsolationRequested,
238    /// Use this variant when the contract needs to represent isolation adapter health checked; selecting it has no side effect by itself.
239    IsolationAdapterHealthChecked,
240    /// Use this variant when the contract needs to represent isolation capability matched; selecting it has no side effect by itself.
241    IsolationCapabilityMatched,
242    /// Use this variant when the contract needs to represent isolation downgrade denied; selecting it has no side effect by itself.
243    IsolationDowngradeDenied,
244    /// Use this variant when the contract needs to represent isolation downgrade approved; selecting it has no side effect by itself.
245    IsolationDowngradeApproved,
246    /// Use this variant when the contract needs to represent isolation environment prepared; selecting it has no side effect by itself.
247    IsolationEnvironmentPrepared,
248    /// Use this variant when the contract needs to represent isolation process started; selecting it has no side effect by itself.
249    IsolationProcessStarted,
250    /// Use this variant when the contract needs to represent isolation process io captured; selecting it has no side effect by itself.
251    IsolationProcessIoCaptured,
252    /// Use this variant when the contract needs to represent isolation process stats recorded; selecting it has no side effect by itself.
253    IsolationProcessStatsRecorded,
254    /// Use this variant when the contract needs to represent isolation cleanup started; selecting it has no side effect by itself.
255    IsolationCleanupStarted,
256    /// Use this variant when the contract needs to represent isolation cleanup completed; selecting it has no side effect by itself.
257    IsolationCleanupCompleted,
258    /// Use this variant when the contract needs to represent isolation cleanup failed; selecting it has no side effect by itself.
259    IsolationCleanupFailed,
260    /// Use this variant when the contract needs to represent isolation failed; selecting it has no side effect by itself.
261    IsolationFailed,
262    /// Use this variant when the contract needs to represent child lifecycle requested; selecting it has no side effect by itself.
263    ChildLifecycleRequested,
264    /// Use this variant when the contract needs to represent child lifecycle completed; selecting it has no side effect by itself.
265    ChildLifecycleCompleted,
266    /// Use this variant when the contract needs to represent child lifecycle detached; selecting it has no side effect by itself.
267    ChildLifecycleDetached,
268    /// Use this variant when the contract needs to represent subagent started; selecting it has no side effect by itself.
269    SubagentStarted,
270    /// Use this variant when the contract needs to represent subagent handoff; selecting it has no side effect by itself.
271    SubagentHandoff,
272    /// Use this variant when the contract needs to represent subagent event wrapped; selecting it has no side effect by itself.
273    SubagentEventWrapped,
274    /// Use this variant when the contract needs to represent subagent usage rolled up; selecting it has no side effect by itself.
275    SubagentUsageRolledUp,
276    /// Use this variant when the contract needs to represent subagent completed; selecting it has no side effect by itself.
277    SubagentCompleted,
278    /// Use this variant when the contract needs to represent extension action submitted; selecting it has no side effect by itself.
279    ExtensionActionSubmitted,
280    /// Use this variant when the contract needs to represent extension action started; selecting it has no side effect by itself.
281    ExtensionActionStarted,
282    /// Use this variant when the contract needs to represent extension action completed; selecting it has no side effect by itself.
283    ExtensionActionCompleted,
284    /// Use this variant when the contract needs to represent extension action failed; selecting it has no side effect by itself.
285    ExtensionActionFailed,
286    /// Use this variant when the contract needs to represent extension action denied; selecting it has no side effect by itself.
287    ExtensionActionDenied,
288    /// Use this variant when the contract needs to represent output dispatch requested; selecting it has no side effect by itself.
289    OutputDispatchRequested,
290    /// Use this variant when the contract needs to represent output dispatch completed; selecting it has no side effect by itself.
291    OutputDispatchCompleted,
292    /// Use this variant when the contract needs to represent output dispatch failed; selecting it has no side effect by itself.
293    OutputDispatchFailed,
294    /// Use this variant when the contract needs to represent output dispatch deduped; selecting it has no side effect by itself.
295    OutputDispatchDeduped,
296    /// Use this variant when the contract needs to represent structured output requested; selecting it has no side effect by itself.
297    StructuredOutputRequested,
298    /// Use this variant when the contract needs to represent structured output validation started; selecting it has no side effect by itself.
299    StructuredOutputValidationStarted,
300    /// Use this variant when the contract needs to represent structured output validation failed; selecting it has no side effect by itself.
301    StructuredOutputValidationFailed,
302    /// Use this variant when the contract needs to represent structured output repair requested; selecting it has no side effect by itself.
303    StructuredOutputRepairRequested,
304    /// Use this variant when the contract needs to represent structured output validated; selecting it has no side effect by itself.
305    StructuredOutputValidated,
306    /// Use this variant when the contract needs to represent structured output failed; selecting it has no side effect by itself.
307    StructuredOutputFailed,
308    /// Use this variant when the contract needs to represent telemetry sink failed; selecting it has no side effect by itself.
309    TelemetrySinkFailed,
310    /// Use this variant when the contract needs to represent telemetry sink recovered; selecting it has no side effect by itself.
311    TelemetrySinkRecovered,
312    /// Use this variant when the contract needs to represent usage recorded; selecting it has no side effect by itself.
313    UsageRecorded,
314    /// Use this variant when the contract needs to represent cost estimated; selecting it has no side effect by itself.
315    CostEstimated,
316    /// Use this variant when the contract needs to represent cost corrected; selecting it has no side effect by itself.
317    CostCorrected,
318    /// Use this variant when the contract needs to represent replay started; selecting it has no side effect by itself.
319    ReplayStarted,
320    /// Use this variant when the contract needs to represent replay completed; selecting it has no side effect by itself.
321    ReplayCompleted,
322    /// Use this variant when the contract needs to represent replay failed; selecting it has no side effect by itself.
323    ReplayFailed,
324    /// Use this variant when the contract needs to represent agent pool created; selecting it has no side effect by itself.
325    AgentPoolCreated,
326    /// Use this variant when the contract needs to represent agent pool run joined; selecting it has no side effect by itself.
327    AgentPoolRunJoined,
328    /// Use this variant when the contract needs to represent agent pool run left; selecting it has no side effect by itself.
329    AgentPoolRunLeft,
330    /// Use this variant when the contract needs to represent run message accepted; selecting it has no side effect by itself.
331    RunMessageAccepted,
332    /// Use this variant when the contract needs to represent run message delivered; selecting it has no side effect by itself.
333    RunMessageDelivered,
334    /// Use this variant when the contract needs to represent run message responded; selecting it has no side effect by itself.
335    RunMessageResponded,
336    /// Use this variant when the contract needs to represent run message failed; selecting it has no side effect by itself.
337    RunMessageFailed,
338    /// Use this variant when the contract needs to represent run message timed out; selecting it has no side effect by itself.
339    RunMessageTimedOut,
340    /// Use this variant when the contract needs to represent run message expired; selecting it has no side effect by itself.
341    RunMessageExpired,
342    /// Use this variant when the contract needs to represent run message cancelled; selecting it has no side effect by itself.
343    RunMessageCancelled,
344    /// Use this variant when the contract needs to represent wake condition registered; selecting it has no side effect by itself.
345    WakeConditionRegistered,
346    /// Use this variant when the contract needs to represent wake condition triggered; selecting it has no side effect by itself.
347    WakeConditionTriggered,
348    /// Use this variant when the contract needs to represent wake condition timed out; selecting it has no side effect by itself.
349    WakeConditionTimedOut,
350    /// Use this variant when the contract needs to represent wake condition cancelled; selecting it has no side effect by itself.
351    WakeConditionCancelled,
352    /// Use this variant when the contract needs to represent wake condition failed; selecting it has no side effect by itself.
353    WakeConditionFailed,
354}
355
356impl EventKind {
357    /// Reports whether this value is terminal. The check is pure and
358    /// does not mutate SDK or host state.
359    pub fn is_terminal(&self) -> bool {
360        matches!(
361            self,
362            Self::RunCompleted
363                | Self::RunFailed
364                | Self::RunCancelled
365                | Self::TurnCompleted
366                | Self::TurnFailed
367                | Self::ModelMessageCompleted
368                | Self::ModelAttemptFailed
369                | Self::ToolCompleted
370                | Self::ToolFailed
371                | Self::ToolDenied
372                | Self::ToolRecoveryRequired
373                | Self::ApprovalResponded
374                | Self::OutputDispatchCompleted
375                | Self::OutputDispatchFailed
376                | Self::OutputDispatchDeduped
377                | Self::ApprovalDenied
378                | Self::ApprovalTimedOut
379                | Self::ApprovalCancelled
380                | Self::ApprovalDispatchUnavailable
381                | Self::HookCompleted
382                | Self::HookFailed
383                | Self::HookTimedOut
384                | Self::HookCancelled
385                | Self::HookResponseApplied
386                | Self::HookResponseRejected
387                | Self::RealtimeRestartFailed
388                | Self::RealtimeClosed
389                | Self::IsolationCleanupCompleted
390                | Self::IsolationCleanupFailed
391                | Self::IsolationFailed
392                | Self::ChildLifecycleCompleted
393                | Self::ChildLifecycleDetached
394                | Self::SubagentCompleted
395                | Self::ExtensionActionCompleted
396                | Self::ExtensionActionFailed
397                | Self::ExtensionActionDenied
398                | Self::StructuredOutputValidated
399                | Self::StructuredOutputFailed
400                | Self::ReplayCompleted
401                | Self::ReplayFailed
402                | Self::RunMessageResponded
403                | Self::RunMessageFailed
404                | Self::RunMessageTimedOut
405                | Self::RunMessageExpired
406                | Self::RunMessageCancelled
407                | Self::WakeConditionTriggered
408                | Self::WakeConditionTimedOut
409                | Self::WakeConditionCancelled
410                | Self::WakeConditionFailed
411        )
412    }
413}
414
415#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
416/// Carries the agent event record payload for journal, event, or fixture surfaces.
417/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
418pub struct AgentEvent {
419    /// Envelope used by this record or request.
420    pub envelope: EventEnvelope,
421    /// Payload carried by this record.
422    /// Use the surrounding policy and redaction fields to decide whether it can be exposed.
423    pub payload: EventPayload,
424}
425
426impl AgentEvent {
427    /// Builds the envelope only value.
428    /// This is data construction and performs no I/O, journal append, event publication, or
429    /// process work.
430    pub fn envelope_only(envelope: EventEnvelope) -> Self {
431        Self {
432            envelope,
433            payload: EventPayload::EnvelopeOnly,
434        }
435    }
436
437    /// Returns this value with its redacted summary setting replaced.
438    /// The method follows builder-style data construction and does not
439    /// execute external work.
440    pub fn with_redacted_summary(
441        envelope: EventEnvelope,
442        redacted_summary: impl Into<String>,
443    ) -> Self {
444        Self {
445            envelope,
446            payload: EventPayload::RedactedSummary {
447                redacted_summary: redacted_summary.into(),
448                payload_refs: Vec::new(),
449            },
450        }
451    }
452
453    /// Reads the stored redacted summary without registry or runtime work.
454    /// This is data-only and does not perform I/O, call host ports, append journals, publish
455    /// events, or start processes.
456    pub fn redacted_summary(&self) -> Option<&str> {
457        match &self.payload {
458            EventPayload::EnvelopeOnly => None,
459            EventPayload::RedactedSummary {
460                redacted_summary, ..
461            } => Some(redacted_summary.as_str()),
462        }
463    }
464}
465
466#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
467/// Carries the event envelope record payload for journal, event, or fixture surfaces.
468/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
469pub struct EventEnvelope {
470    /// Wire schema version used for compatibility checks.
471    pub schema_version: u16,
472    /// Event identifier used to correlate live events with journal or replay
473    /// evidence.
474    pub event_id: EventId,
475    /// Event seq used by this record or request.
476    pub event_seq: u64,
477    /// Event family used by this record or request.
478    pub event_family: EventFamily,
479    /// Kind discriminator for event kind.
480    /// Use it to route finite match arms without parsing display text.
481    pub event_kind: EventKind,
482    /// Wire schema version for this record shape.
483    /// Use it for compatibility checks before deserializing or replaying stored data.
484    pub payload_schema_version: u16,
485    /// Timestamp in milliseconds associated with this record.
486    /// Use it for ordering and diagnostics; durable causality still comes from ids and cursors.
487    pub timestamp: String,
488    /// Recorded at used by this record or request.
489    pub recorded_at: String,
490    /// Run identifier used for lineage, filtering, replay, and dedupe.
491    pub run_id: RunId,
492    /// Agent identifier used for lineage, filtering, and ownership checks.
493    pub agent_id: AgentId,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    /// Turn identifier for one loop turn within a run.
496    pub turn_id: Option<TurnId>,
497    #[serde(skip_serializing_if = "Option::is_none")]
498    /// Attempt identifier for retry, repair, provider, or tool execution
499    /// evidence.
500    pub attempt_id: Option<AttemptId>,
501    #[serde(skip_serializing_if = "Option::is_none")]
502    /// Message identifier for transcript, projection, or provider-response
503    /// lineage.
504    pub message_id: Option<MessageId>,
505    #[serde(skip_serializing_if = "Option::is_none")]
506    /// Stable context item id used for typed lineage, lookup, or dedupe.
507    pub context_item_id: Option<ContextItemId>,
508    /// Stable trace id used for typed lineage, lookup, or dedupe.
509    pub trace_id: TraceId,
510    /// Stable span id used for typed lineage, lookup, or dedupe.
511    pub span_id: SpanId,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    /// Stable parent event id used for typed lineage, lookup, or dedupe.
514    pub parent_event_id: Option<EventId>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    /// Optional caused by value.
517    /// When absent, callers should use the documented default or skip that optional behavior.
518    pub caused_by: Option<CausalRef>,
519    /// Typed subject ref reference. Resolving or executing it is a separate
520    /// policy-gated step.
521    pub subject_ref: EntityRef,
522    #[serde(default, skip_serializing_if = "Vec::is_empty")]
523    /// Typed related refs references. Resolving them is separate from
524    /// constructing this record.
525    pub related_refs: Vec<EntityRef>,
526    #[serde(default, skip_serializing_if = "Vec::is_empty")]
527    /// Typed causal refs references. Resolving them is separate from
528    /// constructing this record.
529    pub causal_refs: Vec<CausalRef>,
530    /// Correlation used by this record or request.
531    pub correlation: EventCorrelation,
532    #[serde(default, skip_serializing_if = "Vec::is_empty")]
533    /// Tag selector for event filtering.
534    /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
535    pub tags: Vec<EventTag>,
536    /// Source label or ref for this item; it is metadata and does not fetch
537    /// content by itself.
538    pub source: SourceRef,
539    #[serde(skip_serializing_if = "Option::is_none")]
540    /// Destination label or ref for this item; it is metadata and does not
541    /// deliver content by itself.
542    pub destination: Option<DestinationRef>,
543    #[serde(default, skip_serializing_if = "Vec::is_empty")]
544    /// Policy references that govern admission, projection, execution, or
545    /// delivery.
546    pub policy_refs: Vec<PolicyRef>,
547    #[serde(skip_serializing_if = "Option::is_none")]
548    /// Cursor identifying a replay, export, or subscription position.
549    /// Use it to resume without widening the original scope.
550    pub journal_cursor: Option<JournalCursor>,
551    #[serde(skip_serializing_if = "Option::is_none")]
552    /// Optional state before value.
553    /// When absent, callers should use the documented default or skip that optional behavior.
554    pub state_before: Option<String>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    /// Optional state after value.
557    /// When absent, callers should use the documented default or skip that optional behavior.
558    pub state_after: Option<String>,
559    /// Delivery-semantic selector for event filtering.
560    /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
561    /// semantics.
562    pub delivery_semantics: EventDeliverySemantics,
563    /// Privacy class used for projection, telemetry, and raw-content access
564    /// decisions.
565    pub privacy: PrivacyClass,
566    /// Content capture used by this record or request.
567    pub content_capture: ContentCaptureMode,
568    /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
569    pub redaction_policy_id: String,
570    /// Fingerprint of the runtime package snapshot in force when this value was produced.
571    /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
572    /// not execute package behavior.
573    pub runtime_package_fingerprint: String,
574}
575
576impl EventEnvelope {
577    /// Builds the cursor value.
578    /// This is data construction and performs no I/O, journal append, event publication, or
579    /// process work.
580    pub fn cursor(&self, scope: EventStreamScope) -> EventCursor {
581        EventCursor {
582            scope,
583            event_seq: self.event_seq,
584            event_id: self.event_id.clone(),
585            journal_cursor: self.journal_cursor.clone(),
586        }
587    }
588
589    /// Builds the redacted summary value.
590    /// This is data construction and performs no I/O, journal append, event publication, or
591    /// process work.
592    pub fn redacted_summary(&self) -> String {
593        format!("{:?}/{:?}", self.event_family, self.event_kind)
594    }
595}
596
597#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
598#[serde(tag = "mode", rename_all = "snake_case")]
599/// Enumerates the finite event payload cases.
600/// Serialized names are part of the SDK contract; update fixtures when variants change.
601pub enum EventPayload {
602    /// Use this variant when the contract needs to represent envelope only; selecting it has no side effect by itself.
603    EnvelopeOnly,
604    /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
605    RedactedSummary {
606        /// Redacted human-readable summary safe for events, telemetry, and
607        /// logs.
608        redacted_summary: String,
609        #[serde(default, skip_serializing_if = "Vec::is_empty")]
610        /// Typed payload refs references. Resolving them is separate from
611        /// constructing this record.
612        payload_refs: Vec<EntityRef>,
613    },
614}
615
616#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
617/// Carries the causal ref record payload for journal, event, or fixture surfaces.
618/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
619pub struct CausalRef {
620    #[serde(skip_serializing_if = "Option::is_none")]
621    /// Event identifier used to correlate live events with journal or replay
622    /// evidence.
623    pub event_id: Option<EventId>,
624    /// Typed subject ref reference. Resolving or executing it is a separate
625    /// policy-gated step.
626    pub subject_ref: EntityRef,
627    #[serde(skip_serializing_if = "Option::is_none")]
628    /// Redacted explanation for a denial, failure, status, or package delta.
629    pub reason: Option<String>,
630}
631
632#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
633/// Carries the event correlation record payload for journal, event, or fixture surfaces.
634/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
635pub struct EventCorrelation {
636    #[serde(default, skip_serializing_if = "Vec::is_empty")]
637    /// Bounded entries included in this record. Limits and truncation are
638    /// represented by companion metadata when applicable.
639    pub entries: Vec<CorrelationEntry>,
640}
641
642#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
643#[serde(transparent)]
644/// Carries the event tag record payload for journal, event, or fixture surfaces.
645/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
646pub struct EventTag(String);
647
648impl EventTag {
649    /// Creates a new records::event value with explicit caller-provided
650    /// inputs. This constructor is data-only and performs no I/O or
651    /// external side effects.
652    pub fn new(value: impl Into<String>) -> Self {
653        Self(value.into())
654    }
655
656    /// Returns this value as str. The accessor is side-effect free and
657    /// keeps ownership with the caller.
658    pub fn as_str(&self) -> &str {
659        &self.0
660    }
661}
662
663#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
664/// Carries the event frame record payload for journal, event, or fixture surfaces.
665/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
666pub struct EventFrame {
667    /// Event used by this record or request.
668    pub event: AgentEvent,
669    /// Cursor identifying a replay, export, or subscription position.
670    /// Use it to resume without widening the original scope.
671    pub cursor: EventCursor,
672    #[serde(skip_serializing_if = "Option::is_none")]
673    /// Cursor identifying a replay, export, or subscription position.
674    /// Use it to resume without widening the original scope.
675    pub archive_cursor: Option<ArchiveCursor>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    /// Overflow policy applied when a subscriber queue reaches capacity.
678    /// It decides whether to drop, summarize, backpressure, or fail the subscriber.
679    pub overflow: Option<EventOverflowNotice>,
680}
681
682#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
683/// Carries the event cursor record payload for journal, event, or fixture surfaces.
684/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
685pub struct EventCursor {
686    /// Scope used by this record or request.
687    pub scope: EventStreamScope,
688    /// Event seq used by this record or request.
689    pub event_seq: u64,
690    /// Event identifier used to correlate live events with journal or replay
691    /// evidence.
692    pub event_id: EventId,
693    #[serde(skip_serializing_if = "Option::is_none")]
694    /// Cursor identifying a replay, export, or subscription position.
695    /// Use it to resume without widening the original scope.
696    pub journal_cursor: Option<JournalCursor>,
697}
698
699#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
700#[serde(rename_all = "snake_case")]
701/// Enumerates the finite event stream scope cases.
702/// Serialized names are part of the SDK contract; update fixtures when variants change.
703pub enum EventStreamScope {
704    /// Use this variant when the contract needs to represent all; selecting it has no side effect by itself.
705    All,
706    /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
707    Run(RunId),
708    /// Use this variant when the contract needs to represent agent; selecting it has no side effect by itself.
709    Agent(AgentId),
710    /// Use this variant when the contract needs to represent filter; selecting it has no side effect by itself.
711    Filter {
712        /// Stable filter id used for typed lineage, lookup, or dedupe.
713        filter_id: EventFilterId,
714        /// Deterministic filter fingerprint used for stale checks, package
715        /// evidence, or replay comparisons.
716        filter_fingerprint: EventFilterFingerprint,
717    },
718}
719
720#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
721/// Carries the archive cursor record payload for journal, event, or fixture surfaces.
722/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
723pub struct ArchiveCursor {
724    /// Stable archive id used for typed lineage, lookup, or dedupe.
725    pub archive_id: ArchiveCursorId,
726    /// Position used by this record or request.
727    pub position: String,
728    #[serde(skip_serializing_if = "Option::is_none")]
729    /// Event identifier used to correlate live events with journal or replay
730    /// evidence.
731    pub event_id: Option<EventId>,
732    #[serde(skip_serializing_if = "Option::is_none")]
733    /// Optional watermark value.
734    /// When absent, callers should use the documented default or skip that optional behavior.
735    pub watermark: Option<String>,
736}
737
738#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
739/// Carries the event overflow notice record payload for journal, event, or fixture surfaces.
740/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
741pub struct EventOverflowNotice {
742    /// Policy used by this record or request.
743    pub policy: SubscriberOverflowPolicy,
744    /// Count of dropped items observed or included in this record.
745    pub dropped_count: u64,
746    #[serde(skip_serializing_if = "Option::is_none")]
747    /// Optional gap start value.
748    /// When absent, callers should use the documented default or skip that optional behavior.
749    pub gap_start: Option<EventCursor>,
750    /// Gap end used by this record or request.
751    pub gap_end: EventCursor,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    /// Repair policy used after structured output validation fails.
754    /// It controls whether repair is attempted and which policy gates must approve it.
755    pub repair_from: Option<JournalCursor>,
756    /// Whether terminal preserved is enabled.
757    /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
758    pub terminal_preserved: bool,
759    /// Redacted explanation for a denial, failure, status, or package delta.
760    pub reason: EventOverflowReason,
761}
762
763#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
764#[serde(rename_all = "snake_case")]
765/// Enumerates the finite event overflow reason cases.
766/// Serialized names are part of the SDK contract; update fixtures when variants change.
767pub enum EventOverflowReason {
768    /// Use this variant when the contract needs to represent subscriber queue full; selecting it has no side effect by itself.
769    SubscriberQueueFull,
770    /// Use this variant when the contract needs to represent subscriber lagged; selecting it has no side effect by itself.
771    SubscriberLagged,
772    /// Use this variant when the contract needs to represent live buffer expired; selecting it has no side effect by itself.
773    LiveBufferExpired,
774    /// Use this variant when the contract needs to represent policy dropped progress; selecting it has no side effect by itself.
775    PolicyDroppedProgress,
776    /// Use this variant when the contract needs to represent policy dropped non terminal; selecting it has no side effect by itself.
777    PolicyDroppedNonTerminal,
778}
779
780#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
781#[serde(rename_all = "snake_case")]
782/// Enumerates the finite event delivery semantics cases.
783/// Serialized names are part of the SDK contract; update fixtures when variants change.
784pub enum EventDeliverySemantics {
785    /// Use this variant when the contract needs to represent best effort live; selecting it has no side effect by itself.
786    BestEffortLive,
787    /// Use this variant when the contract needs to represent journal backed; selecting it has no side effect by itself.
788    JournalBacked,
789    /// Use this variant when the contract needs to represent derived replay; selecting it has no side effect by itself.
790    DerivedReplay,
791    /// Use this variant when the contract needs to represent diagnostic only; selecting it has no side effect by itself.
792    DiagnosticOnly,
793}
794
795#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796#[serde(rename_all = "snake_case")]
797/// Enumerates the finite content capture mode cases.
798/// Serialized names are part of the SDK contract; update fixtures when variants change.
799pub enum ContentCaptureMode {
800    /// Use this variant when the contract needs to represent off; selecting it has no side effect by itself.
801    Off,
802    /// Use this variant when the contract needs to represent metadata only; selecting it has no side effect by itself.
803    MetadataOnly,
804    /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
805    RedactedSummary,
806    /// Use this variant when the contract needs to represent payload refs; selecting it has no side effect by itself.
807    PayloadRefs,
808    /// Use this variant when the contract needs to represent raw content; selecting it has no side effect by itself.
809    RawContent,
810}
811
812#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
813#[serde(rename_all = "snake_case")]
814/// Enumerates the finite payload access mode cases.
815/// Serialized names are part of the SDK contract; update fixtures when variants change.
816pub enum PayloadAccessMode {
817    /// Use this variant when the contract needs to represent envelope only; selecting it has no side effect by itself.
818    EnvelopeOnly,
819    /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
820    RedactedSummary,
821    /// Use this variant when the contract needs to represent payload refs; selecting it has no side effect by itself.
822    PayloadRefs,
823    /// Use this variant when the contract needs to represent full payload if policy allows; selecting it has no side effect by itself.
824    FullPayloadIfPolicyAllows,
825}
826
827#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
828#[serde(rename_all = "snake_case")]
829/// Enumerates the finite subscriber overflow policy cases.
830/// Serialized names are part of the SDK contract; update fixtures when variants change.
831pub enum SubscriberOverflowPolicy {
832    /// Use this variant when the contract needs to represent drop non terminal; selecting it has no side effect by itself.
833    DropNonTerminal,
834    /// Use this variant when the contract needs to represent drop progress; selecting it has no side effect by itself.
835    DropProgress,
836    /// Use this variant when the contract needs to represent summarize and continue; selecting it has no side effect by itself.
837    SummarizeAndContinue,
838    /// Use this variant when the contract needs to represent backpressure caller; selecting it has no side effect by itself.
839    BackpressureCaller,
840    /// Use this variant when the contract needs to represent fail subscriber; selecting it has no side effect by itself.
841    FailSubscriber,
842}
843
844#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
845/// Carries the subscriber queue config record payload for journal, event, or fixture surfaces.
846/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
847pub struct SubscriberQueueConfig {
848    /// Total subscriber queue capacity.
849    /// This bounds buffered event frames for a live subscriber.
850    pub capacity: NonZeroUsize,
851    /// Queue slots reserved for terminal frames.
852    /// This keeps important terminal events available even when non-terminal frames overflow.
853    pub terminal_reserve: NonZeroUsize,
854    /// Overflow policy applied when a subscriber queue reaches capacity.
855    /// It decides whether to drop, summarize, backpressure, or fail the subscriber.
856    pub overflow: SubscriberOverflowPolicy,
857}
858
859impl Default for SubscriberQueueConfig {
860    fn default() -> Self {
861        Self {
862            capacity: NonZeroUsize::new(64).expect("nonzero default capacity"),
863            terminal_reserve: NonZeroUsize::new(1).expect("nonzero terminal reserve"),
864            overflow: SubscriberOverflowPolicy::DropNonTerminal,
865        }
866    }
867}
868
869#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
870#[serde(rename_all = "snake_case")]
871/// Enumerates the finite event filter set cases.
872/// Serialized names are part of the SDK contract; update fixtures when variants change.
873pub enum EventFilterSet<T> {
874    #[default]
875    /// Use this variant when the contract needs to represent any; selecting it has no side effect by itself.
876    Any,
877    /// Use this variant when the contract needs to represent include; selecting it has no side effect by itself.
878    Include(Vec<T>),
879}
880
881impl<T: PartialEq> EventFilterSet<T> {
882    /// Returns matches for the current value.
883    /// This is a read-only or data-construction helper unless the method body explicitly calls
884    /// a port or store.
885    pub fn matches(&self, candidate: &T) -> bool {
886        match self {
887            Self::Any => true,
888            Self::Include(values) => values.contains(candidate),
889        }
890    }
891
892    /// Reports whether this value is any. The check is pure and does
893    /// not mutate SDK or host state.
894    pub fn is_any(&self) -> bool {
895        matches!(self, Self::Any)
896    }
897}
898
899#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
900/// Carries the event filter record payload for journal, event, or fixture surfaces.
901/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
902pub struct EventFilter {
903    /// Run-id selector for event filtering.
904    /// `Any` leaves run ids unconstrained; `Include` restricts matches to the listed runs.
905    pub run_ids: EventFilterSet<RunId>,
906    /// Agent-id selector for event filtering.
907    /// `Any` leaves agent ids unconstrained; `Include` restricts matches to the listed agents.
908    pub agent_ids: EventFilterSet<AgentId>,
909    /// Turn-id selector for event filtering.
910    /// `Any` leaves turn ids unconstrained; `Include` restricts matches to the listed turns.
911    pub turn_ids: EventFilterSet<TurnId>,
912    /// Event-family selector for event filtering.
913    /// `Any` leaves event families unconstrained; `Include` restricts matches to listed
914    /// families.
915    pub families: EventFilterSet<EventFamily>,
916    /// Event-kind selector for event filtering.
917    /// `Any` leaves event kinds unconstrained; `Include` restricts matches to listed event
918    /// kinds.
919    pub kinds: EventFilterSet<EventKind>,
920    /// Source-kind selector for event filtering.
921    /// `Any` leaves source kinds unconstrained; `Include` restricts matches to listed source
922    /// kinds.
923    pub source_kinds: EventFilterSet<SourceKind>,
924    /// Destination-kind selector for event filtering.
925    /// `Any` leaves destination kinds unconstrained; `Include` restricts matches to listed
926    /// destination kinds.
927    pub destination_kinds: EventFilterSet<DestinationKind>,
928    /// Subject entity-kind selector for event filtering.
929    /// `Any` leaves subject kinds unconstrained; `Include` restricts matches to listed entity
930    /// kinds.
931    pub subject_kinds: EventFilterSet<EntityKind>,
932    /// Related-entity kind selector for event filtering.
933    /// `Any` leaves related kinds unconstrained; `Include` restricts matches to listed entity
934    /// kinds.
935    pub related_entity_kinds: EventFilterSet<EntityKind>,
936    /// Correlation-key selector for event filtering.
937    /// `Any` leaves correlation keys unconstrained; `Include` restricts matches to listed keys.
938    pub correlation_keys: EventFilterSet<crate::domain::CorrelationKey>,
939    /// Tag selector for event filtering.
940    /// `Any` leaves tags unconstrained; `Include` restricts matches to listed event tags.
941    pub tags: EventFilterSet<EventTag>,
942    /// Privacy-class selector for event filtering.
943    /// `Any` leaves privacy classes unconstrained; `Include` restricts matches to listed
944    /// classes.
945    pub privacy_classes: EventFilterSet<PrivacyClass>,
946    /// Delivery-semantic selector for event filtering.
947    /// `Any` leaves delivery semantics unconstrained; `Include` restricts matches to listed
948    /// semantics.
949    pub delivery_semantics: EventFilterSet<EventDeliverySemantics>,
950    /// Whether the filter should match only terminal event frames.
951    /// When true, non-terminal frames are excluded even if all other selectors match.
952    pub terminal_only: bool,
953    /// Payload access mode allowed for matching event frames.
954    /// Use it to keep subscriptions envelope-only unless payload access is explicitly
955    /// requested.
956    pub payload_access: PayloadAccessMode,
957    /// Subscriber queue settings used for streams created with this filter.
958    /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
959    pub queue: SubscriberQueueConfig,
960}
961
962impl Default for EventFilter {
963    fn default() -> Self {
964        Self {
965            run_ids: EventFilterSet::Any,
966            agent_ids: EventFilterSet::Any,
967            turn_ids: EventFilterSet::Any,
968            families: EventFilterSet::Any,
969            kinds: EventFilterSet::Any,
970            source_kinds: EventFilterSet::Any,
971            destination_kinds: EventFilterSet::Any,
972            subject_kinds: EventFilterSet::Any,
973            related_entity_kinds: EventFilterSet::Any,
974            correlation_keys: EventFilterSet::Any,
975            tags: EventFilterSet::Any,
976            privacy_classes: EventFilterSet::Any,
977            delivery_semantics: EventFilterSet::Any,
978            terminal_only: false,
979            payload_access: PayloadAccessMode::EnvelopeOnly,
980            queue: SubscriberQueueConfig::default(),
981        }
982    }
983}
984
985impl EventFilter {
986    /// Builds the terminal run events value.
987    /// This is data construction and performs no I/O, journal append, event publication, or
988    /// process work.
989    pub fn terminal_run_events() -> Self {
990        Self {
991            families: EventFilterSet::Include(vec![EventFamily::Run]),
992            terminal_only: true,
993            ..Self::default()
994        }
995    }
996
997    /// Builds the run value with the documented defaults.
998    /// This is data-only and does not perform I/O, call host ports, append journals, publish
999    /// events, or start processes.
1000    pub fn run(run_id: RunId) -> Self {
1001        Self {
1002            run_ids: EventFilterSet::Include(vec![run_id]),
1003            ..Self::default()
1004        }
1005    }
1006
1007    /// Returns agent for the current value.
1008    /// This is a read-only or data-construction helper unless the method body explicitly calls
1009    /// a port or store.
1010    pub fn agent(agent_id: AgentId) -> Self {
1011        Self {
1012            agent_ids: EventFilterSet::Include(vec![agent_id]),
1013            ..Self::default()
1014        }
1015    }
1016
1017    /// Returns compile for the current value.
1018    /// This is a read-only or data-construction helper unless the method body explicitly calls
1019    /// a port or store.
1020    pub fn compile(self) -> Result<CompiledEventFilter, AgentError> {
1021        CompiledEventFilter::new(self)
1022    }
1023
1024    fn indexed_fields(&self) -> Vec<EventIndexField> {
1025        let mut fields = Vec::new();
1026        if !self.run_ids.is_any() {
1027            fields.push(EventIndexField::RunId);
1028        }
1029        if !self.agent_ids.is_any() {
1030            fields.push(EventIndexField::AgentId);
1031        }
1032        if !self.turn_ids.is_any() {
1033            fields.push(EventIndexField::TurnId);
1034        }
1035        if !self.families.is_any() {
1036            fields.push(EventIndexField::EventFamily);
1037        }
1038        if !self.kinds.is_any() {
1039            fields.push(EventIndexField::EventKind);
1040        }
1041        if !self.source_kinds.is_any() {
1042            fields.push(EventIndexField::Source);
1043        }
1044        if !self.destination_kinds.is_any() {
1045            fields.push(EventIndexField::Destination);
1046        }
1047        if !self.subject_kinds.is_any() {
1048            fields.push(EventIndexField::SubjectKind);
1049        }
1050        if !self.related_entity_kinds.is_any() {
1051            fields.push(EventIndexField::RelatedEntityKind);
1052        }
1053        if !self.correlation_keys.is_any() {
1054            fields.push(EventIndexField::CorrelationKey);
1055        }
1056        if !self.tags.is_any() {
1057            fields.push(EventIndexField::Tag);
1058        }
1059        if !self.privacy_classes.is_any() {
1060            fields.push(EventIndexField::Privacy);
1061        }
1062        if !self.delivery_semantics.is_any() {
1063            fields.push(EventIndexField::DeliverySemantics);
1064        }
1065        fields
1066    }
1067
1068    fn matches_envelope(&self, envelope: &EventEnvelope) -> bool {
1069        self.run_ids.matches(&envelope.run_id)
1070            && self.agent_ids.matches(&envelope.agent_id)
1071            && option_matches(&self.turn_ids, envelope.turn_id.as_ref())
1072            && self.families.matches(&envelope.event_family)
1073            && self.kinds.matches(&envelope.event_kind)
1074            && self.source_kinds.matches(&envelope.source.kind)
1075            && option_matches(
1076                &self.destination_kinds,
1077                envelope
1078                    .destination
1079                    .as_ref()
1080                    .map(|destination| &destination.kind),
1081            )
1082            && self.subject_kinds.matches(&envelope.subject_ref.kind)
1083            && any_matches(
1084                &self.related_entity_kinds,
1085                envelope.related_refs.iter().map(|entity| &entity.kind),
1086            )
1087            && any_matches(
1088                &self.correlation_keys,
1089                envelope.correlation.entries.iter().map(|entry| &entry.key),
1090            )
1091            && any_matches(&self.tags, envelope.tags.iter())
1092            && self.privacy_classes.matches(&envelope.privacy)
1093            && self
1094                .delivery_semantics
1095                .matches(&envelope.delivery_semantics)
1096            && (!self.terminal_only || envelope.event_kind.is_terminal())
1097    }
1098}
1099
1100#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1101/// Carries the compiled event filter record payload for journal, event, or fixture surfaces.
1102/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
1103pub struct CompiledEventFilter {
1104    /// Stable filter id used for typed lineage, lookup, or dedupe.
1105    pub filter_id: EventFilterId,
1106    /// Deterministic filter fingerprint used for stale checks, package
1107    /// evidence, or replay comparisons.
1108    pub filter_fingerprint: EventFilterFingerprint,
1109    /// Collection of indexed fields values.
1110    /// Ordering and membership should be treated as part of the serialized contract when
1111    /// relevant.
1112    pub indexed_fields: Vec<EventIndexField>,
1113    /// Payload access mode allowed for matching event frames.
1114    /// Use it to keep subscriptions envelope-only unless payload access is explicitly
1115    /// requested.
1116    pub payload_access: PayloadAccessMode,
1117    /// Subscriber queue settings used for streams created with this filter.
1118    /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
1119    pub queue: SubscriberQueueConfig,
1120    criteria: EventFilter,
1121}
1122
1123impl CompiledEventFilter {
1124    /// Creates a new records::event value with explicit caller-provided
1125    /// inputs. This constructor is data-only and performs no I/O or
1126    /// external side effects.
1127    pub fn new(criteria: EventFilter) -> Result<Self, AgentError> {
1128        let encoded = serde_json::to_vec(&criteria)
1129            .map_err(|error| AgentError::contract_violation(error.to_string()))?;
1130        let fingerprint = format!("sha256:{:x}", Sha256::digest(encoded));
1131        let filter_id = EventFilterId::new(format!("filter.{:x}", Sha256::digest(&fingerprint)));
1132        let indexed_fields = criteria.indexed_fields();
1133
1134        Ok(Self {
1135            filter_id,
1136            filter_fingerprint: EventFilterFingerprint::new(fingerprint),
1137            indexed_fields,
1138            payload_access: criteria.payload_access.clone(),
1139            queue: criteria.queue.clone(),
1140            criteria,
1141        })
1142    }
1143
1144    /// Returns matches envelope for the current value.
1145    /// This is a read-only or data-construction helper unless the method body explicitly calls
1146    /// a port or store.
1147    pub fn matches_envelope(&self, envelope: &EventEnvelope) -> bool {
1148        self.criteria.matches_envelope(envelope)
1149    }
1150
1151    /// Returns cursor scope derived from the supplied state.
1152    /// This is data-only and does not perform I/O, call host ports, append journals, publish
1153    /// events, or start processes.
1154    pub fn cursor_scope(&self) -> EventStreamScope {
1155        EventStreamScope::Filter {
1156            filter_id: self.filter_id.clone(),
1157            filter_fingerprint: self.filter_fingerprint.clone(),
1158        }
1159    }
1160
1161    /// Returns the criteria currently held by this value.
1162    /// This is data-only and does not perform I/O, call host ports, append journals, publish
1163    /// events, or start processes.
1164    pub fn criteria(&self) -> &EventFilter {
1165        &self.criteria
1166    }
1167}
1168
1169fn option_matches<T: PartialEq>(filter: &EventFilterSet<T>, candidate: Option<&T>) -> bool {
1170    match filter {
1171        EventFilterSet::Any => true,
1172        EventFilterSet::Include(_) => candidate.is_some_and(|candidate| filter.matches(candidate)),
1173    }
1174}
1175
1176fn any_matches<'a, T: PartialEq + 'a>(
1177    filter: &EventFilterSet<T>,
1178    candidates: impl Iterator<Item = &'a T>,
1179) -> bool {
1180    match filter {
1181        EventFilterSet::Any => true,
1182        EventFilterSet::Include(_) => candidates
1183            .into_iter()
1184            .any(|candidate| filter.matches(candidate)),
1185    }
1186}
1187
1188#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1189#[serde(rename_all = "snake_case")]
1190/// Enumerates the finite event index field cases.
1191/// Serialized names are part of the SDK contract; update fixtures when variants change.
1192pub enum EventIndexField {
1193    /// Use this variant when the contract needs to represent run id; selecting it has no side effect by itself.
1194    RunId,
1195    /// Use this variant when the contract needs to represent agent id; selecting it has no side effect by itself.
1196    AgentId,
1197    /// Use this variant when the contract needs to represent turn id; selecting it has no side effect by itself.
1198    TurnId,
1199    /// Use this variant when the contract needs to represent event family; selecting it has no side effect by itself.
1200    EventFamily,
1201    /// Use this variant when the contract needs to represent event kind; selecting it has no side effect by itself.
1202    EventKind,
1203    /// Use this variant when the contract needs to represent source; selecting it has no side effect by itself.
1204    Source,
1205    /// Use this variant when the contract needs to represent destination; selecting it has no side effect by itself.
1206    Destination,
1207    /// Use this variant when the contract needs to represent subject kind; selecting it has no side effect by itself.
1208    SubjectKind,
1209    /// Use this variant when the contract needs to represent related entity kind; selecting it has no side effect by itself.
1210    RelatedEntityKind,
1211    /// Use this variant when the contract needs to represent correlation key; selecting it has no side effect by itself.
1212    CorrelationKey,
1213    /// Use this variant when the contract needs to represent tag; selecting it has no side effect by itself.
1214    Tag,
1215    /// Use this variant when the contract needs to represent privacy; selecting it has no side effect by itself.
1216    Privacy,
1217    /// Use this variant when the contract needs to represent delivery semantics; selecting it has no side effect by itself.
1218    DeliverySemantics,
1219}
1220
1221typed_string!(EventFilterId, "EventFilterId");
1222typed_string!(EventFilterFingerprint, "EventFilterFingerprint");
1223
1224#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1225/// Carries the subscription options record payload for journal, event, or fixture surfaces.
1226/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
1227pub struct SubscriptionOptions {
1228    /// Subscriber queue settings used for streams created with this filter.
1229    /// It controls capacity, terminal reserve, and overflow behavior for the subscriber.
1230    pub queue: SubscriberQueueConfig,
1231    /// Payload access mode allowed for matching event frames.
1232    /// Use it to keep subscriptions envelope-only unless payload access is explicitly
1233    /// requested.
1234    pub payload_access: PayloadAccessMode,
1235}
1236
1237impl Default for SubscriptionOptions {
1238    fn default() -> Self {
1239        Self {
1240            queue: SubscriberQueueConfig::default(),
1241            payload_access: PayloadAccessMode::EnvelopeOnly,
1242        }
1243    }
1244}
1245
1246/// Returns cursor compatible derived from the supplied state.
1247/// This is data-only and does not perform I/O, call host ports, append journals, publish
1248/// events, or start processes.
1249pub fn cursor_compatible(
1250    requested_scope: &EventStreamScope,
1251    cursor: Option<&EventCursor>,
1252) -> Result<(), AgentError> {
1253    let Some(cursor) = cursor else {
1254        return Ok(());
1255    };
1256
1257    let compatible = match (requested_scope, &cursor.scope) {
1258        (EventStreamScope::All, EventStreamScope::All) => true,
1259        (EventStreamScope::Run(requested), EventStreamScope::Run(cursor_run)) => {
1260            requested == cursor_run
1261        }
1262        (EventStreamScope::Agent(requested), EventStreamScope::Agent(cursor_agent)) => {
1263            requested == cursor_agent
1264        }
1265        (
1266            EventStreamScope::Filter {
1267                filter_fingerprint: requested,
1268                ..
1269            },
1270            EventStreamScope::Filter {
1271                filter_fingerprint: cursor,
1272                ..
1273            },
1274        ) => requested == cursor,
1275        _ => false,
1276    };
1277
1278    if compatible {
1279        Ok(())
1280    } else {
1281        Err(AgentError::contract_violation(
1282            "cursor scope mismatch for requested event stream",
1283        ))
1284    }
1285}