Skip to main content

cookie_agent_protocol/
event.rs

1use std::{
2    borrow::Cow,
3    collections::{BTreeMap, HashSet},
4    fmt,
5};
6
7use jiff::Timestamp;
8use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
9use serde::de::IntoDeserializer;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use serde_path_to_error::Segment;
13
14use crate::*;
15
16fn producer_description_is_empty(description: &SafeDisplayText) -> bool {
17    description.as_str().is_empty()
18}
19
20#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub struct SessionTitle(String);
22impl SessionTitle {
23    pub const MAX_BYTES: usize = 512;
24    pub fn new(value: impl Into<String>) -> Result<Self, EventSchemaError> {
25        let value = value.into();
26        if value.trim().is_empty() {
27            return Err(EventSchemaError::EmptyTitle);
28        }
29        if value.len() > Self::MAX_BYTES {
30            return Err(EventSchemaError::TitleTooLong);
31        }
32        if value.chars().any(char::is_control) {
33            return Err(EventSchemaError::TitleControlCharacter);
34        }
35        Ok(Self(value))
36    }
37    #[must_use]
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41}
42impl fmt::Display for SessionTitle {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        self.0.fmt(f)
45    }
46}
47impl Serialize for SessionTitle {
48    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
49    where
50        S: serde::Serializer,
51    {
52        s.serialize_str(&self.0)
53    }
54}
55impl<'de> Deserialize<'de> for SessionTitle {
56    fn deserialize<D>(d: D) -> Result<Self, D::Error>
57    where
58        D: serde::Deserializer<'de>,
59    {
60        Self::new(String::deserialize(d)?).map_err(serde::de::Error::custom)
61    }
62}
63impl JsonSchema for SessionTitle {
64    fn inline_schema() -> bool {
65        true
66    }
67    fn schema_name() -> Cow<'static, str> {
68        Cow::Borrowed("SessionTitle")
69    }
70    fn json_schema(_: &mut SchemaGenerator) -> Schema {
71        json_schema!({"type":"string","minLength":1,"maxLength":512,"description":"Nonblank control-free UTF-8 session title."})
72    }
73}
74
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub struct EventOrigin(String);
77impl EventOrigin {
78    pub fn new(value: impl Into<String>) -> Result<Self, EventSchemaError> {
79        let value = value.into();
80        if value == "user" {
81            return Ok(Self(value));
82        }
83        let Some((class, slug)) = value.split_once(':') else {
84            return Err(EventSchemaError::InvalidOrigin);
85        };
86        if !matches!(class, "engine" | "plugin" | "client")
87            || slug.is_empty()
88            || slug.len() > 64
89            || !slug.bytes().enumerate().all(|(index, byte)| {
90                byte.is_ascii_lowercase() || byte.is_ascii_digit() || (index > 0 && byte == b'-')
91            })
92        {
93            return Err(EventSchemaError::InvalidOrigin);
94        }
95        Ok(Self(value))
96    }
97
98    #[must_use]
99    pub fn as_str(&self) -> &str {
100        &self.0
101    }
102
103    #[must_use]
104    pub fn plugin_name(&self) -> Option<&str> {
105        self.0.strip_prefix("plugin:")
106    }
107}
108impl fmt::Display for EventOrigin {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        self.0.fmt(f)
111    }
112}
113impl Serialize for EventOrigin {
114    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115    where
116        S: serde::Serializer,
117    {
118        serializer.serialize_str(&self.0)
119    }
120}
121impl<'de> Deserialize<'de> for EventOrigin {
122    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123    where
124        D: serde::Deserializer<'de>,
125    {
126        Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
127    }
128}
129impl JsonSchema for EventOrigin {
130    fn inline_schema() -> bool {
131        true
132    }
133    fn schema_name() -> Cow<'static, str> {
134        Cow::Borrowed("EventOrigin")
135    }
136    fn json_schema(_: &mut SchemaGenerator) -> Schema {
137        json_schema!({
138            "type":"string",
139            "pattern":"^(user|(engine|plugin|client):[a-z0-9][a-z0-9-]{0,63})$",
140            "description":"Validated event author origin."
141        })
142    }
143}
144
145#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
146#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
147pub enum SessionOrigin {
148    Root,
149    Delegated {
150        root_session_id: SessionId,
151        parent_session_id: SessionId,
152        parent_run_id: RunId,
153        parent_tool_call_id: ToolCallId,
154        invocation_id: InvocationId,
155        depth: u32,
156    },
157}
158
159#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
160#[serde(rename_all = "snake_case")]
161pub enum SessionStatus {
162    Idle,
163    Running,
164    Completed,
165    Failed,
166    Cancelled,
167    Interrupted,
168}
169
170#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
171#[serde(deny_unknown_fields)]
172pub struct DelegationReservation {
173    pub invocation_id: InvocationId,
174    pub parent_session_id: SessionId,
175    pub parent_run_id: RunId,
176    pub parent_tool_call_id: ToolCallId,
177    pub child_session_id: SessionId,
178}
179
180#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
181#[serde(rename_all = "snake_case")]
182pub enum DelegatedContextRole {
183    User,
184    Assistant,
185}
186
187#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
188#[serde(deny_unknown_fields)]
189pub struct DelegatedContextTurn {
190    pub role: DelegatedContextRole,
191    pub text: String,
192}
193
194#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
195#[serde(rename_all = "snake_case")]
196pub enum StagedSkillProvenance {
197    SkillFork,
198}
199
200#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
201#[serde(deny_unknown_fields)]
202pub struct StagedSkillPayload {
203    pub provenance: StagedSkillProvenance,
204    pub name: String,
205    pub args: String,
206    pub rendered_body: String,
207    pub source_path: String,
208    pub base_dir: String,
209    #[schemars(length(max = 10))]
210    pub supporting_files: Vec<String>,
211    #[schemars(length(max = 256))]
212    pub grants: Vec<PermissionRule>,
213    #[serde(deserialize_with = "deserialize_required_option")]
214    #[schemars(with = "crate::NullableSchema<ModelKey>", required)]
215    pub model: Option<ModelKey>,
216}
217
218#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
219#[serde(deny_unknown_fields)]
220pub struct DelegateRequestPayload {
221    pub description: String,
222    pub prompt: String,
223    pub title: SessionTitle,
224    #[serde(deserialize_with = "deserialize_required_option")]
225    #[schemars(with = "crate::NullableSchema<SessionId>", required)]
226    pub resume_session_id: Option<SessionId>,
227    pub inherit_context: bool,
228    #[schemars(length(max = 65536))]
229    pub seeded_context: Vec<DelegatedContextTurn>,
230    pub background: bool,
231    #[serde(deserialize_with = "deserialize_required_option")]
232    #[schemars(with = "crate::NullableSchema<StagedSkillPayload>", required)]
233    pub staged_skill: Option<StagedSkillPayload>,
234}
235impl DelegateRequestPayload {
236    fn validate(&self) -> Result<(), EventSchemaError> {
237        if self.description.is_empty()
238            || self.prompt.is_empty()
239            || (self.resume_session_id.is_some() && self.inherit_context)
240            || (!self.inherit_context && !self.seeded_context.is_empty())
241            || self.seeded_context.len() > 65_536
242            || self.seeded_context.iter().any(|turn| turn.text.is_empty())
243            || self
244                .seeded_context
245                .iter()
246                .map(|turn| turn.text.len())
247                .sum::<usize>()
248                > 65_536
249        {
250            return Err(EventSchemaError::InvalidDelegationLifecycle);
251        }
252        if self.staged_skill.as_ref().is_some_and(|skill| {
253            skill.provenance != StagedSkillProvenance::SkillFork
254                || skill.name.is_empty()
255                || skill.rendered_body.is_empty()
256                || skill.source_path.is_empty()
257                || skill.base_dir.is_empty()
258                || skill.supporting_files.len() > 10
259                || skill.grants.len() > 256
260                || self.resume_session_id.is_some()
261        }) {
262            return Err(EventSchemaError::InvalidDelegationLifecycle);
263        }
264        Ok(())
265    }
266}
267
268#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
269pub struct SessionMeta {
270    pub session_id: SessionId,
271    pub origin: SessionOrigin,
272    /// Short, tree-unique model-facing handle, absent for pre-handle sessions.
273    #[serde(default, skip_serializing_if = "Option::is_none")]
274    pub short_id: Option<String>,
275    pub cwd_identity: CwdIdentity,
276    pub creation_selection: RunSelection,
277    pub runtime_revision: RuntimeRevision,
278    pub catalog_revision: CatalogRevision,
279    pub provider_state_revision: ProviderStateRevision,
280    pub model_revision: ModelRevision,
281    pub agent_revision: AgentRevision,
282    pub recipe_registry_revision: RecipeRegistryRevision,
283    pub manifest_revision: ModelSnapshotRevision,
284    #[serde(deserialize_with = "deserialize_required_option")]
285    #[schemars(with = "crate::NullableSchema<SessionTitle>", required)]
286    pub title: Option<SessionTitle>,
287    pub title_updated_seq: u64,
288    #[schemars(range(min = 1))]
289    pub last_event_seq: u64,
290    pub last_activity: Timestamp,
291    pub status: SessionStatus,
292    pub skipped_events: Vec<SkippedEvent>,
293}
294
295#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
296pub struct SkippedEvent {
297    pub seq: u64,
298    pub reason: String,
299}
300impl SessionMeta {
301    pub fn validate(&self) -> Result<(), EventSchemaError> {
302        if self.last_event_seq == 0 || self.title_updated_seq > self.last_event_seq {
303            return Err(EventSchemaError::InvalidSessionMetaSequence);
304        }
305        Ok(())
306    }
307}
308impl<'de> Deserialize<'de> for SessionMeta {
309    fn deserialize<D>(d: D) -> Result<Self, D::Error>
310    where
311        D: serde::Deserializer<'de>,
312    {
313        #[derive(Deserialize)]
314        struct Wire {
315            session_id: SessionId,
316            origin: SessionOrigin,
317            #[serde(default)]
318            short_id: Option<String>,
319            cwd_identity: CwdIdentity,
320            creation_selection: RunSelection,
321            runtime_revision: RuntimeRevision,
322            catalog_revision: CatalogRevision,
323            provider_state_revision: ProviderStateRevision,
324            model_revision: ModelRevision,
325            agent_revision: AgentRevision,
326            recipe_registry_revision: RecipeRegistryRevision,
327            manifest_revision: ModelSnapshotRevision,
328            #[serde(deserialize_with = "deserialize_required_option")]
329            title: Option<SessionTitle>,
330            title_updated_seq: u64,
331            last_event_seq: u64,
332            last_activity: Timestamp,
333            status: SessionStatus,
334            #[serde(default)]
335            skipped_events: Vec<SkippedEvent>,
336        }
337        let w = Wire::deserialize(d)?;
338        let value = Self {
339            session_id: w.session_id,
340            origin: w.origin,
341            short_id: w.short_id,
342            cwd_identity: w.cwd_identity,
343            creation_selection: w.creation_selection,
344            runtime_revision: w.runtime_revision,
345            catalog_revision: w.catalog_revision,
346            provider_state_revision: w.provider_state_revision,
347            model_revision: w.model_revision,
348            agent_revision: w.agent_revision,
349            recipe_registry_revision: w.recipe_registry_revision,
350            manifest_revision: w.manifest_revision,
351            title: w.title,
352            title_updated_seq: w.title_updated_seq,
353            last_event_seq: w.last_event_seq,
354            last_activity: w.last_activity,
355            status: w.status,
356            skipped_events: w.skipped_events,
357        };
358        value.validate().map_err(serde::de::Error::custom)?;
359        Ok(value)
360    }
361}
362
363#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
364#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
365pub enum SessionTitleChange {
366    UserSet {
367        title: SessionTitle,
368        client_rename_id: ClientRenameId,
369    },
370    UserClear {
371        client_rename_id: ClientRenameId,
372    },
373    UserReset {
374        client_rename_id: ClientRenameId,
375    },
376    InternalAgentSet {
377        title: SessionTitle,
378        invocation_id: InternalAgentInvocationId,
379    },
380    DelegatedSet {
381        title: SessionTitle,
382        invocation_id: InvocationId,
383    },
384    FallbackSet {
385        title: SessionTitle,
386    },
387}
388impl SessionTitleChange {
389    #[must_use]
390    pub fn user_rename_record(&self) -> Option<SessionRenameRecord> {
391        match self {
392            Self::UserSet {
393                title,
394                client_rename_id,
395            } => Some(SessionRenameRecord {
396                client_rename_id: client_rename_id.clone(),
397                change: SessionRenameChange::Set {
398                    title: title.clone(),
399                },
400            }),
401            Self::UserClear { client_rename_id } => Some(SessionRenameRecord {
402                client_rename_id: client_rename_id.clone(),
403                change: SessionRenameChange::Clear,
404            }),
405            Self::UserReset { client_rename_id } => Some(SessionRenameRecord {
406                client_rename_id: client_rename_id.clone(),
407                change: SessionRenameChange::Reset,
408            }),
409            Self::InternalAgentSet { .. }
410            | Self::DelegatedSet { .. }
411            | Self::FallbackSet { .. } => None,
412        }
413    }
414}
415
416#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
417#[serde(deny_unknown_fields)]
418pub struct ArtifactReference {
419    pub uri: String,
420}
421impl ArtifactReference {
422    pub const MAX_URI_BYTES: usize = 1024;
423    pub fn validate(&self) -> Result<(), EventSchemaError> {
424        if self.uri.is_empty()
425            || self.uri.len() > Self::MAX_URI_BYTES
426            || self.uri.chars().any(char::is_control)
427            || !self.uri.starts_with("artifact://")
428        {
429            Err(EventSchemaError::InvalidArtifactReference)
430        } else {
431            Ok(())
432        }
433    }
434}
435
436#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
437#[serde(deny_unknown_fields)]
438pub struct ToolAttachment {
439    pub mime_type: MimeType,
440    #[serde(deserialize_with = "crate::deserialize_required_option")]
441    #[schemars(with = "crate::NullableSchema<String>", required)]
442    pub filename: Option<String>,
443    pub byte_length: u64,
444    pub sha256: Sha256Digest,
445    pub reference: ArtifactReference,
446}
447impl ToolAttachment {
448    pub fn validate(&self) -> Result<(), EventSchemaError> {
449        self.reference.validate()?;
450        if self.filename.as_ref().is_some_and(|name| {
451            name.is_empty() || name.len() > 512 || name.chars().any(char::is_control)
452        }) {
453            return Err(EventSchemaError::InvalidAttachmentFilename);
454        }
455        Ok(())
456    }
457}
458
459#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
460#[serde(rename_all = "snake_case")]
461pub enum ToolEmittedMessageRole {
462    System,
463    User,
464}
465
466#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
467#[serde(rename_all = "snake_case", deny_unknown_fields)]
468pub enum ToolEmittedContent {
469    Text(String),
470    File(ToolAttachment),
471}
472
473#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
474#[serde(deny_unknown_fields)]
475pub struct ToolEmittedMessage {
476    pub role: ToolEmittedMessageRole,
477    #[schemars(length(min = 1))]
478    pub content: Vec<ToolEmittedContent>,
479}
480impl ToolEmittedMessage {
481    pub const MAX_TEXT_BYTES: usize = 64 * 1024;
482
483    pub fn new(
484        role: ToolEmittedMessageRole,
485        content: Vec<ToolEmittedContent>,
486    ) -> Result<Self, EventSchemaError> {
487        let value = Self { role, content };
488        value.validate()?;
489        Ok(value)
490    }
491
492    pub fn validate(&self) -> Result<(), EventSchemaError> {
493        if self.content.is_empty() {
494            return Err(EventSchemaError::EmptyToolEmittedMessage);
495        }
496        let mut text_bytes = 0_usize;
497        for part in &self.content {
498            match part {
499                ToolEmittedContent::Text(text) => {
500                    if text.trim().is_empty() {
501                        return Err(EventSchemaError::EmptyToolEmittedText);
502                    }
503                    text_bytes = text_bytes
504                        .checked_add(text.len())
505                        .ok_or(EventSchemaError::ToolEmittedTextTooLarge)?;
506                    if text_bytes > Self::MAX_TEXT_BYTES {
507                        return Err(EventSchemaError::ToolEmittedTextTooLarge);
508                    }
509                }
510                ToolEmittedContent::File(attachment) => attachment.validate()?,
511            }
512        }
513        Ok(())
514    }
515}
516impl<'de> Deserialize<'de> for ToolEmittedMessage {
517    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
518    where
519        D: serde::Deserializer<'de>,
520    {
521        #[derive(Deserialize)]
522        #[serde(deny_unknown_fields)]
523        struct Wire {
524            role: ToolEmittedMessageRole,
525            content: Vec<ToolEmittedContent>,
526        }
527
528        let wire = Wire::deserialize(deserializer)?;
529        Self::new(wire.role, wire.content).map_err(serde::de::Error::custom)
530    }
531}
532#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
533#[serde(deny_unknown_fields)]
534pub struct ToolOutputTruncation {
535    pub original_bytes: u64,
536    pub original_lines: u64,
537    pub retained: ArtifactReference,
538}
539
540#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
541#[serde(deny_unknown_fields)]
542pub struct PersistedToolResult {
543    pub title: SafeDisplayText,
544    pub output: String,
545    #[serde(default, skip_serializing_if = "Option::is_none")]
546    pub display: Option<String>,
547    #[serde(default, skip_serializing_if = "Option::is_none")]
548    pub retained_output: Option<crate::RetainedToolOutput>,
549    pub metadata: Value,
550    #[serde(deserialize_with = "crate::deserialize_required_option")]
551    #[schemars(with = "crate::NullableSchema<ToolOutputTruncation>", required)]
552    pub truncation: Option<ToolOutputTruncation>,
553    #[schemars(
554        description = "Direct result files. Together with files in additional_messages, at most 256 are allowed."
555    )]
556    #[schemars(length(max = 256))]
557    pub attachments: Vec<ToolAttachment>,
558    #[schemars(
559        description = "Emitted model-history messages. Their files share the 256-file limit with attachments."
560    )]
561    #[serde(default, skip_serializing_if = "Vec::is_empty")]
562    #[schemars(length(max = 4))]
563    pub additional_messages: Vec<ToolEmittedMessage>,
564}
565impl PersistedToolResult {
566    pub const MAX_OUTPUT_BYTES: usize = 2 * 1024 * 1024;
567    pub const MAX_METADATA_BYTES: usize = 2 * 1024 * 1024;
568    pub const MAX_ADDITIONAL_MESSAGES: usize = 4;
569    pub const MAX_ATTACHMENTS: usize = 256;
570
571    pub fn all_attachments(&self) -> impl Iterator<Item = &ToolAttachment> {
572        self.attachments.iter().chain(
573            self.additional_messages
574                .iter()
575                .flat_map(|message| message.content.iter())
576                .filter_map(|part| match part {
577                    ToolEmittedContent::Text(_) => None,
578                    ToolEmittedContent::File(attachment) => Some(attachment),
579                }),
580        )
581    }
582
583    pub fn validate(&self) -> Result<(), EventSchemaError> {
584        if self.output.len() > Self::MAX_OUTPUT_BYTES {
585            return Err(EventSchemaError::ToolOutputTooLarge);
586        }
587        if self.display.as_ref().is_some_and(|display| {
588            !crate::tool_output::validate_display(display, crate::MAX_TOOL_DISPLAY_BYTES)
589        }) {
590            return Err(EventSchemaError::ToolOutputTooLarge);
591        }
592        if let Some(retained) = &self.retained_output {
593            retained.reference.validate()?;
594            if retained.streams.is_empty() || retained.streams.len() > crate::MAX_TOOL_STREAMS {
595                return Err(EventSchemaError::ToolOutputTooLarge);
596            }
597            for stream in &retained.streams {
598                stream.reference.validate()?;
599                if let Some(name) = &stream.name {
600                    crate::validate_tool_stream_name(name)
601                        .map_err(|_| EventSchemaError::InvalidArtifactReference)?;
602                }
603            }
604        }
605        if serde_json::to_vec(&self.metadata)
606            .map_err(|_| EventSchemaError::InvalidJson)?
607            .len()
608            > Self::MAX_METADATA_BYTES
609        {
610            return Err(EventSchemaError::ToolMetadataTooLarge);
611        }
612        let emitted_attachment_count = self
613            .additional_messages
614            .iter()
615            .flat_map(|message| &message.content)
616            .filter(|part| matches!(part, ToolEmittedContent::File(_)))
617            .count();
618        if self
619            .attachments
620            .len()
621            .saturating_add(emitted_attachment_count)
622            > Self::MAX_ATTACHMENTS
623        {
624            return Err(EventSchemaError::TooManyAttachments);
625        }
626        if self.additional_messages.len() > Self::MAX_ADDITIONAL_MESSAGES {
627            return Err(EventSchemaError::TooManyToolEmittedMessages);
628        }
629        for message in &self.additional_messages {
630            message.validate()?;
631        }
632        for attachment in self.all_attachments() {
633            attachment.validate()?;
634        }
635        if let Some(truncation) = &self.truncation {
636            truncation.retained.validate()?;
637        }
638        Ok(())
639    }
640}
641impl<'de> Deserialize<'de> for PersistedToolResult {
642    fn deserialize<D>(d: D) -> Result<Self, D::Error>
643    where
644        D: serde::Deserializer<'de>,
645    {
646        #[derive(Deserialize)]
647        #[serde(deny_unknown_fields)]
648        struct Wire {
649            title: SafeDisplayText,
650            output: String,
651            #[serde(default)]
652            display: Option<String>,
653            #[serde(default)]
654            retained_output: Option<crate::RetainedToolOutput>,
655            metadata: Value,
656            #[serde(deserialize_with = "crate::deserialize_required_option")]
657            truncation: Option<ToolOutputTruncation>,
658            attachments: Vec<ToolAttachment>,
659            #[serde(default)]
660            additional_messages: Vec<ToolEmittedMessage>,
661        }
662        let w = Wire::deserialize(d)?;
663        let value = Self {
664            title: w.title,
665            output: w.output,
666            display: w.display,
667            retained_output: w.retained_output,
668            metadata: w.metadata,
669            truncation: w.truncation,
670            attachments: w.attachments,
671            additional_messages: w.additional_messages,
672        };
673        value.validate().map_err(serde::de::Error::custom)?;
674        Ok(value)
675    }
676}
677
678#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
679#[serde(deny_unknown_fields)]
680pub struct SafeToolError {
681    pub code: SafeCode,
682    pub message: SafeErrorMessage,
683}
684
685#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
686#[serde(deny_unknown_fields)]
687pub struct AssistantToolCallRef {
688    #[schemars(range(min = 1))]
689    pub model_turn_seq: u64,
690    pub content_index: u32,
691    pub model_call_id: ModelCallId,
692    #[serde(deserialize_with = "deserialize_required_option")]
693    #[schemars(with = "crate::NullableSchema<ProviderItemId>", required)]
694    pub provider_item_id: Option<ProviderItemId>,
695}
696
697#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
698#[serde(deny_unknown_fields)]
699pub struct ToolCallPresentation {
700    pub title: SafeDisplayText,
701    #[serde(deserialize_with = "deserialize_required_option")]
702    #[schemars(with = "crate::NullableSchema<BoundedDisplayText>", required)]
703    pub primary_argument: Option<BoundedDisplayText>,
704}
705
706#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
707#[serde(deny_unknown_fields)]
708pub struct ToolCallStart {
709    pub tool_call_id: ToolCallId,
710    #[serde(default)]
711    pub output: crate::ToolOutputDeclaration,
712    pub owner: AssistantToolCallRef,
713    pub presentation: ToolCallPresentation,
714    pub operation_fingerprint: OperationFingerprint,
715}
716
717#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
718#[serde(rename_all = "snake_case")]
719pub enum ToolTerminationOutcome {
720    Completed,
721    Failed,
722    Cancelled,
723    Interrupted,
724}
725
726#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
727#[serde(deny_unknown_fields)]
728pub struct ToolCallTermination {
729    pub tool_call_id: ToolCallId,
730    pub owner: AssistantToolCallRef,
731    pub outcome: ToolTerminationOutcome,
732    #[serde(deserialize_with = "crate::deserialize_required_option")]
733    #[schemars(with = "crate::NullableSchema<PersistedToolResult>", required)]
734    pub result: Option<PersistedToolResult>,
735    #[serde(deserialize_with = "crate::deserialize_required_option")]
736    #[schemars(with = "crate::NullableSchema<SafeToolError>", required)]
737    pub error: Option<SafeToolError>,
738}
739impl ToolCallTermination {
740    pub fn validate(&self) -> Result<(), EventSchemaError> {
741        if self.owner.model_turn_seq == 0 {
742            return Err(EventSchemaError::ZeroModelTurnSequence);
743        }
744        match self.outcome {
745            ToolTerminationOutcome::Completed if self.result.is_some() && self.error.is_none() => {}
746            ToolTerminationOutcome::Failed
747            | ToolTerminationOutcome::Cancelled
748            | ToolTerminationOutcome::Interrupted
749                if self.error.is_some() => {}
750            _ => return Err(EventSchemaError::InvalidToolTermination),
751        }
752        if let Some(result) = &self.result {
753            result.validate()?;
754        }
755        Ok(())
756    }
757    #[must_use]
758    pub fn matches_start(&self, start: &ToolCallStart) -> bool {
759        self.tool_call_id == start.tool_call_id && self.owner == start.owner
760    }
761}
762impl<'de> Deserialize<'de> for ToolCallTermination {
763    fn deserialize<D>(d: D) -> Result<Self, D::Error>
764    where
765        D: serde::Deserializer<'de>,
766    {
767        #[derive(Deserialize)]
768        #[serde(deny_unknown_fields)]
769        struct Wire {
770            tool_call_id: ToolCallId,
771            owner: AssistantToolCallRef,
772            outcome: ToolTerminationOutcome,
773            #[serde(deserialize_with = "crate::deserialize_required_option")]
774            result: Option<PersistedToolResult>,
775            #[serde(deserialize_with = "crate::deserialize_required_option")]
776            error: Option<SafeToolError>,
777        }
778        let w = Wire::deserialize(d)?;
779        let value = Self {
780            tool_call_id: w.tool_call_id,
781            owner: w.owner,
782            outcome: w.outcome,
783            result: w.result,
784            error: w.error,
785        };
786        value.validate().map_err(serde::de::Error::custom)?;
787        Ok(value)
788    }
789}
790
791#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
792#[serde(deny_unknown_fields)]
793pub struct Usage {
794    #[serde(deserialize_with = "crate::deserialize_required_option")]
795    #[schemars(with = "crate::NullableSchema<u64>", required)]
796    pub input_tokens: Option<u64>,
797    #[serde(deserialize_with = "crate::deserialize_required_option")]
798    #[schemars(with = "crate::NullableSchema<u64>", required)]
799    pub input_tokens_no_cache: Option<u64>,
800    #[serde(deserialize_with = "crate::deserialize_required_option")]
801    #[schemars(with = "crate::NullableSchema<u64>", required)]
802    pub input_tokens_cache_read: Option<u64>,
803    #[serde(deserialize_with = "crate::deserialize_required_option")]
804    #[schemars(with = "crate::NullableSchema<u64>", required)]
805    pub input_tokens_cache_write: Option<u64>,
806    #[serde(deserialize_with = "crate::deserialize_required_option")]
807    #[schemars(with = "crate::NullableSchema<u64>", required)]
808    pub output_tokens: Option<u64>,
809    #[serde(deserialize_with = "crate::deserialize_required_option")]
810    #[schemars(with = "crate::NullableSchema<u64>", required)]
811    pub output_tokens_text: Option<u64>,
812    #[serde(deserialize_with = "crate::deserialize_required_option")]
813    #[schemars(with = "crate::NullableSchema<u64>", required)]
814    pub output_tokens_reasoning: Option<u64>,
815}
816
817pub type SafeModelMetadata = BTreeMap<String, Value>;
818
819#[derive(Clone, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
820#[serde(deny_unknown_fields)]
821pub struct NativeContextScope {
822    pub provider_id: ProviderId,
823    pub model_id: ProviderModelId,
824    pub resource_id: SafeDisplayText,
825}
826
827#[derive(Clone, Debug, PartialEq)]
828pub enum NativeArtifactError {
829    PayloadTooLarge { size: usize, maximum: usize },
830    InvalidScope,
831    Serialization,
832}
833impl fmt::Display for NativeArtifactError {
834    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835        match self {
836            Self::PayloadTooLarge { size, maximum } => write!(
837                f,
838                "native payload is {size} bytes, exceeding the {maximum}-byte limit"
839            ),
840            Self::InvalidScope => f.write_str("native artifact scope does not match its selection"),
841            Self::Serialization => f.write_str("native payload could not be serialized"),
842        }
843    }
844}
845impl std::error::Error for NativeArtifactError {}
846
847#[derive(Clone, JsonSchema, PartialEq, Serialize)]
848#[serde(deny_unknown_fields)]
849pub struct NativeReplayArtifact {
850    adapter_id: SafeCode,
851    selection_fingerprint: Sha256Digest,
852    scope: NativeContextScope,
853    #[schemars(extend("x-max-serialized-bytes"=2_097_152))]
854    payload: Value,
855}
856impl NativeReplayArtifact {
857    pub const MAX_PAYLOAD_BYTES: usize = 2 * 1024 * 1024;
858    pub fn new(
859        adapter_id: SafeCode,
860        selection_fingerprint: Sha256Digest,
861        scope: NativeContextScope,
862        payload: Value,
863    ) -> Result<Self, NativeArtifactError> {
864        validate_payload(&payload, Self::MAX_PAYLOAD_BYTES)?;
865        Ok(Self {
866            adapter_id,
867            selection_fingerprint,
868            scope,
869            payload,
870        })
871    }
872    #[must_use]
873    pub const fn selection_fingerprint(&self) -> &Sha256Digest {
874        &self.selection_fingerprint
875    }
876    #[must_use]
877    pub const fn adapter_id(&self) -> &SafeCode {
878        &self.adapter_id
879    }
880    #[must_use]
881    pub const fn scope(&self) -> &NativeContextScope {
882        &self.scope
883    }
884    #[must_use]
885    pub const fn payload(&self) -> &Value {
886        &self.payload
887    }
888
889    pub fn validate_for(
890        &self,
891        resolved: &ResolvedModelRef,
892        expected_scope: &NativeContextScope,
893    ) -> Result<(), NativeArtifactError> {
894        validate_native_identity(
895            &self.adapter_id,
896            &self.selection_fingerprint,
897            &self.scope,
898            resolved,
899            expected_scope,
900        )
901    }
902
903    pub fn validate_resolved(
904        &self,
905        resolved: &ResolvedModelRef,
906    ) -> Result<(), NativeArtifactError> {
907        self.validate_for(resolved, &self.scope)
908    }
909
910    pub fn validate_for_binding(
911        &self,
912        binding: &FrozenModelBinding,
913        expected_scope: &NativeContextScope,
914    ) -> Result<(), NativeArtifactError> {
915        binding
916            .validate()
917            .map_err(|_| NativeArtifactError::InvalidScope)?;
918        if self.adapter_id.as_str() != binding.descriptor.adapter_id.as_str()
919            || self.selection_fingerprint != binding.blueprint_fingerprint
920            || self.scope != *expected_scope
921            || self.scope.provider_id != binding.selection.model.provider_id()
922            || self.scope.model_id != binding.selection.model.model_id()
923        {
924            return Err(NativeArtifactError::InvalidScope);
925        }
926        Ok(())
927    }
928}
929impl fmt::Debug for NativeReplayArtifact {
930    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931        f.debug_struct("NativeReplayArtifact")
932            .field("adapter_id", &self.adapter_id)
933            .field("selection_fingerprint", &self.selection_fingerprint)
934            .field("scope", &self.scope)
935            .field("payload", &"<redacted>")
936            .finish()
937    }
938}
939impl<'de> Deserialize<'de> for NativeReplayArtifact {
940    fn deserialize<D>(d: D) -> Result<Self, D::Error>
941    where
942        D: serde::Deserializer<'de>,
943    {
944        #[derive(Deserialize)]
945        #[serde(deny_unknown_fields)]
946        struct Wire {
947            adapter_id: SafeCode,
948            selection_fingerprint: Sha256Digest,
949            scope: NativeContextScope,
950            payload: Value,
951        }
952        let w = Wire::deserialize(d)?;
953        Self::new(w.adapter_id, w.selection_fingerprint, w.scope, w.payload)
954            .map_err(serde::de::Error::custom)
955    }
956}
957
958#[derive(Clone, Eq, JsonSchema, PartialEq, Serialize)]
959#[serde(deny_unknown_fields)]
960pub struct NativeContextWindow {
961    adapter_id: SafeCode,
962    selection_fingerprint: Sha256Digest,
963    scope: NativeContextScope,
964    #[schemars(extend("x-max-serialized-bytes"=33_554_432))]
965    payload: Value,
966}
967impl NativeContextWindow {
968    pub const MAX_PAYLOAD_BYTES: usize = 32 * 1024 * 1024;
969    pub fn new(
970        adapter_id: SafeCode,
971        selection_fingerprint: Sha256Digest,
972        scope: NativeContextScope,
973        payload: Value,
974    ) -> Result<Self, NativeArtifactError> {
975        validate_payload(&payload, Self::MAX_PAYLOAD_BYTES)?;
976        Ok(Self {
977            adapter_id,
978            selection_fingerprint,
979            scope,
980            payload,
981        })
982    }
983    #[must_use]
984    pub const fn selection_fingerprint(&self) -> &Sha256Digest {
985        &self.selection_fingerprint
986    }
987    #[must_use]
988    pub const fn adapter_id(&self) -> &SafeCode {
989        &self.adapter_id
990    }
991    #[must_use]
992    pub const fn scope(&self) -> &NativeContextScope {
993        &self.scope
994    }
995    #[must_use]
996    pub const fn payload(&self) -> &Value {
997        &self.payload
998    }
999
1000    pub fn validate_for(
1001        &self,
1002        resolved: &ResolvedModelRef,
1003        expected_scope: &NativeContextScope,
1004    ) -> Result<(), NativeArtifactError> {
1005        validate_native_identity(
1006            &self.adapter_id,
1007            &self.selection_fingerprint,
1008            &self.scope,
1009            resolved,
1010            expected_scope,
1011        )
1012    }
1013
1014    pub fn validate_for_binding(
1015        &self,
1016        binding: &FrozenModelBinding,
1017        expected_scope: &NativeContextScope,
1018    ) -> Result<(), NativeArtifactError> {
1019        binding
1020            .validate()
1021            .map_err(|_| NativeArtifactError::InvalidScope)?;
1022        if self.adapter_id.as_str() != binding.descriptor.adapter_id.as_str()
1023            || self.selection_fingerprint != binding.blueprint_fingerprint
1024            || self.scope != *expected_scope
1025            || self.scope.provider_id != binding.selection.model.provider_id()
1026            || self.scope.model_id != binding.selection.model.model_id()
1027        {
1028            return Err(NativeArtifactError::InvalidScope);
1029        }
1030        Ok(())
1031    }
1032}
1033impl fmt::Debug for NativeContextWindow {
1034    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1035        f.debug_struct("NativeContextWindow")
1036            .field("adapter_id", &self.adapter_id)
1037            .field("selection_fingerprint", &self.selection_fingerprint)
1038            .field("scope", &self.scope)
1039            .field("payload", &"<redacted>")
1040            .finish()
1041    }
1042}
1043impl<'de> Deserialize<'de> for NativeContextWindow {
1044    fn deserialize<D>(d: D) -> Result<Self, D::Error>
1045    where
1046        D: serde::Deserializer<'de>,
1047    {
1048        #[derive(Deserialize)]
1049        #[serde(deny_unknown_fields)]
1050        struct Wire {
1051            adapter_id: SafeCode,
1052            selection_fingerprint: Sha256Digest,
1053            scope: NativeContextScope,
1054            payload: Value,
1055        }
1056        let w = Wire::deserialize(d)?;
1057        Self::new(w.adapter_id, w.selection_fingerprint, w.scope, w.payload)
1058            .map_err(serde::de::Error::custom)
1059    }
1060}
1061fn validate_payload(payload: &Value, maximum: usize) -> Result<(), NativeArtifactError> {
1062    let size = serde_json::to_vec(payload)
1063        .map_err(|_| NativeArtifactError::Serialization)?
1064        .len();
1065    if size > maximum {
1066        Err(NativeArtifactError::PayloadTooLarge { size, maximum })
1067    } else {
1068        Ok(())
1069    }
1070}
1071
1072fn validate_native_identity(
1073    _adapter_id: &SafeCode,
1074    selection_fingerprint: &Sha256Digest,
1075    scope: &NativeContextScope,
1076    resolved: &ResolvedModelRef,
1077    expected_scope: &NativeContextScope,
1078) -> Result<(), NativeArtifactError> {
1079    resolved
1080        .validate()
1081        .map_err(|_| NativeArtifactError::InvalidScope)?;
1082    if selection_fingerprint != &resolved.selection_fingerprint
1083        || scope != expected_scope
1084        || scope.provider_id != resolved.provider_id
1085        || scope.model_id != resolved.model_id
1086    {
1087        return Err(NativeArtifactError::InvalidScope);
1088    }
1089    Ok(())
1090}
1091
1092#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1093#[serde(rename_all = "snake_case")]
1094pub enum ModelFinishReason {
1095    Stop,
1096    ToolCalls,
1097    Length,
1098    ContentFilter,
1099    Cancelled,
1100    Error,
1101    Aborted,
1102    Timeout,
1103    Refused,
1104    Unknown,
1105    Other(String),
1106}
1107#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1108#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1109pub enum PersistedFileSource {
1110    Artifact {
1111        byte_length: u64,
1112        sha256: Sha256Digest,
1113        reference: ArtifactReference,
1114    },
1115    Url {
1116        url: String,
1117    },
1118    ProviderReference {
1119        provider_id: ProviderId,
1120        id: SafeDisplayText,
1121    },
1122}
1123#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1124#[serde(deny_unknown_fields)]
1125pub struct PersistedFilePart {
1126    pub media_type: MimeType,
1127    #[serde(deserialize_with = "crate::deserialize_required_option")]
1128    #[schemars(with = "crate::NullableSchema<String>", required)]
1129    pub filename: Option<String>,
1130    pub source: PersistedFileSource,
1131    #[serde(deserialize_with = "crate::deserialize_required_option")]
1132    #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1133    pub metadata: Option<SafeModelMetadata>,
1134}
1135#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1136#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1137pub enum PersistedContentValue {
1138    Text { text: String },
1139    File { file: PersistedFilePart },
1140    Json { value: Value },
1141}
1142#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1143#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1144pub enum PersistedToolContent {
1145    Text {
1146        text: String,
1147    },
1148    Json {
1149        value: Value,
1150    },
1151    Mixed {
1152        values: Vec<PersistedContentValue>,
1153    },
1154    Denied {
1155        #[serde(deserialize_with = "deserialize_required_option")]
1156        #[schemars(with = "crate::NullableSchema<String>", required)]
1157        reason: Option<String>,
1158    },
1159}
1160#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1161#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1162pub enum PersistedAssistantPart {
1163    Text {
1164        text: String,
1165        #[serde(deserialize_with = "deserialize_required_option")]
1166        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1167        metadata: Option<SafeModelMetadata>,
1168    },
1169    Reasoning {
1170        text: String,
1171        #[serde(deserialize_with = "deserialize_required_option")]
1172        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1173        metadata: Option<SafeModelMetadata>,
1174    },
1175    ToolCall {
1176        id: ModelCallId,
1177        #[serde(deserialize_with = "deserialize_required_option")]
1178        #[schemars(with = "crate::NullableSchema<ProviderItemId>", required)]
1179        provider_item_id: Option<ProviderItemId>,
1180        name: SafeCode,
1181        input: Value,
1182        #[serde(deserialize_with = "deserialize_required_option")]
1183        #[schemars(with = "crate::NullableSchema<String>", required)]
1184        raw_input: Option<String>,
1185        #[serde(deserialize_with = "deserialize_required_option")]
1186        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1187        metadata: Option<SafeModelMetadata>,
1188    },
1189    ToolResult {
1190        tool_call_id: ModelCallId,
1191        content: PersistedToolContent,
1192        is_error: bool,
1193        #[serde(deserialize_with = "deserialize_required_option")]
1194        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1195        metadata: Option<SafeModelMetadata>,
1196    },
1197    File {
1198        file: PersistedFilePart,
1199    },
1200    Source {
1201        #[serde(deserialize_with = "deserialize_required_option")]
1202        #[schemars(with = "crate::NullableSchema<String>", required)]
1203        id: Option<String>,
1204        #[serde(deserialize_with = "deserialize_required_option")]
1205        #[schemars(with = "crate::NullableSchema<String>", required)]
1206        url: Option<String>,
1207        #[serde(deserialize_with = "deserialize_required_option")]
1208        #[schemars(with = "crate::NullableSchema<String>", required)]
1209        title: Option<String>,
1210        #[serde(deserialize_with = "deserialize_required_option")]
1211        #[schemars(with = "crate::NullableSchema<String>", required)]
1212        media_type: Option<String>,
1213        #[serde(deserialize_with = "deserialize_required_option")]
1214        #[schemars(with = "crate::NullableSchema<String>", required)]
1215        excerpt: Option<String>,
1216        #[serde(deserialize_with = "deserialize_required_option")]
1217        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1218        metadata: Option<SafeModelMetadata>,
1219    },
1220    ToolApproval {
1221        tool_call_id: ModelCallId,
1222        #[serde(deserialize_with = "deserialize_required_option")]
1223        #[schemars(with = "crate::NullableSchema<String>", required)]
1224        message: Option<String>,
1225        #[serde(deserialize_with = "deserialize_required_option")]
1226        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1227        metadata: Option<SafeModelMetadata>,
1228    },
1229    Custom {
1230        kind: SafeCode,
1231        data: Value,
1232        #[serde(deserialize_with = "deserialize_required_option")]
1233        #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1234        metadata: Option<SafeModelMetadata>,
1235    },
1236}
1237
1238#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
1239#[serde(deny_unknown_fields)]
1240pub struct PersistedModelTurn {
1241    #[schemars(length(max = 4096))]
1242    pub content: Vec<PersistedAssistantPart>,
1243    pub provider_options: BTreeMap<String, Value>,
1244    pub finish_reason: ModelFinishReason,
1245    pub usage: Usage,
1246    pub response_metadata: SafeModelMetadata,
1247    pub provider_metadata: SafeModelMetadata,
1248    #[serde(deserialize_with = "crate::deserialize_required_option")]
1249    #[schemars(with = "crate::NullableSchema<NativeReplayArtifact>", required)]
1250    pub native_replay: Option<NativeReplayArtifact>,
1251}
1252impl PersistedModelTurn {
1253    pub const MAX_SERIALIZED_BYTES: usize = 8 * 1024 * 1024;
1254    pub fn validate(&self) -> Result<(), EventSchemaError> {
1255        if self.content.len() > 4096 {
1256            return Err(EventSchemaError::TooManyModelParts);
1257        }
1258        if serde_json::to_vec(self)
1259            .map_err(|_| EventSchemaError::InvalidJson)?
1260            .len()
1261            > Self::MAX_SERIALIZED_BYTES
1262        {
1263            return Err(EventSchemaError::ModelTurnTooLarge);
1264        }
1265        Ok(())
1266    }
1267
1268    pub fn validate_for(&self, resolved: &ResolvedModelRef) -> Result<(), EventSchemaError> {
1269        self.validate()?;
1270        if let Some(native_replay) = &self.native_replay {
1271            native_replay
1272                .validate_resolved(resolved)
1273                .map_err(|_| EventSchemaError::NativeSelectionMismatch)?;
1274        }
1275        Ok(())
1276    }
1277}
1278impl<'de> Deserialize<'de> for PersistedModelTurn {
1279    fn deserialize<D>(d: D) -> Result<Self, D::Error>
1280    where
1281        D: serde::Deserializer<'de>,
1282    {
1283        #[derive(Deserialize)]
1284        #[serde(deny_unknown_fields)]
1285        struct Wire {
1286            content: Vec<PersistedAssistantPart>,
1287            provider_options: BTreeMap<String, Value>,
1288            finish_reason: ModelFinishReason,
1289            usage: Usage,
1290            response_metadata: SafeModelMetadata,
1291            provider_metadata: SafeModelMetadata,
1292            #[serde(deserialize_with = "crate::deserialize_required_option")]
1293            native_replay: Option<NativeReplayArtifact>,
1294        }
1295        let w = Wire::deserialize(d)?;
1296        let value = Self {
1297            content: w.content,
1298            provider_options: w.provider_options,
1299            finish_reason: w.finish_reason,
1300            usage: w.usage,
1301            response_metadata: w.response_metadata,
1302            provider_metadata: w.provider_metadata,
1303            native_replay: w.native_replay,
1304        };
1305        value.validate().map_err(serde::de::Error::custom)?;
1306        Ok(value)
1307    }
1308}
1309
1310#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1311#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1312pub enum ReplayDisposition {
1313    Replayed,
1314    NoArtifact,
1315    DiscardedForeignAdapter {
1316        found: SafeCode,
1317        expected: SafeCode,
1318    },
1319    DiscardedForeignModelSelection {
1320        #[serde(deserialize_with = "crate::deserialize_required_model_selection")]
1321        #[schemars(with = "crate::RequiredModelSelectionSchema")]
1322        found: ModelSelection,
1323        #[serde(deserialize_with = "crate::deserialize_required_model_selection")]
1324        #[schemars(with = "crate::RequiredModelSelectionSchema")]
1325        expected: ModelSelection,
1326    },
1327    DiscardedForeignVariant {
1328        #[serde(deserialize_with = "deserialize_required_option")]
1329        #[schemars(with = "crate::NullableSchema<VariantId>", required)]
1330        found: Option<VariantId>,
1331        #[serde(deserialize_with = "deserialize_required_option")]
1332        #[schemars(with = "crate::NullableSchema<VariantId>", required)]
1333        expected: Option<VariantId>,
1334    },
1335    DiscardedInvalidPayload {
1336        reason: SafeErrorMessage,
1337    },
1338    ReconstructedNormalizedHistory,
1339}
1340#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1341#[serde(deny_unknown_fields)]
1342pub struct ReplayDecision {
1343    pub history_index: u64,
1344    pub disposition: ReplayDisposition,
1345}
1346
1347#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1348#[serde(rename_all = "snake_case")]
1349pub enum ModelErrorStage {
1350    #[default]
1351    Unknown,
1352    RequestValidation,
1353    RequestEncoding,
1354    Connect,
1355    ResponseHeaders,
1356    ResponseBody,
1357    StreamRead,
1358    StreamDecode,
1359    StreamEvent,
1360    StreamFinalize,
1361    ReplayEncode,
1362    ReplayDecode,
1363    NativeContextEncode,
1364    NativeContextDecode,
1365    Middleware,
1366}
1367#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
1368#[serde(rename_all = "snake_case")]
1369pub enum ModelErrorKind {
1370    Transport,
1371    Timeout,
1372    RateLimited,
1373    Auth,
1374    PermissionDenied,
1375    InvalidRequest,
1376    ModelNotFound,
1377    ContextLength,
1378    Quota,
1379    Overload,
1380    Unsupported,
1381    UnexpectedEof,
1382    InvalidResponse,
1383    InvalidToolInput,
1384    ContentFilter,
1385    Replay,
1386    NativeContext,
1387    Provider,
1388    Abort,
1389    Unknown,
1390}
1391#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1392#[serde(deny_unknown_fields)]
1393pub struct ModelErrorSummary {
1394    #[serde(default, skip_serializing_if = "Option::is_none")]
1395    pub response_body: Option<DiagnosticText>,
1396    pub kind: ModelErrorKind,
1397    pub message: SafeErrorMessage,
1398    pub retryable: bool,
1399    pub stage: ModelErrorStage,
1400    #[serde(deserialize_with = "crate::deserialize_required_option")]
1401    #[schemars(with = "crate::NullableSchema<u16>", required)]
1402    pub http_status: Option<u16>,
1403    pub bytes_received: u64,
1404    #[serde(deserialize_with = "crate::deserialize_required_option")]
1405    #[schemars(with = "crate::NullableSchema<SafeCode>", required)]
1406    pub vendor_code: Option<SafeCode>,
1407    #[serde(deserialize_with = "crate::deserialize_required_option")]
1408    #[schemars(with = "crate::NullableSchema<SafeDisplayText>", required)]
1409    pub request_id: Option<SafeDisplayText>,
1410    #[serde(deserialize_with = "crate::deserialize_required_option")]
1411    #[schemars(with = "crate::NullableSchema<u64>", required)]
1412    pub retry_after_ms: Option<u64>,
1413}
1414
1415#[derive(
1416    Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize,
1417)]
1418#[serde(rename_all = "snake_case")]
1419pub enum InternalAgentKind {
1420    Approval,
1421    ContextCompaction,
1422    SessionTitle,
1423}
1424
1425#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1426#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1427pub enum FrozenInternalAgentFallback {
1428    ParentModel,
1429    Model { binding: Box<FrozenModelBinding> },
1430}
1431
1432#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1433#[serde(deny_unknown_fields)]
1434pub struct FrozenInternalAgentDefinition {
1435    pub kind: InternalAgentKind,
1436    pub agent: AgentId,
1437    #[schemars(length(min = 1, max = 512))]
1438    pub description: String,
1439    pub document_source: AgentDocumentSource,
1440    pub document_fingerprint: Sha256Digest,
1441    #[schemars(length(min = 1, max = 131_072))]
1442    pub composed_prompt: String,
1443    pub prompt_fingerprint: Sha256Digest,
1444    pub enabled: bool,
1445    pub max_output_tokens: u64,
1446    pub timeout_ms: u64,
1447    #[schemars(length(max = 256))]
1448    pub fallbacks: Vec<FrozenInternalAgentFallback>,
1449}
1450
1451impl FrozenInternalAgentDefinition {
1452    pub fn validate(&self) -> Result<(), &'static str> {
1453        if self.description.trim().is_empty()
1454            || self.description.len() > 512
1455            || self.description.chars().any(char::is_control)
1456            || self.composed_prompt.trim().is_empty()
1457            || self.composed_prompt.len() > AgentSnapshot::MAX_PROMPT_BYTES
1458            || self.fallbacks.len() > 256
1459        {
1460            return Err("invalid frozen internal agent definition");
1461        }
1462        for fallback in &self.fallbacks {
1463            if let FrozenInternalAgentFallback::Model { binding } = fallback {
1464                binding
1465                    .validate()
1466                    .map_err(|_| "invalid frozen internal agent binding")?;
1467            }
1468        }
1469        Ok(())
1470    }
1471}
1472#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1473#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1474pub enum InternalAgentBackend {
1475    Model {
1476        resolved_model: ResolvedModelRef,
1477    },
1478    Builtin {
1479        name: SafeCode,
1480        revision: SafeDisplayText,
1481    },
1482}
1483#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1484#[serde(deny_unknown_fields)]
1485pub struct SafeInternalAgentCall {
1486    pub name: SafeCode,
1487    pub input_summary: SafeDisplayText,
1488    pub input_digest: Sha256Digest,
1489}
1490#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1491#[serde(deny_unknown_fields)]
1492pub struct SafeInternalAgentResult {
1493    pub output_summary: SafeDisplayText,
1494    pub output_digest: Sha256Digest,
1495}
1496#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1497#[serde(deny_unknown_fields)]
1498pub struct InternalAgentFailure {
1499    pub code: SafeCode,
1500    pub message: SafeErrorMessage,
1501    pub retryable: bool,
1502    #[serde(deserialize_with = "crate::deserialize_required_option")]
1503    #[schemars(with = "crate::NullableSchema<ModelErrorSummary>", required)]
1504    pub model_error: Option<ModelErrorSummary>,
1505}
1506
1507fn validate_internal_backend(backend: &InternalAgentBackend) -> Result<(), EventSchemaError> {
1508    match backend {
1509        InternalAgentBackend::Model { resolved_model } => resolved_model
1510            .validate()
1511            .map_err(|_| EventSchemaError::InvalidInternalAgentLifecycle),
1512        InternalAgentBackend::Builtin { .. } => Ok(()),
1513    }
1514}
1515
1516fn validate_internal_failure(failure: &InternalAgentFailure) -> Result<(), EventSchemaError> {
1517    if failure
1518        .model_error
1519        .as_ref()
1520        .is_some_and(|error| error.retryable != failure.retryable)
1521    {
1522        Err(EventSchemaError::InvalidInternalAgentLifecycle)
1523    } else {
1524        Ok(())
1525    }
1526}
1527
1528#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1529#[serde(deny_unknown_fields)]
1530pub struct ContextCheckpointBoundaries {
1531    #[schemars(range(min = 1))]
1532    pub source_from_seq: u64,
1533    #[schemars(range(min = 1))]
1534    pub source_through_seq: u64,
1535    #[serde(default)]
1536    pub recent_from_seq: Option<u64>,
1537    pub input_through_seq: u64,
1538    #[serde(deserialize_with = "crate::deserialize_required_option")]
1539    #[schemars(with = "crate::NullableSchema<u64>", required)]
1540    pub prior_checkpoint_seq: Option<u64>,
1541}
1542#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1543pub struct SummaryByteLimit(u64);
1544impl SummaryByteLimit {
1545    pub const GLOBAL_MAX_BYTES: u64 = 2 * 1024 * 1024;
1546    pub fn new(value: u64) -> Result<Self, EventSchemaError> {
1547        if value > Self::GLOBAL_MAX_BYTES {
1548            Err(EventSchemaError::SummaryLimitTooLarge)
1549        } else {
1550            Ok(Self(value))
1551        }
1552    }
1553    #[must_use]
1554    pub const fn get(self) -> u64 {
1555        self.0
1556    }
1557}
1558impl Serialize for SummaryByteLimit {
1559    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
1560    where
1561        S: serde::Serializer,
1562    {
1563        s.serialize_u64(self.0)
1564    }
1565}
1566impl<'de> Deserialize<'de> for SummaryByteLimit {
1567    fn deserialize<D>(d: D) -> Result<Self, D::Error>
1568    where
1569        D: serde::Deserializer<'de>,
1570    {
1571        Self::new(u64::deserialize(d)?).map_err(serde::de::Error::custom)
1572    }
1573}
1574impl JsonSchema for SummaryByteLimit {
1575    fn inline_schema() -> bool {
1576        true
1577    }
1578    fn schema_name() -> Cow<'static, str> {
1579        Cow::Borrowed("SummaryByteLimit")
1580    }
1581    fn json_schema(_: &mut SchemaGenerator) -> Schema {
1582        json_schema!({"type":"integer","minimum":0,"maximum":2_097_152})
1583    }
1584}
1585#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1586#[serde(deny_unknown_fields)]
1587pub struct ContextCheckpointBudgets {
1588    #[schemars(range(min = 1))]
1589    pub context_limit_tokens: u64,
1590    #[schemars(range(min = 1))]
1591    pub trigger_tokens: u64,
1592    #[schemars(range(min = 1))]
1593    pub input_tokens_before: u64,
1594    pub input_tokens_after: u64,
1595    #[serde(default)]
1596    pub keep_recent_tokens: u64,
1597    pub max_summary_bytes: SummaryByteLimit,
1598}
1599
1600#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1601#[serde(deny_unknown_fields)]
1602pub struct ContextRehydratedFile {
1603    pub path: SafeDisplayText,
1604    pub content: String,
1605    pub byte_length: u64,
1606    pub sha256: Sha256Digest,
1607}
1608
1609#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1610#[serde(deny_unknown_fields)]
1611pub struct AgentMdEntry {
1612    pub source: SafeDisplayText,
1613    pub content: String,
1614    pub byte_length: u64,
1615}
1616impl AgentMdEntry {
1617    pub const MAX_CONTENT_BYTES: usize = 2 * 1024 * 1024;
1618
1619    pub fn validate(&self) -> Result<(), EventSchemaError> {
1620        if self.content.len() > Self::MAX_CONTENT_BYTES
1621            || self.byte_length != self.content.len() as u64
1622        {
1623            return Err(EventSchemaError::InvalidAgentMd);
1624        }
1625        Ok(())
1626    }
1627}
1628
1629#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1630#[serde(deny_unknown_fields)]
1631pub struct AgentMdSkipped {
1632    pub path: SafeDisplayText,
1633    pub byte_length: u64,
1634}
1635impl AgentMdSkipped {
1636    pub const MAX_SKIP_BYTES: u64 = 2 * 1024 * 1024;
1637
1638    pub fn validate(&self) -> Result<(), EventSchemaError> {
1639        if self.byte_length <= Self::MAX_SKIP_BYTES {
1640            return Err(EventSchemaError::InvalidAgentMd);
1641        }
1642        Ok(())
1643    }
1644}
1645
1646impl ContextRehydratedFile {
1647    pub const MAX_CONTENT_BYTES: usize = 32 * 1024;
1648
1649    pub fn validate(&self) -> Result<(), EventSchemaError> {
1650        if self.content.len() > Self::MAX_CONTENT_BYTES
1651            || self.byte_length != self.content.len() as u64
1652            || self.sha256 != Sha256Digest::of_bytes(self.content.as_bytes())
1653        {
1654            return Err(EventSchemaError::InvalidContextRehydration);
1655        }
1656        Ok(())
1657    }
1658}
1659
1660#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
1661#[serde(deny_unknown_fields)]
1662pub struct InternalSummaryCheckpoint {
1663    summary: String,
1664    byte_length: u64,
1665    sha256: Sha256Digest,
1666    invocation_id: InternalAgentInvocationId,
1667    internal_run_id: InternalAgentRunId,
1668}
1669impl InternalSummaryCheckpoint {
1670    pub fn new(
1671        summary: String,
1672        invocation_id: InternalAgentInvocationId,
1673        internal_run_id: InternalAgentRunId,
1674        max: SummaryByteLimit,
1675    ) -> Result<Self, EventSchemaError> {
1676        let byte_length = summary.len() as u64;
1677        if byte_length > max.get() || byte_length > SummaryByteLimit::GLOBAL_MAX_BYTES {
1678            return Err(EventSchemaError::SummaryTooLarge);
1679        }
1680        let sha256 = Sha256Digest::of_bytes(summary.as_bytes());
1681        Ok(Self {
1682            summary,
1683            byte_length,
1684            sha256,
1685            invocation_id,
1686            internal_run_id,
1687        })
1688    }
1689    #[must_use]
1690    pub fn summary(&self) -> &str {
1691        &self.summary
1692    }
1693    #[must_use]
1694    pub const fn byte_length(&self) -> u64 {
1695        self.byte_length
1696    }
1697}
1698impl<'de> Deserialize<'de> for InternalSummaryCheckpoint {
1699    fn deserialize<D>(d: D) -> Result<Self, D::Error>
1700    where
1701        D: serde::Deserializer<'de>,
1702    {
1703        #[derive(Deserialize)]
1704        #[serde(deny_unknown_fields)]
1705        struct Wire {
1706            summary: String,
1707            byte_length: u64,
1708            sha256: Sha256Digest,
1709            invocation_id: InternalAgentInvocationId,
1710            internal_run_id: InternalAgentRunId,
1711        }
1712        let w = Wire::deserialize(d)?;
1713        if w.summary.len() as u64 != w.byte_length
1714            || Sha256Digest::of_bytes(w.summary.as_bytes()) != w.sha256
1715            || w.byte_length > SummaryByteLimit::GLOBAL_MAX_BYTES
1716        {
1717            return Err(serde::de::Error::custom(
1718                "invalid summary length, digest, or bound",
1719            ));
1720        }
1721        Ok(Self {
1722            summary: w.summary,
1723            byte_length: w.byte_length,
1724            sha256: w.sha256,
1725            invocation_id: w.invocation_id,
1726            internal_run_id: w.internal_run_id,
1727        })
1728    }
1729}
1730
1731#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1732#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1733pub enum ContextCheckpoint {
1734    InternalSummary {
1735        #[serde(flatten)]
1736        checkpoint: InternalSummaryCheckpoint,
1737    },
1738    NativeWindow {
1739        window: NativeContextWindow,
1740    },
1741}
1742#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
1743#[serde(deny_unknown_fields)]
1744pub struct ContextCheckpointCommit {
1745    pub checkpoint: ContextCheckpoint,
1746    pub boundaries: ContextCheckpointBoundaries,
1747    pub budgets: ContextCheckpointBudgets,
1748}
1749impl ContextCheckpointCommit {
1750    pub fn validate(&self) -> Result<(), EventSchemaError> {
1751        let recent_outside_source =
1752            self.boundaries
1753                .recent_from_seq
1754                .is_some_and(|recent_from_seq| {
1755                    recent_from_seq < self.boundaries.source_from_seq
1756                        || recent_from_seq > self.boundaries.source_through_seq
1757                });
1758        let native_has_recent_tail =
1759            matches!(self.checkpoint, ContextCheckpoint::NativeWindow { .. })
1760                && (self.boundaries.recent_from_seq.is_some()
1761                    || self.budgets.keep_recent_tokens != 0);
1762        if self.boundaries.source_from_seq == 0
1763            || self.boundaries.source_from_seq > self.boundaries.source_through_seq
1764            || recent_outside_source
1765            || self.boundaries.input_through_seq < self.boundaries.source_through_seq
1766            || self.budgets.context_limit_tokens == 0
1767            || self.budgets.trigger_tokens > self.budgets.context_limit_tokens
1768            || self.budgets.input_tokens_after >= self.budgets.input_tokens_before
1769            || native_has_recent_tail
1770        {
1771            return Err(EventSchemaError::InvalidCheckpointBoundaries);
1772        }
1773        match &self.checkpoint {
1774            ContextCheckpoint::InternalSummary { checkpoint }
1775                if checkpoint.byte_length() > self.budgets.max_summary_bytes.get() =>
1776            {
1777                Err(EventSchemaError::SummaryTooLarge)
1778            }
1779            _ => Ok(()),
1780        }
1781    }
1782
1783    pub fn validate_for_binding(
1784        &self,
1785        binding: &FrozenModelBinding,
1786    ) -> Result<(), EventSchemaError> {
1787        self.validate()?;
1788        if let ContextCheckpoint::NativeWindow { window } = &self.checkpoint {
1789            window
1790                .validate_for_binding(binding, window.scope())
1791                .map_err(|_| EventSchemaError::NativeSelectionMismatch)?;
1792        }
1793        Ok(())
1794    }
1795}
1796impl<'de> Deserialize<'de> for ContextCheckpointCommit {
1797    fn deserialize<D>(d: D) -> Result<Self, D::Error>
1798    where
1799        D: serde::Deserializer<'de>,
1800    {
1801        #[derive(Deserialize)]
1802        #[serde(deny_unknown_fields)]
1803        struct Wire {
1804            checkpoint: ContextCheckpoint,
1805            boundaries: ContextCheckpointBoundaries,
1806            budgets: ContextCheckpointBudgets,
1807        }
1808        let w = Wire::deserialize(d)?;
1809        let value = Self {
1810            checkpoint: w.checkpoint,
1811            boundaries: w.boundaries,
1812            budgets: w.budgets,
1813        };
1814        value.validate().map_err(serde::de::Error::custom)?;
1815        Ok(value)
1816    }
1817}
1818
1819#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1820#[serde(tag = "type", rename_all = "snake_case")]
1821#[allow(clippy::large_enum_variant)]
1822pub enum EventPayload {
1823    SessionCreated {
1824        origin: SessionOrigin,
1825        /// Short, tree-unique model-facing handle (`<agent_type>_<8 hex>`).
1826        /// Optional and additive: sessions created before handles existed carry
1827        /// no value and resolve by full UUID only.
1828        #[serde(default, skip_serializing_if = "Option::is_none")]
1829        short_id: Option<String>,
1830        cwd_identity: CwdIdentity,
1831        creation_selection: RunSelection,
1832        creation_agent: Box<AgentSnapshot>,
1833        runtime_revision: RuntimeRevision,
1834        catalog_revision: CatalogRevision,
1835        provider_state_revision: ProviderStateRevision,
1836        model_revision: ModelRevision,
1837        agent_revision: AgentRevision,
1838        recipe_registry_revision: RecipeRegistryRevision,
1839        manifest_revision: ModelSnapshotRevision,
1840    },
1841    SessionReverted {
1842        #[schemars(range(min = 1))]
1843        through_seq: u64,
1844    },
1845    SessionPermissionOverlaySet {
1846        overlay: SessionPermissionOverlay,
1847    },
1848    SkillLoaded {
1849        name: String,
1850        rendered_body: String,
1851        source_path: String,
1852        args: String,
1853        base_dir: String,
1854        #[schemars(length(max = 10))]
1855        supporting_files: Vec<String>,
1856    },
1857    SkillInvocationNoted {
1858        name: String,
1859    },
1860    AgentMdLoaded {
1861        #[schemars(length(min = 1, max = 2))]
1862        entries: Vec<AgentMdEntry>,
1863    },
1864    AgentMdSkipped {
1865        path: SafeDisplayText,
1866        byte_length: u64,
1867    },
1868    PluginEventAdded {
1869        plugin: String,
1870        name: String,
1871        payload: Value,
1872    },
1873    PluginDiagnostic {
1874        plugin: String,
1875        kind: PluginDiagnosticKind,
1876        message: String,
1877        count: u64,
1878    },
1879    GoalActivated {
1880        goal_id: GoalId,
1881        objective: String,
1882        revision: u64,
1883        #[serde(default, skip_serializing_if = "Option::is_none")]
1884        selection: Option<RunSelection>,
1885    },
1886    GoalChecklistRevised {
1887        goal_id: GoalId,
1888        items: Vec<GoalItem>,
1889        revision: u64,
1890    },
1891    GoalLifecycleChanged {
1892        goal_id: GoalId,
1893        status: GoalStatus,
1894        revision: u64,
1895        #[serde(default, skip_serializing_if = "Option::is_none")]
1896        selection: Option<RunSelection>,
1897    },
1898    /// Sole durable message body and per-send delivery mode. Acceptance is valid
1899    /// during an active run without creating a runless UserInputAdmitted.
1900    ProducerMessageAccepted {
1901        message_id: ProducerMessageId,
1902        producer_owner: ProducerOwner,
1903        mode: ProducerDeliveryMode,
1904        idempotency_key: ProducerIdempotencyKey,
1905        #[serde(default, skip_serializing_if = "producer_description_is_empty")]
1906        description: SafeDisplayText,
1907        body: String,
1908        #[serde(default, skip_serializing_if = "Option::is_none")]
1909        reminder: Option<GoalReminderIdentity>,
1910        /// Internal `send_message` guard metadata: position of this message in
1911        /// its agent-mail chain (`0` for mail sent by a run that has seen no
1912        /// agent mail). Never rendered into the agent-visible envelope and
1913        /// never compared for idempotency. Absent on non-Agent producers and on
1914        /// mail accepted before hop counting existed.
1915        #[serde(default, skip_serializing_if = "Option::is_none")]
1916        agent_hop: Option<u32>,
1917    },
1918    /// Run-scoped reference, not a second body or consumption marker. Its event
1919    /// sequence participates in ModelTurnCommitted.input_through_seq coverage.
1920    ProducerMessageAdmitted {
1921        message_id: ProducerMessageId,
1922    },
1923    /// Run-scoped reservation of pending admitted messages. The envelope
1924    /// sequence is the claim token, not proof that provider dispatch occurred.
1925    ProducerMessagesClaimed {
1926        #[schemars(length(min = 1))]
1927        message_ids: Vec<ProducerMessageId>,
1928    },
1929    /// Releases the claim identified by a ProducerMessagesClaimed envelope.
1930    ProducerMessagesReleased {
1931        #[schemars(range(min = 1))]
1932        claim_seq: u64,
1933    },
1934    /// Append only after committed model input coverage includes the admission.
1935    /// Recovery repairs missing markers from that commit, never from API selection.
1936    ProducerMessageConsumed {
1937        message_id: ProducerMessageId,
1938        run_id: RunId,
1939    },
1940    /// Pending messages may be discarded by stable owner identity. Historical
1941    /// goal reminder discards omitted producer_owner and remain readable.
1942    ProducerMessageDiscarded {
1943        message_id: ProducerMessageId,
1944        #[serde(default, skip_serializing_if = "Option::is_none")]
1945        reminder: Option<GoalReminderIdentity>,
1946        #[serde(default, skip_serializing_if = "Option::is_none")]
1947        producer_owner: Option<ProducerOwner>,
1948    },
1949    RunStarted {
1950        client_run_id: ClientRunId,
1951        selection: RunSelection,
1952        agent: Box<AgentSnapshot>,
1953        runtime_revision: RuntimeRevision,
1954        catalog_revision: CatalogRevision,
1955        provider_state_revision: ProviderStateRevision,
1956        model_revision: ModelRevision,
1957        agent_revision: AgentRevision,
1958        recipe_registry_revision: RecipeRegistryRevision,
1959        manifest_revision: ModelSnapshotRevision,
1960        #[schemars(length(min = 1, max = 256))]
1961        selected_suffix: Vec<FrozenModelBinding>,
1962        #[serde(default)]
1963        #[schemars(length(max = 3))]
1964        internal_agents: Vec<FrozenInternalAgentDefinition>,
1965        input_through_seq: u64,
1966    },
1967    MessageInjected {
1968        role: crate::ExtensionMessageRole,
1969        input: String,
1970    },
1971    UserInputAdmitted {
1972        input: String,
1973    },
1974    UserInputSubmitted {
1975        input: String,
1976    },
1977    UserInputTransformed {
1978        original_input: String,
1979        input: String,
1980    },
1981    UserInputRecalled {
1982        input: String,
1983    },
1984    UserInputRecalledV2 {
1985        user_input_seq: u64,
1986        input: String,
1987    },
1988    UserInputApplied {
1989        user_input_seq: u64,
1990    },
1991    DelegatedContextSeeded {
1992        invocation_id: InvocationId,
1993        #[schemars(length(max = 65536))]
1994        turns: Vec<DelegatedContextTurn>,
1995    },
1996    RunCompleted {
1997        #[serde(deserialize_with = "deserialize_required_option")]
1998        #[schemars(with = "crate::NullableSchema<String>", required)]
1999        final_text: Option<String>,
2000    },
2001    RunFailed {
2002        error: SafeErrorMessage,
2003        #[serde(default, skip_serializing_if = "Option::is_none")]
2004        model_error: Option<ModelErrorSummary>,
2005        #[serde(default, skip_serializing_if = "Option::is_none")]
2006        resolved_model: Option<ResolvedModelRef>,
2007    },
2008    RunCancelled {
2009        #[serde(deserialize_with = "deserialize_required_option")]
2010        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2011        reason: Option<SafeErrorMessage>,
2012    },
2013    RunInterrupted {
2014        #[serde(deserialize_with = "deserialize_required_option")]
2015        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2016        reason: Option<SafeErrorMessage>,
2017    },
2018    ModelAttemptStarted {
2019        attempt_id: AttemptId,
2020        #[schemars(range(min = 1))]
2021        attempt_ordinal: u32,
2022        fallback_index: u32,
2023        retry_ordinal: u32,
2024        resolved_model: ResolvedModelRef,
2025        prompt_fingerprint: Sha256Digest,
2026    },
2027    ModelRequestPrepared {
2028        attempt_id: AttemptId,
2029        prompt_fingerprint: Sha256Digest,
2030    },
2031    TextDelta {
2032        attempt_id: AttemptId,
2033        text: String,
2034    },
2035    ReasoningDelta {
2036        attempt_id: AttemptId,
2037        text: String,
2038    },
2039    AttemptAbandoned {
2040        attempt_id: AttemptId,
2041        /// The model error that ended the attempt (retry, fallback, or
2042        /// recovery trigger).
2043        #[serde(default, skip_serializing_if = "Option::is_none")]
2044        model_error: Option<ModelErrorSummary>,
2045    },
2046    ModelReplayEvaluated {
2047        attempt_id: AttemptId,
2048        resolved_model: ResolvedModelRef,
2049        #[schemars(length(max = 4096))]
2050        ordered_decisions: Vec<ReplayDecision>,
2051    },
2052    ModelTurnCommitted {
2053        attempt_id: AttemptId,
2054        #[schemars(range(min = 1))]
2055        model_turn_seq: u64,
2056        resolved_model: ResolvedModelRef,
2057        input_through_seq: u64,
2058        turn: PersistedModelTurn,
2059        #[schemars(length(max = 256))]
2060        warnings: Vec<SafeErrorMessage>,
2061    },
2062    ModelUsageRecorded {
2063        #[schemars(range(min = 1))]
2064        model_turn_seq: u64,
2065        agent_id: AgentId,
2066        resolved_model: ResolvedModelRef,
2067        usage: Usage,
2068        #[serde(default)]
2069        estimated_cost_pico_usd: Option<u64>,
2070    },
2071    ModelFallback {
2072        from: ResolvedModelRef,
2073        to: ResolvedModelRef,
2074        from_fallback_index: u32,
2075        to_fallback_index: u32,
2076        #[schemars(range(min = 1))]
2077        attempts_on_from: u32,
2078        error: ModelErrorSummary,
2079    },
2080    ToolCallStarted {
2081        #[serde(flatten)]
2082        start: ToolCallStart,
2083    },
2084    ToolCallProgress {
2085        tool_call_id: ToolCallId,
2086        message: SafeDisplayText,
2087        #[serde(
2088            default,
2089            alias = "output_chunk",
2090            deserialize_with = "crate::tool_output::deserialize_delta_display"
2091        )]
2092        display: Option<String>,
2093    },
2094    ToolCallTerminated {
2095        #[serde(flatten)]
2096        termination: ToolCallTermination,
2097    },
2098    ToolOutputElided {
2099        tool_call_id: ToolCallId,
2100        original_bytes: u64,
2101        retained: ArtifactReference,
2102    },
2103    ToolStdinSubmitted {
2104        tool_call_id: ToolCallId,
2105        byte_count: u64,
2106    },
2107    ToolCallLinked {
2108        tool_call_id: ToolCallId,
2109        child_session_id: SessionId,
2110    },
2111    DelegationReserved {
2112        reservation: DelegationReservation,
2113        child_agent: Box<AgentSnapshot>,
2114        manifest_revision: ModelSnapshotRevision,
2115        runtime_revision: RuntimeRevision,
2116        catalog_revision: CatalogRevision,
2117        provider_state_revision: ProviderStateRevision,
2118        model_revision: ModelRevision,
2119        agent_revision: AgentRevision,
2120        recipe_registry_revision: RecipeRegistryRevision,
2121        #[schemars(length(min = 1, max = 256))]
2122        selected_suffix: Vec<FrozenModelBinding>,
2123        #[serde(default)]
2124        #[schemars(length(max = 256))]
2125        cache_strategies: Vec<Option<FrozenCacheStrategy>>,
2126        request_fingerprint: Sha256Digest,
2127        request: DelegateRequestPayload,
2128    },
2129    DelegationStarted {
2130        invocation_id: InvocationId,
2131        child_session_id: SessionId,
2132    },
2133    DelegationRunStarted {
2134        invocation_id: InvocationId,
2135        child_run_id: RunId,
2136    },
2137    DelegationRunAttached {
2138        invocation_id: InvocationId,
2139        child_run_id: RunId,
2140    },
2141    DelegationFinished {
2142        invocation_id: InvocationId,
2143        child_session_id: SessionId,
2144        #[serde(deserialize_with = "deserialize_required_option")]
2145        #[schemars(with = "crate::NullableSchema<RunId>", required)]
2146        child_run_id: Option<RunId>,
2147        status: SessionStatus,
2148        #[serde(deserialize_with = "deserialize_required_option")]
2149        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2150        reason: Option<SafeErrorMessage>,
2151    },
2152    DelegateQueued {
2153        session_id: SessionId,
2154        #[serde(deserialize_with = "deserialize_required_option")]
2155        #[schemars(with = "crate::NullableSchema<u32>", required)]
2156        position: Option<u32>,
2157    },
2158    DelegateFinished {
2159        session_id: SessionId,
2160        /// Handle rendered into the notification body. Additive and optional:
2161        /// pre-handle logs render the UUID.
2162        #[serde(default, skip_serializing_if = "Option::is_none")]
2163        short_id: Option<String>,
2164        status: SessionStatus,
2165        #[schemars(length(max = 2048))]
2166        preview: String,
2167        total_lines: u64,
2168    },
2169    DelegateFinishedV2 {
2170        invocation_id: InvocationId,
2171        session_id: SessionId,
2172        /// Handle rendered into the notification body. Additive and optional:
2173        /// pre-handle logs render the UUID.
2174        #[serde(default, skip_serializing_if = "Option::is_none")]
2175        short_id: Option<String>,
2176        status: SessionStatus,
2177        #[schemars(length(max = 2048))]
2178        preview: String,
2179        total_lines: u64,
2180    },
2181    DelegateChildTerminated {
2182        status: SessionStatus,
2183        #[serde(deserialize_with = "deserialize_required_option")]
2184        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2185        reason: Option<SafeErrorMessage>,
2186    },
2187    ApprovalRequested {
2188        request: ApprovalRequest,
2189    },
2190    ApprovalEvaluated {
2191        approval_id: ApprovalId,
2192        decision: ApprovalInternalDecision,
2193    },
2194    ApprovalEscalated {
2195        approval_id: ApprovalId,
2196        reason_code: ApprovalReasonCode,
2197    },
2198    ApprovalUserDecisionRecorded {
2199        approval_id: ApprovalId,
2200        client_response_id: ClientResponseId,
2201        decision: ApprovalUserDecision,
2202        #[serde(deserialize_with = "deserialize_required_option")]
2203        #[schemars(with = "crate::NullableSchema<ApprovalFeedback>", required)]
2204        feedback: Option<ApprovalFeedback>,
2205    },
2206    ApprovalFinalized {
2207        approval_id: ApprovalId,
2208        decision: ApprovalFinalDecision,
2209    },
2210    ApprovalCancelled {
2211        approval_id: ApprovalId,
2212        reason_code: ApprovalReasonCode,
2213    },
2214    ApprovalDoomLoopDetected {
2215        approval_id: ApprovalId,
2216        operation_fingerprint: OperationFingerprint,
2217        #[schemars(range(min = 4))]
2218        repetitions: u32,
2219    },
2220    TreeApprovalGrantCommitted {
2221        grant: TreeApprovalGrant,
2222    },
2223    InternalAgentStarted {
2224        invocation_id: InternalAgentInvocationId,
2225        internal_run_id: InternalAgentRunId,
2226        kind: InternalAgentKind,
2227        backend: InternalAgentBackend,
2228        call: SafeInternalAgentCall,
2229    },
2230    InternalAgentCompleted {
2231        invocation_id: InternalAgentInvocationId,
2232        internal_run_id: InternalAgentRunId,
2233        kind: InternalAgentKind,
2234        result: SafeInternalAgentResult,
2235    },
2236    InternalAgentUsageRecorded {
2237        internal_run_id: InternalAgentRunId,
2238        kind: InternalAgentKind,
2239        agent_id: AgentId,
2240        resolved_model: ResolvedModelRef,
2241        usage: Usage,
2242        #[serde(default)]
2243        estimated_cost_pico_usd: Option<u64>,
2244    },
2245    InternalAgentFailed {
2246        invocation_id: InternalAgentInvocationId,
2247        internal_run_id: InternalAgentRunId,
2248        kind: InternalAgentKind,
2249        failure: InternalAgentFailure,
2250    },
2251    InternalAgentCancelled {
2252        invocation_id: InternalAgentInvocationId,
2253        internal_run_id: InternalAgentRunId,
2254        kind: InternalAgentKind,
2255        #[serde(deserialize_with = "deserialize_required_option")]
2256        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2257        reason: Option<SafeErrorMessage>,
2258    },
2259    InternalAgentInterrupted {
2260        invocation_id: InternalAgentInvocationId,
2261        internal_run_id: InternalAgentRunId,
2262        kind: InternalAgentKind,
2263        #[serde(deserialize_with = "deserialize_required_option")]
2264        #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2265        reason: Option<SafeErrorMessage>,
2266    },
2267    InternalAgentFallback {
2268        invocation_id: InternalAgentInvocationId,
2269        internal_run_id: InternalAgentRunId,
2270        kind: InternalAgentKind,
2271        from: InternalAgentBackend,
2272        to: InternalAgentBackend,
2273        failure: InternalAgentFailure,
2274        #[schemars(range(min = 1))]
2275        attempts: u32,
2276    },
2277    ContextCheckpointCommitted {
2278        #[serde(flatten)]
2279        commit: ContextCheckpointCommit,
2280    },
2281    ContextRehydrated {
2282        #[schemars(length(max = 5))]
2283        files: Vec<ContextRehydratedFile>,
2284    },
2285    SessionTitleCommitted {
2286        change: SessionTitleChange,
2287        input_through_seq: u64,
2288    },
2289}
2290impl EventPayload {
2291    fn requires_run_id(&self) -> bool {
2292        !matches!(
2293            self,
2294            Self::SessionCreated { .. }
2295                | Self::SessionReverted { .. }
2296                | Self::SessionPermissionOverlaySet { .. }
2297                | Self::SkillLoaded { .. }
2298                | Self::SkillInvocationNoted { .. }
2299                | Self::PluginEventAdded { .. }
2300                | Self::PluginDiagnostic { .. }
2301                | Self::GoalActivated { .. }
2302                | Self::GoalChecklistRevised { .. }
2303                | Self::GoalLifecycleChanged { .. }
2304                | Self::ProducerMessageAccepted { .. }
2305                | Self::ProducerMessageDiscarded { .. }
2306                | Self::SessionTitleCommitted { .. }
2307                | Self::UserInputAdmitted { .. }
2308                | Self::UserInputRecalled { .. }
2309                | Self::DelegatedContextSeeded { .. }
2310                | Self::DelegateChildTerminated { .. }
2311        )
2312    }
2313    fn validate(&self) -> Result<(), EventSchemaError> {
2314        match self {
2315            Self::GoalActivated { objective, .. } if objective.trim().is_empty() => {
2316                return Err(EventSchemaError::InvalidGoalEvent);
2317            }
2318            Self::GoalLifecycleChanged { status, selection: Some(_), .. }
2319                if *status != GoalStatus::Active => {
2320                return Err(EventSchemaError::InvalidGoalEvent);
2321            }
2322            Self::GoalChecklistRevised { items, .. } => {
2323                if items.iter().any(|item| item.description.trim().is_empty()) {
2324                    return Err(EventSchemaError::InvalidGoalEvent);
2325                }
2326            }
2327            Self::ProducerMessageAccepted { producer_owner, reminder, .. } => {
2328                if matches!(producer_owner, ProducerOwner::Plugin { plugin } if plugin.trim().is_empty())
2329                    || reminder.as_ref().is_some_and(|identity| {
2330                        !matches!(producer_owner, ProducerOwner::Goal { goal_id } if *goal_id == identity.goal_id)
2331                    })
2332                {
2333                    return Err(EventSchemaError::InvalidProducerMessage);
2334                }
2335            }
2336            Self::ProducerMessagesClaimed { message_ids }
2337                if message_ids.is_empty()
2338                    || message_ids.iter().copied().collect::<HashSet<_>>().len()
2339                        != message_ids.len() =>
2340            {
2341                return Err(EventSchemaError::InvalidProducerMessage);
2342            }
2343            Self::ProducerMessagesReleased { claim_seq } if *claim_seq == 0 => {
2344                return Err(EventSchemaError::InvalidProducerMessage);
2345            }
2346            Self::ProducerMessageDiscarded {
2347                reminder,
2348                producer_owner,
2349                ..
2350            } if reminder.is_none() && producer_owner.is_none() => {
2351                return Err(EventSchemaError::InvalidProducerMessage);
2352            }
2353            Self::SessionCreated {
2354                creation_selection,
2355                creation_agent,
2356                ..
2357            } => {
2358                creation_agent
2359                    .validate_selection(creation_selection)
2360                    .map_err(|_| EventSchemaError::SelectionAgentMismatch)?;
2361            }
2362            Self::SessionReverted { through_seq } => {
2363                if *through_seq == 0 {
2364                    return Err(EventSchemaError::InvalidRevertSequence);
2365                }
2366            }
2367            Self::SessionPermissionOverlaySet { overlay } => overlay
2368                .validate()
2369                .map_err(|_| EventSchemaError::InvalidSessionPermissionOverlay)?,
2370            Self::SkillLoaded {
2371                name,
2372                rendered_body,
2373                source_path,
2374                base_dir,
2375                supporting_files,
2376                ..
2377            } => {
2378                if name.is_empty()
2379                    || rendered_body.is_empty()
2380                    || source_path.is_empty()
2381                    || base_dir.is_empty()
2382                    || supporting_files.len() > 10
2383                {
2384                    return Err(EventSchemaError::InvalidSkillEvent);
2385                }
2386            }
2387            Self::SkillInvocationNoted { name } if name.is_empty() => {
2388                return Err(EventSchemaError::InvalidSkillEvent);
2389            }
2390            Self::AgentMdLoaded { entries } => {
2391                if entries.is_empty() || entries.len() > 2 {
2392                    return Err(EventSchemaError::InvalidAgentMd);
2393                }
2394                for entry in entries {
2395                    entry.validate()?;
2396                }
2397            }
2398            Self::AgentMdSkipped {
2399                path,
2400                byte_length,
2401            } => {
2402                if byte_length <= &AgentMdSkipped::MAX_SKIP_BYTES || path.as_str().is_empty() {
2403                    return Err(EventSchemaError::InvalidAgentMd);
2404                }
2405            }
2406            Self::RunStarted {
2407                selection,
2408                agent,
2409                selected_suffix,
2410                internal_agents,
2411                ..
2412            } => {
2413                agent
2414                    .validate_selected_suffix(selection, selected_suffix)
2415                    .map_err(|_| EventSchemaError::InvalidSelectedSuffix)?;
2416                if !matches!(internal_agents.len(), 0 | 3)
2417                    || internal_agents
2418                        .windows(2)
2419                        .any(|pair| pair[0].kind >= pair[1].kind)
2420                    || internal_agents.iter().any(|definition| {
2421                        definition.validate().is_err()
2422                            || definition.agent.as_str()
2423                                != match definition.kind {
2424                                    InternalAgentKind::Approval => "approval",
2425                                    InternalAgentKind::ContextCompaction => "compaction",
2426                                    InternalAgentKind::SessionTitle => "title",
2427                                }
2428                    })
2429                {
2430                    return Err(EventSchemaError::InvalidAgentSnapshot);
2431                }
2432            }
2433            Self::MessageInjected { role, input }
2434                if input.trim().is_empty() || *role == crate::ExtensionMessageRole::Tool =>
2435            {
2436                return Err(EventSchemaError::InvalidJson);
2437            }
2438            Self::UserInputTransformed {
2439                original_input,
2440                input,
2441            } if original_input.trim().is_empty()
2442                || input.trim().is_empty()
2443                || original_input == input =>
2444            {
2445                return Err(EventSchemaError::InvalidJson);
2446            }
2447            Self::ModelAttemptStarted {
2448                attempt_ordinal,
2449                resolved_model,
2450                ..
2451            } => {
2452                if *attempt_ordinal == 0 {
2453                    return Err(EventSchemaError::InvalidAttemptOrdinal);
2454                }
2455                resolved_model
2456                    .validate()
2457                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2458            }
2459            Self::ModelReplayEvaluated {
2460                resolved_model,
2461                ordered_decisions,
2462                ..
2463            } => {
2464                if ordered_decisions.len() > 4096 {
2465                    return Err(EventSchemaError::TooManyReplayDecisions);
2466                }
2467                resolved_model
2468                    .validate()
2469                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2470            }
2471            Self::ModelTurnCommitted {
2472                model_turn_seq,
2473                resolved_model,
2474                turn,
2475                warnings,
2476                ..
2477            } => {
2478                if *model_turn_seq == 0 || warnings.len() > 256 {
2479                    return Err(EventSchemaError::InvalidModelTurnCommit);
2480                }
2481                resolved_model
2482                    .validate()
2483                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2484                turn.validate_for(resolved_model)?;
2485            }
2486            Self::ModelUsageRecorded {
2487                model_turn_seq,
2488                resolved_model,
2489                ..
2490            } => {
2491                if *model_turn_seq == 0 {
2492                    return Err(EventSchemaError::InvalidModelTurnCommit);
2493                }
2494                resolved_model
2495                    .validate()
2496                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2497            }
2498            Self::InternalAgentUsageRecorded { resolved_model, .. } => {
2499                resolved_model
2500                    .validate()
2501                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2502            }
2503            Self::ModelFallback {
2504                from,
2505                to,
2506                from_fallback_index,
2507                to_fallback_index,
2508                attempts_on_from,
2509                ..
2510            } => {
2511                if to_fallback_index <= from_fallback_index
2512                    || *attempts_on_from == 0
2513                    || from.selection.model == to.selection.model
2514                {
2515                    return Err(EventSchemaError::InvalidFallback);
2516                }
2517                from.validate()
2518                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2519                to.validate()
2520                    .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2521            }
2522            Self::ToolCallStarted { start } => {
2523                start.output.validate().map_err(|_| EventSchemaError::InvalidToolTermination)?;
2524                if start.owner.model_turn_seq == 0 {
2525                    return Err(EventSchemaError::ZeroModelTurnSequence);
2526                }
2527            }
2528            Self::ToolCallProgress { display, .. } => {
2529                if display.as_ref().is_some_and(|text| !crate::tool_output::validate_display(text, SafeDisplayText::MAX_BYTES)) {
2530                    return Err(EventSchemaError::ToolOutputTooLarge);
2531                }
2532            }
2533            Self::ToolCallTerminated { termination } => termination.validate()?,
2534            Self::DelegationReserved {
2535                child_agent,
2536                selected_suffix,
2537                cache_strategies,
2538                request,
2539                ..
2540            } => {
2541                request.validate()?;
2542                if (!cache_strategies.is_empty() && cache_strategies.len() != selected_suffix.len())
2543                    || cache_strategies
2544                        .iter()
2545                        .flatten()
2546                        .any(|strategy| strategy.validate().is_err())
2547                {
2548                    return Err(EventSchemaError::InvalidDelegationLifecycle);
2549                }
2550                child_agent
2551                    .validate_selected_suffix(
2552                        &RunSelection {
2553                            agent: child_agent.agent.clone(),
2554                            model: selected_suffix
2555                                .first()
2556                                .ok_or(EventSchemaError::InvalidDelegationLifecycle)?
2557                                .selection
2558                                .clone(),
2559                            preset: None,
2560                        },
2561                        selected_suffix,
2562                    )
2563                    .map_err(|_| EventSchemaError::InvalidDelegationLifecycle)?;
2564            }
2565            Self::DelegationFinished { status, .. }
2566                if !matches!(
2567                    status,
2568                    SessionStatus::Completed
2569                        | SessionStatus::Failed
2570                        | SessionStatus::Interrupted
2571                        | SessionStatus::Cancelled
2572                ) =>
2573            {
2574                return Err(EventSchemaError::InvalidDelegationLifecycle);
2575            }
2576            Self::DelegatedContextSeeded { turns, .. } => {
2577                if turns.is_empty()
2578                    || turns.len() > 65_536
2579                    || turns.iter().any(|turn| turn.text.is_empty())
2580                    || turns.iter().map(|turn| turn.text.len()).sum::<usize>() > 65_536
2581                {
2582                    return Err(EventSchemaError::InvalidDelegatedContext);
2583                }
2584            }
2585            Self::DelegateFinished {
2586                status, preview, ..
2587            }
2588            | Self::DelegateFinishedV2 {
2589                status, preview, ..
2590            } => {
2591                if !matches!(
2592                    status,
2593                    SessionStatus::Completed
2594                        | SessionStatus::Failed
2595                        | SessionStatus::Interrupted
2596                        | SessionStatus::Cancelled
2597                ) || preview.len() > 2048
2598                {
2599                    return Err(EventSchemaError::InvalidDelegateFinished);
2600                }
2601            }
2602            Self::DelegateChildTerminated { status, .. }
2603                if !matches!(status, SessionStatus::Failed | SessionStatus::Cancelled) =>
2604            {
2605                return Err(EventSchemaError::InvalidDelegateFinished);
2606            }
2607            Self::ToolOutputElided {
2608                original_bytes,
2609                retained,
2610                ..
2611            } => {
2612                if *original_bytes == 0 {
2613                    return Err(EventSchemaError::InvalidToolElision);
2614                }
2615                retained.validate()?;
2616            }
2617            Self::ApprovalEvaluated { decision, .. } => {
2618                decision
2619                    .validate()
2620                    .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?;
2621            }
2622            Self::ApprovalEscalated { reason_code, .. }
2623                if *reason_code != ApprovalReasonCode::Escalated =>
2624            {
2625                return Err(EventSchemaError::InvalidApprovalLifecycle);
2626            }
2627            Self::ApprovalUserDecisionRecorded {
2628                decision, feedback, ..
2629            } if feedback.is_some() && *decision != ApprovalUserDecision::Reject => {
2630                return Err(EventSchemaError::InvalidApprovalLifecycle);
2631            }
2632            Self::ApprovalFinalized { decision, .. } => decision
2633                .validate()
2634                .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?,
2635            Self::ApprovalCancelled { reason_code, .. }
2636                if !matches!(
2637                    reason_code,
2638                    ApprovalReasonCode::UserCancelled
2639                        | ApprovalReasonCode::RequestCancelled
2640                        | ApprovalReasonCode::ApprovalExpired
2641                        | ApprovalReasonCode::OperationChanged
2642                        | ApprovalReasonCode::PreparedCapabilityLost
2643                ) =>
2644            {
2645                return Err(EventSchemaError::InvalidApprovalLifecycle);
2646            }
2647            Self::ApprovalDoomLoopDetected { repetitions, .. } if *repetitions < 4 => {
2648                return Err(EventSchemaError::InvalidApprovalCounter);
2649            }
2650            Self::TreeApprovalGrantCommitted { grant } => grant
2651                .validate()
2652                .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?,
2653            Self::InternalAgentStarted { backend, .. } => validate_internal_backend(backend)?,
2654            Self::InternalAgentFailed { failure, .. } => validate_internal_failure(failure)?,
2655            Self::InternalAgentFallback {
2656                from,
2657                to,
2658                failure,
2659                attempts,
2660                ..
2661            } => {
2662                if from == to || *attempts == 0 {
2663                    return Err(EventSchemaError::InvalidInternalAgentLifecycle);
2664                }
2665                validate_internal_backend(from)?;
2666                validate_internal_backend(to)?;
2667                validate_internal_failure(failure)?;
2668            }
2669            Self::ContextCheckpointCommitted { commit } => commit.validate()?,
2670            Self::ContextRehydrated { files } => {
2671                if files.is_empty() || files.len() > 5 {
2672                    return Err(EventSchemaError::InvalidContextRehydration);
2673                }
2674                for file in files {
2675                    file.validate()?;
2676                }
2677            }
2678            _ => {}
2679        }
2680        Ok(())
2681    }
2682}
2683
2684#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
2685#[serde(rename_all = "snake_case")]
2686pub enum PluginDiagnosticKind {
2687    EventDrop,
2688    InterceptionTimeout,
2689    InterceptionCrash,
2690    HookBlocked,
2691    OversizedEvent,
2692    InvalidModification,
2693    UnsupportedCapability,
2694    ContextMismatch,
2695    RateLimited,
2696    RecoveryFailed,
2697    RecoveryDisabled,
2698}
2699
2700#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
2701#[serde(deny_unknown_fields)]
2702pub struct StoredEvent {
2703    #[serde(default, skip_serializing_if = "Option::is_none")]
2704    pub engine_version: Option<String>,
2705    #[serde(default, skip_serializing_if = "Option::is_none")]
2706    pub origin: Option<EventOrigin>,
2707    pub session_id: SessionId,
2708    #[serde(deserialize_with = "crate::deserialize_required_option")]
2709    #[schemars(with = "crate::NullableSchema<RunId>", required)]
2710    pub run_id: Option<RunId>,
2711    #[schemars(range(min = 1))]
2712    pub seq: u64,
2713    pub timestamp: Timestamp,
2714    pub payload: EventPayload,
2715}
2716impl StoredEvent {
2717    pub fn validate(&self) -> Result<(), EventSchemaError> {
2718        if let Some(origin) = &self.origin {
2719            EventOrigin::new(origin.as_str())?;
2720        }
2721        if self.seq == 0 {
2722            return Err(EventSchemaError::ZeroEventSequence);
2723        }
2724        if matches!(self.payload, EventPayload::SessionCreated { .. })
2725            && (self.seq != 1 || self.run_id.is_some())
2726        {
2727            return Err(EventSchemaError::InvalidSessionCreatedEnvelope);
2728        }
2729        if self.payload.requires_run_id() && self.run_id.is_none() {
2730            return Err(EventSchemaError::MissingRunId);
2731        }
2732        if let EventPayload::ProducerMessageConsumed { run_id, .. } = &self.payload
2733            && self.run_id != Some(*run_id)
2734        {
2735            return Err(EventSchemaError::InvalidProducerMessage);
2736        }
2737        self.payload.validate()?;
2738        Ok(())
2739    }
2740}
2741
2742/// Reduces an append-only event stream to the currently visible branch.
2743/// Revert markers are physical control records and are not model/transcript content.
2744#[must_use]
2745pub fn visible_events(events: &[StoredEvent]) -> Vec<StoredEvent> {
2746    let mut visible = Vec::new();
2747    let mut historical_ceiling = u64::MAX;
2748    for event in events {
2749        if let EventPayload::SessionReverted { through_seq } = &event.payload {
2750            historical_ceiling = historical_ceiling.min(*through_seq);
2751            visible.retain(|candidate: &StoredEvent| candidate.seq <= historical_ceiling);
2752            visible.push(event.clone());
2753        } else {
2754            visible.push(event.clone());
2755        }
2756    }
2757    visible
2758}
2759impl<'de> Deserialize<'de> for StoredEvent {
2760    fn deserialize<D>(d: D) -> Result<Self, D::Error>
2761    where
2762        D: serde::Deserializer<'de>,
2763    {
2764        #[derive(Deserialize)]
2765        #[serde(deny_unknown_fields)]
2766        struct Wire {
2767            #[serde(default)]
2768            engine_version: Option<String>,
2769            #[serde(default)]
2770            origin: Option<EventOrigin>,
2771            #[serde(default)]
2772            event_schema_version: Option<Value>,
2773            session_id: SessionId,
2774            #[serde(deserialize_with = "crate::deserialize_required_option")]
2775            run_id: Option<RunId>,
2776            seq: u64,
2777            timestamp: Timestamp,
2778            payload: Value,
2779        }
2780        let w = Wire::deserialize(d)?;
2781        let _ = w.event_schema_version;
2782        let value = Self {
2783            engine_version: w.engine_version,
2784            origin: w.origin,
2785            session_id: w.session_id,
2786            run_id: w.run_id,
2787            seq: w.seq,
2788            timestamp: w.timestamp,
2789            payload: deserialize_event_payload_best_effort(w.payload)
2790                .map_err(serde::de::Error::custom)?
2791                .payload,
2792        };
2793        value.validate().map_err(serde::de::Error::custom)?;
2794        Ok(value)
2795    }
2796}
2797
2798const MAX_DEGRADED_FIELDS: usize = 8;
2799
2800#[derive(Clone, Debug, PartialEq)]
2801pub struct EventPayloadRead {
2802    pub payload: EventPayload,
2803    pub degraded_fields: Vec<String>,
2804}
2805
2806/// Decodes an event payload, replacing only leaves which Serde identifies as
2807/// nullable fields. Required-field and unknown-variant failures remain errors.
2808/// Historical checklist item IDs are ignored here only; authored tool/RPC values
2809/// remain strict. The original event log is not rewritten.
2810pub fn deserialize_event_payload_best_effort(mut value: Value) -> Result<EventPayloadRead, String> {
2811    let mut degraded_fields = Vec::new();
2812    if value.get("type").and_then(Value::as_str) == Some("goal_checklist_revised")
2813        && let Some(items) = value.get_mut("items").and_then(Value::as_array_mut)
2814    {
2815        let mut removed_item_ids = false;
2816        for item in items.iter_mut().filter_map(Value::as_object_mut) {
2817            removed_item_ids |= item.remove("id").is_some();
2818        }
2819        if removed_item_ids {
2820            degraded_fields.push("items[].id".into());
2821        }
2822    }
2823    let mut previous_path = None;
2824    for _ in 0..=MAX_DEGRADED_FIELDS {
2825        match serde_path_to_error::deserialize::<_, EventPayload>(value.clone().into_deserializer())
2826        {
2827            Ok(payload) => {
2828                return Ok(EventPayloadRead {
2829                    payload,
2830                    degraded_fields,
2831                });
2832            }
2833            Err(error) => {
2834                let path = error.path().to_string();
2835                if path != "." && previous_path.as_deref() == Some(path.as_str()) {
2836                    return Err(error.to_string());
2837                }
2838                let degraded = if path == "." {
2839                    replace_degradable_leaf(&mut value, &error.to_string())
2840                } else if replace_path_with_null(&mut value, error.path()) {
2841                    Some(path.clone())
2842                } else {
2843                    None
2844                };
2845                let Some(degraded) = degraded else {
2846                    return Err(error.to_string());
2847                };
2848                previous_path = Some(path.clone());
2849                degraded_fields.push(degraded);
2850            }
2851        }
2852    }
2853    Err(format!(
2854        "event payload exceeds the {MAX_DEGRADED_FIELDS}-field degradation limit"
2855    ))
2856}
2857
2858#[derive(Clone)]
2859enum ValuePathSegment {
2860    Key(String),
2861    Index(usize),
2862}
2863
2864fn replace_degradable_leaf(value: &mut Value, original_error: &str) -> Option<String> {
2865    let mut paths = Vec::new();
2866    collect_leaf_paths(value, &mut Vec::new(), &mut paths);
2867    for path in paths {
2868        let mut candidate = value.clone();
2869        if !set_value_path(&mut candidate, &path, Value::Null) {
2870            continue;
2871        }
2872        match serde_path_to_error::deserialize::<_, EventPayload>(
2873            candidate.clone().into_deserializer(),
2874        ) {
2875            Ok(_) => {
2876                *value = candidate;
2877                return Some(display_value_path(&path));
2878            }
2879            Err(error)
2880                if error.to_string() != original_error
2881                    && !error.inner().to_string().contains("invalid type: null") =>
2882            {
2883                *value = candidate;
2884                return Some(display_value_path(&path));
2885            }
2886            Err(_) => {}
2887        }
2888    }
2889    let missing = original_error
2890        .split("missing field `")
2891        .nth(1)
2892        .and_then(|suffix| suffix.split('`').next())?;
2893    let mut object_paths = Vec::new();
2894    collect_object_paths(value, &mut Vec::new(), &mut object_paths);
2895    for mut path in object_paths {
2896        path.push(ValuePathSegment::Key(missing.to_owned()));
2897        let mut candidate = value.clone();
2898        if !set_value_path(&mut candidate, &path, Value::Null) {
2899            continue;
2900        }
2901        if serde_json::from_value::<EventPayload>(candidate.clone()).is_ok() {
2902            *value = candidate;
2903            return Some(display_value_path(&path));
2904        }
2905    }
2906    None
2907}
2908
2909fn collect_leaf_paths(
2910    value: &Value,
2911    current: &mut Vec<ValuePathSegment>,
2912    paths: &mut Vec<Vec<ValuePathSegment>>,
2913) {
2914    match value {
2915        Value::Object(object) => {
2916            for (key, child) in object {
2917                if key == "type" || child.is_null() {
2918                    continue;
2919                }
2920                current.push(ValuePathSegment::Key(key.clone()));
2921                collect_leaf_paths(child, current, paths);
2922                current.pop();
2923            }
2924        }
2925        Value::Array(array) => {
2926            for (index, child) in array.iter().enumerate() {
2927                current.push(ValuePathSegment::Index(index));
2928                collect_leaf_paths(child, current, paths);
2929                current.pop();
2930            }
2931        }
2932        _ => paths.push(current.clone()),
2933    }
2934}
2935
2936fn collect_object_paths(
2937    value: &Value,
2938    current: &mut Vec<ValuePathSegment>,
2939    paths: &mut Vec<Vec<ValuePathSegment>>,
2940) {
2941    match value {
2942        Value::Object(object) => {
2943            paths.push(current.clone());
2944            for (key, child) in object {
2945                current.push(ValuePathSegment::Key(key.clone()));
2946                collect_object_paths(child, current, paths);
2947                current.pop();
2948            }
2949        }
2950        Value::Array(array) => {
2951            for (index, child) in array.iter().enumerate() {
2952                current.push(ValuePathSegment::Index(index));
2953                collect_object_paths(child, current, paths);
2954                current.pop();
2955            }
2956        }
2957        _ => {}
2958    }
2959}
2960
2961fn set_value_path(value: &mut Value, path: &[ValuePathSegment], replacement: Value) -> bool {
2962    let Some((leaf, parents)) = path.split_last() else {
2963        return false;
2964    };
2965    let mut current = value;
2966    for segment in parents {
2967        let Some(next) = (match segment {
2968            ValuePathSegment::Key(key) => current.get_mut(key),
2969            ValuePathSegment::Index(index) => current.get_mut(*index),
2970        }) else {
2971            return false;
2972        };
2973        current = next;
2974    }
2975    match leaf {
2976        ValuePathSegment::Key(key) => current
2977            .as_object_mut()
2978            .map(|object| object.insert(key.clone(), replacement))
2979            .is_some(),
2980        ValuePathSegment::Index(index) => current
2981            .as_array_mut()
2982            .and_then(|array| array.get_mut(*index))
2983            .map(|value| *value = replacement)
2984            .is_some(),
2985    }
2986}
2987
2988fn display_value_path(path: &[ValuePathSegment]) -> String {
2989    let mut result = String::new();
2990    for segment in path {
2991        match segment {
2992            ValuePathSegment::Key(key) => {
2993                if !result.is_empty() {
2994                    result.push('.');
2995                }
2996                result.push_str(key);
2997            }
2998            ValuePathSegment::Index(index) => result.push_str(&format!("[{index}]")),
2999        }
3000    }
3001    result
3002}
3003
3004fn replace_path_with_null(value: &mut Value, path: &serde_path_to_error::Path) -> bool {
3005    let segments = path
3006        .iter()
3007        .filter(|segment| !matches!(segment, Segment::Enum { .. }))
3008        .collect::<Vec<_>>();
3009    let Some((leaf, parents)) = segments.split_last() else {
3010        return false;
3011    };
3012    let mut current = value;
3013    for segment in parents {
3014        let next = match segment {
3015            Segment::Map { key } => current.get_mut(key),
3016            Segment::Seq { index } => current.get_mut(*index),
3017            Segment::Enum { .. } | Segment::Unknown => None,
3018        };
3019        let Some(next) = next else {
3020            return false;
3021        };
3022        current = next;
3023    }
3024    match leaf {
3025        Segment::Map { key } => {
3026            let Some(object) = current.as_object_mut() else {
3027                return false;
3028            };
3029            object.remove(key);
3030            object.insert(key.clone(), Value::Null);
3031            true
3032        }
3033        Segment::Seq { index } => current
3034            .as_array_mut()
3035            .and_then(|array| array.get_mut(*index))
3036            .map(|leaf| *leaf = Value::Null)
3037            .is_some(),
3038        Segment::Enum { .. } | Segment::Unknown => false,
3039    }
3040}
3041
3042#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
3043#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
3044pub enum EventSubscriptionMessage {
3045    Event {
3046        event: Box<StoredEvent>,
3047    },
3048    Gap {
3049        session_id: SessionId,
3050        last_delivered_seq: u64,
3051    },
3052}
3053#[derive(Clone, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
3054#[serde(rename_all = "snake_case")]
3055pub enum OutputStream {
3056    Stdout,
3057    Stderr,
3058    Single,
3059    Named(#[serde(deserialize_with = "crate::tool_output::deserialize_stream_name")] String),
3060}
3061
3062impl OutputStream {
3063    pub fn name(&self) -> &str {
3064        match self {
3065            Self::Stdout => "stdout",
3066            Self::Stderr => "stderr",
3067            Self::Single => "",
3068            Self::Named(name) => name,
3069        }
3070    }
3071
3072    pub fn from_channel(name: Option<&str>) -> Self {
3073        match name {
3074            None => Self::Single,
3075            Some("stdout") => Self::Stdout,
3076            Some("stderr") => Self::Stderr,
3077            Some(name) => Self::Named(name.to_owned()),
3078        }
3079    }
3080}
3081#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3082#[serde(deny_unknown_fields)]
3083pub struct OutputDelta {
3084    pub call_id: ToolCallId,
3085    pub stream: OutputStream,
3086    pub byte_offset: u64,
3087    pub data: String,
3088}
3089#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3090#[serde(deny_unknown_fields)]
3091pub struct OutputGap {
3092    pub call_id: ToolCallId,
3093    pub stream: OutputStream,
3094    pub next_offset: u64,
3095}
3096#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3097#[serde(deny_unknown_fields)]
3098pub struct OutputSnapshot {
3099    pub call_id: ToolCallId,
3100    pub start_offset: u64,
3101    pub end_offset: u64,
3102    pub chunks: Vec<OutputDelta>,
3103}
3104#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3105#[serde(deny_unknown_fields)]
3106pub struct OutputSnapshotEnvelope {
3107    pub stream: OutputStream,
3108    pub snapshot: OutputSnapshot,
3109}
3110
3111#[derive(Clone, Debug, Eq, PartialEq)]
3112pub enum EventSchemaError {
3113    InvalidGoalEvent,
3114    InvalidProducerMessage,
3115    InvalidOrigin,
3116    InvalidAgentMd,
3117    EmptyTitle,
3118    TitleTooLong,
3119    TitleControlCharacter,
3120    InvalidArtifactReference,
3121    InvalidToolElision,
3122    InvalidContextRehydration,
3123    ToolOutputTooLarge,
3124    ToolMetadataTooLarge,
3125    TooManyAttachments,
3126    InvalidAttachmentFilename,
3127    TooManyToolEmittedMessages,
3128    EmptyToolEmittedMessage,
3129    EmptyToolEmittedText,
3130    ToolEmittedTextTooLarge,
3131    InvalidJson,
3132    ZeroModelTurnSequence,
3133    InvalidToolTermination,
3134    InvalidDelegateFinished,
3135    InvalidDelegationLifecycle,
3136    InvalidDelegatedContext,
3137    TooManyModelParts,
3138    ModelTurnTooLarge,
3139    SummaryLimitTooLarge,
3140    SummaryTooLarge,
3141    InvalidCheckpointBoundaries,
3142    NativeSelectionMismatch,
3143    InvalidApprovalLifecycle,
3144    InvalidApprovalCounter,
3145    InvalidInternalAgentLifecycle,
3146    InvalidSessionMetaSequence,
3147    SelectionAgentMismatch,
3148    InvalidAgentSnapshot,
3149    InvalidSelectedSuffix,
3150    InvalidAttemptOrdinal,
3151    InvalidResolvedModel,
3152    TooManyReplayDecisions,
3153    InvalidModelTurnCommit,
3154    InvalidFallback,
3155    InvalidRevertSequence,
3156    InvalidSessionPermissionOverlay,
3157    InvalidSkillEvent,
3158    ZeroEventSequence,
3159    InvalidSessionCreatedEnvelope,
3160    MissingRunId,
3161}
3162impl fmt::Display for EventSchemaError {
3163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3164        f.write_str(match self {
3165            Self::InvalidGoalEvent => "goal objective/items must be nonblank",
3166            Self::InvalidProducerMessage => {
3167                "producer owner, reminder identity, or consumed run is invalid"
3168            }
3169            Self::InvalidOrigin => "event origin is invalid",
3170            Self::InvalidAgentMd => "AGENTS.md context event is invalid",
3171            Self::EmptyTitle => "session title must not be blank",
3172            Self::TitleTooLong => "session title exceeds 512 bytes",
3173            Self::TitleControlCharacter => "session title must not contain control characters",
3174            Self::InvalidArtifactReference => {
3175                "artifact reference must be a bounded artifact:// URI"
3176            }
3177            Self::InvalidToolElision => "tool output elision is invalid",
3178            Self::InvalidContextRehydration => "context rehydration is invalid",
3179            Self::ToolOutputTooLarge => "persisted tool output exceeds 2 MiB",
3180            Self::ToolMetadataTooLarge => "persisted tool metadata exceeds 2 MiB",
3181            Self::TooManyAttachments => "persisted tool result exceeds 256 attachments",
3182            Self::InvalidAttachmentFilename => "attachment filename is invalid",
3183            Self::TooManyToolEmittedMessages => {
3184                "persisted tool result exceeds 4 additional messages"
3185            }
3186            Self::EmptyToolEmittedMessage => "tool-emitted message content must not be empty",
3187            Self::EmptyToolEmittedText => "tool-emitted text must not be blank",
3188            Self::ToolEmittedTextTooLarge => "tool-emitted message text exceeds 64 KiB",
3189            Self::InvalidJson => "JSON value could not be serialized",
3190            Self::ZeroModelTurnSequence => "model_turn_seq must be positive",
3191            Self::InvalidToolTermination => {
3192                "tool termination outcome/result/error combination is invalid"
3193            }
3194            Self::InvalidDelegateFinished => "delegate completion payload is invalid",
3195            Self::InvalidDelegationLifecycle => "delegation lifecycle payload is invalid",
3196            Self::InvalidDelegatedContext => "delegated context seed is invalid",
3197            Self::TooManyModelParts => "persisted model turn exceeds 4096 parts",
3198            Self::ModelTurnTooLarge => "persisted model turn exceeds 8 MiB",
3199            Self::SummaryLimitTooLarge => "summary limit exceeds 2 MiB",
3200            Self::SummaryTooLarge => "summary exceeds its frozen byte limit",
3201            Self::InvalidCheckpointBoundaries => "context checkpoint boundaries are invalid",
3202            Self::NativeSelectionMismatch => {
3203                "native artifact selection fingerprint does not match resolved model"
3204            }
3205            Self::InvalidApprovalLifecycle => "approval lifecycle fields contradict",
3206            Self::InvalidApprovalCounter => {
3207                "approval lifecycle counter is below its runtime minimum"
3208            }
3209            Self::InvalidInternalAgentLifecycle => "internal-agent lifecycle fields contradict",
3210            Self::InvalidSessionMetaSequence => "session metadata sequence fields are inconsistent",
3211            Self::SelectionAgentMismatch => {
3212                "creation selection agent does not match creation snapshot"
3213            }
3214            Self::InvalidAgentSnapshot => "event contains an invalid agent snapshot",
3215            Self::InvalidSelectedSuffix => "RunStarted selected suffix is invalid",
3216            Self::InvalidAttemptOrdinal => "attempt ordinal must be positive",
3217            Self::InvalidResolvedModel => "event contains an invalid resolved model",
3218            Self::TooManyReplayDecisions => "replay decision list exceeds 4096 entries",
3219            Self::InvalidModelTurnCommit => "model turn sequence or warnings are invalid",
3220            Self::InvalidFallback => {
3221                "fallback must advance to a distinct later model after at least one attempt"
3222            }
3223            Self::InvalidRevertSequence => "session revert sequence must be positive",
3224            Self::InvalidSessionPermissionOverlay => "session permission overlay is invalid",
3225            Self::InvalidSkillEvent => "skill event is invalid",
3226            Self::ZeroEventSequence => "event sequence must be positive",
3227            Self::InvalidSessionCreatedEnvelope => {
3228                "SessionCreated must be sequence 1 with no run_id"
3229            }
3230            Self::MissingRunId => "run-scoped event requires run_id",
3231        })
3232    }
3233}
3234impl std::error::Error for EventSchemaError {}