agent_sdk_core/records/structured_output.rs
1//! Durable and observable SDK records. Use these DTOs for events, journals, effects,
2//! context, output, and feature evidence. Constructing records is data-only;
3//! persistence, publication, and external actions happen through ports or application
4//! coordinators. This file contains the structured output portion of that contract.
5//!
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 domain::{
10 AttemptId, ContentRef, OutputSchemaId, PrivacyClass, RepairAttemptId, ValidationAttemptId,
11 },
12 output::{ContentHash, RepairAdapterRef, SchemaVersion},
13};
14
15/// Constant value for the records::structured_output contract. Use it
16/// to keep SDK records and tests aligned on the same stable value.
17pub const VALIDATION_RECORD_SCHEMA_VERSION: u16 = 1;
18/// Constant value for the records::structured_output contract. Use it
19/// to keep SDK records and tests aligned on the same stable value.
20pub const REPAIR_RECORD_SCHEMA_VERSION: u16 = 1;
21/// Constant value for the records::structured_output contract. Use it
22/// to keep SDK records and tests aligned on the same stable value.
23pub const STRUCTURED_OUTPUT_LIFECYCLE_RECORD_SCHEMA_VERSION: u16 = 1;
24
25#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
26/// Carries the structured output lifecycle record record payload for journal, event, or fixture surfaces.
27/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
28pub struct StructuredOutputLifecycleRecord {
29 /// Wire schema version for this record shape.
30 /// Use it for compatibility checks before deserializing or replaying stored data.
31 pub record_schema_version: u16,
32 /// Kind discriminator for record kind.
33 /// Use it to route finite match arms without parsing display text.
34 pub record_kind: StructuredOutputLifecycleKind,
35 /// Stable schema id used for typed lineage, lookup, or dedupe.
36 pub schema_id: OutputSchemaId,
37 /// Wire schema version for this record shape.
38 /// Use it for compatibility checks before deserializing or replaying stored data.
39 pub output_schema_version: SchemaVersion,
40 /// Deterministic schema fingerprint used for stale checks, package
41 /// evidence, or replay comparisons.
42 pub schema_fingerprint: ContentHash,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 /// Stable source attempt id used for typed lineage, lookup, or dedupe.
45 pub source_attempt_id: Option<AttemptId>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 /// Content reference for the candidate value being validated.
48 pub candidate_content_ref: Option<ContentRef>,
49 /// Privacy class used for projection, telemetry, and raw-content access
50 /// decisions.
51 pub privacy: PrivacyClass,
52 /// Redacted human-readable summary safe for events, telemetry, and logs.
53 pub redacted_summary: String,
54}
55
56impl StructuredOutputLifecycleRecord {
57 /// Builds the requested record or result value.
58 /// This is data-only and does not perform I/O, call host ports, append journals, publish
59 /// events, or start processes.
60 pub fn requested(
61 schema_id: OutputSchemaId,
62 output_schema_version: SchemaVersion,
63 schema_fingerprint: ContentHash,
64 ) -> Self {
65 Self {
66 record_schema_version: STRUCTURED_OUTPUT_LIFECYCLE_RECORD_SCHEMA_VERSION,
67 record_kind: StructuredOutputLifecycleKind::Requested,
68 schema_id,
69 output_schema_version,
70 schema_fingerprint,
71 source_attempt_id: None,
72 candidate_content_ref: None,
73 privacy: PrivacyClass::ContentRefsOnly,
74 redacted_summary: "structured output requested".to_string(),
75 }
76 }
77
78 /// Builds the validation started record or result value.
79 /// This is data-only and does not perform I/O, call host ports, append journals, publish
80 /// events, or start processes.
81 pub fn validation_started(
82 schema_id: OutputSchemaId,
83 output_schema_version: SchemaVersion,
84 schema_fingerprint: ContentHash,
85 source_attempt_id: AttemptId,
86 candidate_content_ref: ContentRef,
87 ) -> Self {
88 Self {
89 record_schema_version: STRUCTURED_OUTPUT_LIFECYCLE_RECORD_SCHEMA_VERSION,
90 record_kind: StructuredOutputLifecycleKind::ValidationStarted,
91 schema_id,
92 output_schema_version,
93 schema_fingerprint,
94 source_attempt_id: Some(source_attempt_id),
95 candidate_content_ref: Some(candidate_content_ref),
96 privacy: PrivacyClass::ContentRefsOnly,
97 redacted_summary: "structured output validation started".to_string(),
98 }
99 }
100}
101
102#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103#[serde(rename_all = "snake_case")]
104/// Enumerates the finite structured output lifecycle kind cases.
105/// Serialized names are part of the SDK contract; update fixtures when variants change.
106pub enum StructuredOutputLifecycleKind {
107 /// Use this variant when the contract needs to represent requested; selecting it has no side effect by itself.
108 Requested,
109 /// Use this variant when the contract needs to represent validation started; selecting it has no side effect by itself.
110 ValidationStarted,
111}
112
113#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
114/// Carries the validation record record payload for journal, event, or fixture surfaces.
115/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
116pub struct ValidationRecord {
117 /// Wire schema version for this record shape.
118 /// Use it for compatibility checks before deserializing or replaying stored data.
119 pub record_schema_version: u16,
120 /// Kind discriminator for record kind.
121 /// Use it to route finite match arms without parsing display text.
122 pub record_kind: ValidationRecordKind,
123 /// Stable schema id used for typed lineage, lookup, or dedupe.
124 pub schema_id: OutputSchemaId,
125 /// Wire schema version for this record shape.
126 /// Use it for compatibility checks before deserializing or replaying stored data.
127 pub output_schema_version: SchemaVersion,
128 /// Deterministic schema fingerprint used for stale checks, package
129 /// evidence, or replay comparisons.
130 pub schema_fingerprint: ContentHash,
131 /// Stable validation attempt id used for typed lineage, lookup, or
132 /// dedupe.
133 pub validation_attempt_id: ValidationAttemptId,
134 /// Stable source attempt id used for typed lineage, lookup, or dedupe.
135 pub source_attempt_id: AttemptId,
136 /// Content reference for the candidate value being validated.
137 pub candidate_content_ref: ContentRef,
138 /// Privacy class used for projection, telemetry, and raw-content access
139 /// decisions.
140 pub privacy: PrivacyClass,
141 /// Redacted human-readable summary safe for events, telemetry, and logs.
142 pub redacted_summary: String,
143 #[serde(default, skip_serializing_if = "Vec::is_empty")]
144 /// Bounded errors included in this record. Limits and truncation are
145 /// represented by companion metadata when applicable.
146 pub errors: Vec<ValidationErrorSummary>,
147 #[serde(default, skip_serializing_if = "Vec::is_empty")]
148 /// Validation policy applied before output is accepted as typed data.
149 /// It controls validator selection, bounds, failure visibility, and local validation
150 /// behavior.
151 pub validation_attempts: Vec<ValidationAttemptId>,
152 #[serde(default, skip_serializing_if = "Vec::is_empty")]
153 /// Attempt identifier or attempt history for bounded retry/repair.
154 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
155 pub repair_attempts: Vec<RepairAttemptId>,
156 #[serde(default, skip_serializing_if = "Vec::is_empty")]
157 /// Attempt identifier or attempt history for bounded retry/repair.
158 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
159 pub source_attempt_ids: Vec<AttemptId>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 /// Optional retry exhausted value.
162 /// When absent, callers should use the documented default or skip that optional behavior.
163 pub retry_exhausted: Option<bool>,
164}
165
166#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
167#[serde(rename_all = "snake_case")]
168/// Enumerates the finite validation record kind cases.
169/// Serialized names are part of the SDK contract; update fixtures when variants change.
170pub enum ValidationRecordKind {
171 /// Use this variant when the contract needs to represent validation succeeded; selecting it has no side effect by itself.
172 ValidationSucceeded,
173 /// Use this variant when the contract needs to represent validation failed; selecting it has no side effect by itself.
174 ValidationFailed,
175 /// Use this variant when the contract needs to represent schema rejected; selecting it has no side effect by itself.
176 SchemaRejected,
177 /// Use this variant when the contract needs to represent terminal failure; selecting it has no side effect by itself.
178 TerminalFailure,
179}
180
181#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
182/// Carries the validation error summary record payload for journal, event, or fixture surfaces.
183/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
184pub struct ValidationErrorSummary {
185 /// Code used by this record or request.
186 pub code: ValidationErrorCode,
187 /// Workspace-relative or resource path selected by the request or result.
188 pub path: String,
189 /// Redacted human-readable summary safe for events, telemetry, and logs.
190 pub redacted_summary: String,
191}
192
193impl ValidationErrorSummary {
194 /// Creates a new records::structured_output value with explicit
195 /// caller-provided inputs. This constructor is data-only and
196 /// performs no I/O or external side effects.
197 pub fn new(
198 code: ValidationErrorCode,
199 path: impl Into<String>,
200 redacted_summary: impl Into<String>,
201 ) -> Self {
202 Self {
203 code,
204 path: path.into(),
205 redacted_summary: redacted_summary.into(),
206 }
207 }
208}
209
210#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
211#[serde(rename_all = "snake_case")]
212/// Enumerates the finite validation error code cases.
213/// Serialized names are part of the SDK contract; update fixtures when variants change.
214pub enum ValidationErrorCode {
215 /// Use this variant when the contract needs to represent candidate too large; selecting it has no side effect by itself.
216 CandidateTooLarge,
217 /// Use this variant when the contract needs to represent invalid json; selecting it has no side effect by itself.
218 InvalidJson,
219 /// Use this variant when the contract needs to represent unsupported dialect; selecting it has no side effect by itself.
220 UnsupportedDialect,
221 /// Use this variant when the contract needs to represent unsupported schema ref; selecting it has no side effect by itself.
222 UnsupportedSchemaRef,
223 /// Use this variant when the contract needs to represent schema contract violation; selecting it has no side effect by itself.
224 SchemaContractViolation,
225 /// Use this variant when the contract needs to represent hostile schema; selecting it has no side effect by itself.
226 HostileSchema,
227 /// Use this variant when the contract needs to represent missing required field; selecting it has no side effect by itself.
228 MissingRequiredField,
229 /// Use this variant when the contract needs to represent type mismatch; selecting it has no side effect by itself.
230 TypeMismatch,
231 /// Use this variant when the contract needs to represent additional property denied; selecting it has no side effect by itself.
232 AdditionalPropertyDenied,
233 /// Use this variant when the contract needs to represent enum mismatch; selecting it has no side effect by itself.
234 EnumMismatch,
235 /// Use this variant when the contract needs to represent min length violation; selecting it has no side effect by itself.
236 MinLengthViolation,
237 /// Use this variant when the contract needs to represent max length violation; selecting it has no side effect by itself.
238 MaxLengthViolation,
239 /// Use this variant when the contract needs to represent semantic validator unavailable; selecting it has no side effect by itself.
240 SemanticValidatorUnavailable,
241}
242
243impl ValidationErrorCode {
244 /// Reports whether this value is schema rejection. The check is
245 /// pure and does not mutate SDK or host state.
246 pub(crate) fn is_schema_rejection(&self) -> bool {
247 matches!(
248 self,
249 Self::UnsupportedDialect
250 | Self::UnsupportedSchemaRef
251 | Self::SchemaContractViolation
252 | Self::HostileSchema
253 | Self::SemanticValidatorUnavailable
254 )
255 }
256}
257
258#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
259/// Carries the repair prompt record payload for journal, event, or fixture surfaces.
260/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
261pub struct RepairPrompt {
262 /// Wire schema version for this record shape.
263 /// Use it for compatibility checks before deserializing or replaying stored data.
264 pub record_schema_version: u16,
265 /// Stable repair attempt id used for typed lineage, lookup, or dedupe.
266 pub repair_attempt_id: RepairAttemptId,
267 /// Stable validation attempt id used for typed lineage, lookup, or
268 /// dedupe.
269 pub validation_attempt_id: ValidationAttemptId,
270 /// Stable source attempt id used for typed lineage, lookup, or dedupe.
271 pub source_attempt_id: AttemptId,
272 /// Stable schema id used for typed lineage, lookup, or dedupe.
273 pub schema_id: OutputSchemaId,
274 /// Wire schema version for this record shape.
275 /// Use it for compatibility checks before deserializing or replaying stored data.
276 pub output_schema_version: SchemaVersion,
277 /// Deterministic schema fingerprint used for stale checks, package
278 /// evidence, or replay comparisons.
279 pub schema_fingerprint: ContentHash,
280 /// Typed repair adapter ref reference. Resolving or executing it is a
281 /// separate policy-gated step.
282 pub repair_adapter_ref: RepairAdapterRef,
283 /// Attempt identifier or attempt history for bounded retry/repair.
284 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
285 pub attempt_index: u8,
286 /// Attempt identifier or attempt history for bounded retry/repair.
287 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
288 pub max_repair_attempts: u8,
289 /// Whether include schema in prompt is enabled.
290 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
291 pub include_schema_in_prompt: bool,
292 /// Collection of redacted errors values.
293 /// Ordering and membership should be treated as part of the serialized contract when
294 /// relevant.
295 pub redacted_errors: Vec<ValidationErrorSummary>,
296 /// Candidate content used by this record or request.
297 pub candidate_content: RepairPromptCandidateContent,
298 /// Redacted summary for display, logs, events, or telemetry.
299 /// It should describe the value without exposing raw private content.
300 pub prompt_summary: String,
301}
302
303#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
304#[serde(tag = "type", rename_all = "snake_case")]
305/// Enumerates the finite repair prompt candidate content cases.
306/// Serialized names are part of the SDK contract; update fixtures when variants change.
307pub enum RepairPromptCandidateContent {
308 /// Use this variant when the contract needs to represent content ref only; selecting it has no side effect by itself.
309 ContentRefOnly {
310 /// Content reference for the candidate value being validated.
311 candidate_content_ref: ContentRef,
312 },
313 /// Use this variant when the contract needs to represent redacted candidate; selecting it has no side effect by itself.
314 RedactedCandidate {
315 /// Redacted human-readable summary safe for events, telemetry, and
316 /// logs.
317 redacted_summary: String,
318 },
319 /// Use this variant when the contract needs to represent omitted; selecting it has no side effect by itself.
320 Omitted,
321}
322
323#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
324/// Carries the repair record record payload for journal, event, or fixture surfaces.
325/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
326pub struct RepairRecord {
327 /// Wire schema version for this record shape.
328 /// Use it for compatibility checks before deserializing or replaying stored data.
329 pub record_schema_version: u16,
330 /// Kind discriminator for record kind.
331 /// Use it to route finite match arms without parsing display text.
332 pub record_kind: RepairRecordKind,
333 /// Stable repair attempt id used for typed lineage, lookup, or dedupe.
334 pub repair_attempt_id: RepairAttemptId,
335 /// Stable validation attempt id used for typed lineage, lookup, or
336 /// dedupe.
337 pub validation_attempt_id: ValidationAttemptId,
338 /// Stable source attempt id used for typed lineage, lookup, or dedupe.
339 pub source_attempt_id: AttemptId,
340 /// Stable schema id used for typed lineage, lookup, or dedupe.
341 pub schema_id: OutputSchemaId,
342 /// Wire schema version for this record shape.
343 /// Use it for compatibility checks before deserializing or replaying stored data.
344 pub output_schema_version: SchemaVersion,
345 /// Deterministic schema fingerprint used for stale checks, package
346 /// evidence, or replay comparisons.
347 pub schema_fingerprint: ContentHash,
348 /// Typed repair adapter ref reference. Resolving or executing it is a
349 /// separate policy-gated step.
350 pub repair_adapter_ref: RepairAdapterRef,
351 /// Attempt identifier or attempt history for bounded retry/repair.
352 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
353 pub attempt_index: u8,
354 /// Attempt identifier or attempt history for bounded retry/repair.
355 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
356 pub max_repair_attempts: u8,
357 /// Prompt used by this record or request.
358 pub prompt: RepairPrompt,
359 /// Redacted human-readable summary safe for events, telemetry, and logs.
360 pub redacted_summary: String,
361 /// Privacy class used for projection, telemetry, and raw-content access
362 /// decisions.
363 pub privacy: PrivacyClass,
364}
365
366#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
367/// Carries the repair exhaustion record record payload for journal, event, or fixture surfaces.
368/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
369pub struct RepairExhaustionRecord {
370 /// Wire schema version for this record shape.
371 /// Use it for compatibility checks before deserializing or replaying stored data.
372 pub record_schema_version: u16,
373 /// Kind discriminator for record kind.
374 /// Use it to route finite match arms without parsing display text.
375 pub record_kind: RepairRecordKind,
376 /// Stable schema id used for typed lineage, lookup, or dedupe.
377 pub schema_id: OutputSchemaId,
378 /// Wire schema version for this record shape.
379 /// Use it for compatibility checks before deserializing or replaying stored data.
380 pub output_schema_version: SchemaVersion,
381 /// Validation policy applied before output is accepted as typed data.
382 /// It controls validator selection, bounds, failure visibility, and local validation
383 /// behavior.
384 pub validation_attempts: Vec<ValidationAttemptId>,
385 /// Attempt identifier or attempt history for bounded retry/repair.
386 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
387 pub repair_attempts: Vec<RepairAttemptId>,
388 /// Attempt identifier or attempt history for bounded retry/repair.
389 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
390 pub source_attempt_ids: Vec<AttemptId>,
391 /// Content reference for the candidate value being validated.
392 pub candidate_content_ref: ContentRef,
393 /// Whether retry exhausted is enabled.
394 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
395 pub retry_exhausted: bool,
396 /// Redacted human-readable summary safe for events, telemetry, and logs.
397 pub redacted_summary: String,
398 /// Redacted explanation for a denial, failure, status, or package delta.
399 pub reason: String,
400 /// Privacy class used for projection, telemetry, and raw-content access
401 /// decisions.
402 pub privacy: PrivacyClass,
403}
404
405#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
406#[serde(rename_all = "snake_case")]
407/// Enumerates the finite repair record kind cases.
408/// Serialized names are part of the SDK contract; update fixtures when variants change.
409pub enum RepairRecordKind {
410 /// Use this variant when the contract needs to represent repair requested; selecting it has no side effect by itself.
411 RepairRequested,
412 /// Use this variant when the contract needs to represent repair exhausted; selecting it has no side effect by itself.
413 RepairExhausted,
414}