agent_sdk_core/records/output_delivery.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 output delivery portion of that contract.
5//!
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::{
10 domain::{
11 AgentId, AttemptId, ContentRef, DedupeKey, DestinationRef, EffectId, EntityKind, EntityRef,
12 IdempotencyKey, MessageId, PolicyRef, PrivacyClass, RetentionClass, RunId, SourceRef,
13 TurnId, ValidatedOutputId,
14 },
15 effect::{EffectIntent, EffectKind, EffectResult, EffectTerminalStatus},
16 error::RetryClassification,
17 journal::{
18 EventIndexProjection, JOURNAL_SCHEMA_VERSION, JournalRecord, JournalRecordKind,
19 JournalRecordPayload,
20 },
21 package::RuntimePackageFingerprint,
22};
23
24#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
25#[serde(transparent)]
26/// Carries the output delivery id 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 OutputDeliveryId(String);
29
30impl OutputDeliveryId {
31 /// Creates a new records::output_delivery value with explicit
32 /// caller-provided inputs. This constructor is data-only and
33 /// performs no I/O or external side effects.
34 pub fn new(value: impl Into<String>) -> Self {
35 let value = value.into();
36 assert!(!value.is_empty(), "OutputDeliveryId must not be empty");
37 Self(value)
38 }
39
40 /// Returns this value as str. The accessor is side-effect free and
41 /// keeps ownership with the caller.
42 pub fn as_str(&self) -> &str {
43 &self.0
44 }
45}
46
47#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
48#[serde(transparent)]
49/// Carries the output sink ref record payload for journal, event, or fixture surfaces.
50/// 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.
51pub struct OutputSinkRef(String);
52
53impl OutputSinkRef {
54 /// Creates a new records::output_delivery value with explicit
55 /// caller-provided inputs. This constructor is data-only and
56 /// performs no I/O or external side effects.
57 pub fn new(value: impl Into<String>) -> Self {
58 let value = value.into();
59 assert!(!value.is_empty(), "OutputSinkRef must not be empty");
60 Self(value)
61 }
62
63 /// Returns this value as str. The accessor is side-effect free and
64 /// keeps ownership with the caller.
65 pub fn as_str(&self) -> &str {
66 &self.0
67 }
68}
69
70#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
71#[serde(rename_all = "snake_case")]
72/// Enumerates the finite output delivery requirement cases.
73/// Serialized names are part of the SDK contract; update fixtures when variants change.
74pub enum OutputDeliveryRequirement {
75 /// Use this variant when the contract needs to represent disabled; selecting it has no side effect by itself.
76 Disabled,
77 /// Use this variant when the contract needs to represent optional; selecting it has no side effect by itself.
78 Optional,
79 /// Use this variant when the contract needs to represent required; selecting it has no side effect by itself.
80 Required,
81}
82
83#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
84#[serde(tag = "type", rename_all = "snake_case")]
85/// Enumerates the finite output delivery kind cases.
86/// Serialized names are part of the SDK contract; update fixtures when variants change.
87pub enum OutputDeliveryKind {
88 /// Use this variant when the contract needs to represent stream chunk; selecting it has no side effect by itself.
89 StreamChunk {
90 /// Cursor identifying a replay, export, or subscription position.
91 /// Use it to resume without widening the original scope.
92 stream_cursor: String,
93 /// Chunk index used by this record or request.
94 chunk_index: u64,
95 },
96 /// Use this variant when the contract needs to represent final message; selecting it has no side effect by itself.
97 FinalMessage,
98 /// Use this variant when the contract needs to represent final validated output; selecting it has no side effect by itself.
99 FinalValidatedOutput,
100}
101
102impl OutputDeliveryKind {
103 /// Reports whether this value is chunk. The check is pure and does
104 /// not mutate SDK or host state.
105 pub fn is_chunk(&self) -> bool {
106 matches!(self, Self::StreamChunk { .. })
107 }
108
109 /// Returns dedupe fragment derived from the supplied state.
110 /// This is data-only and does not perform I/O, call host ports, append journals, publish
111 /// events, or start processes.
112 pub(crate) fn dedupe_fragment(&self) -> String {
113 match self {
114 Self::StreamChunk {
115 stream_cursor,
116 chunk_index,
117 } => format!("stream:{stream_cursor}:{chunk_index}"),
118 Self::FinalMessage => "final:message".to_string(),
119 Self::FinalValidatedOutput => "final:validated_output".to_string(),
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
125#[serde(rename_all = "snake_case")]
126/// Enumerates the finite output content mode cases.
127/// Serialized names are part of the SDK contract; update fixtures when variants change.
128pub enum OutputContentMode {
129 /// Use this variant when the contract needs to represent content refs only; selecting it has no side effect by itself.
130 ContentRefsOnly,
131 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
132 RedactedSummary,
133 /// Use this variant when the contract needs to represent raw content if policy allows; selecting it has no side effect by itself.
134 RawContentIfPolicyAllows,
135}
136
137#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
138/// Carries the output delivery policy record payload for journal, event, or fixture surfaces.
139/// 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.
140pub struct OutputDeliveryPolicy {
141 /// Policy reference that must be resolved by the host or runtime before
142 /// execution.
143 pub policy_ref: PolicyRef,
144 /// Requirement used by this record or request.
145 pub requirement: OutputDeliveryRequirement,
146 /// Default content mode used by this record or request.
147 pub default_content_mode: OutputContentMode,
148 #[serde(default)]
149 /// Allowlist for this policy or contract.
150 /// Validation uses it to reject undeclared or policy-denied values.
151 pub allowed_content_modes: Vec<OutputContentMode>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 /// Typed required sink ref reference. Resolving or executing it is a
154 /// separate policy-gated step.
155 pub required_sink_ref: Option<OutputSinkRef>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 /// Typed retry policy ref reference. Resolving or executing it is a
158 /// separate policy-gated step.
159 pub retry_policy_ref: Option<PolicyRef>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 /// Typed reconciliation policy ref reference. Resolving or executing it
162 /// is a separate policy-gated step.
163 pub reconciliation_policy_ref: Option<PolicyRef>,
164 /// Raw content or raw-content control for this value.
165 /// Use it only when policy explicitly allows raw content capture or delivery.
166 pub raw_content_policy: RawOutputContentPolicy,
167}
168
169impl OutputDeliveryPolicy {
170 /// Returns an updated value with required configured.
171 /// This is data-only and does not perform I/O, call host ports, append journals, publish
172 /// events, or start processes.
173 pub fn required(policy_ref: PolicyRef, sink_ref: OutputSinkRef) -> Self {
174 Self {
175 policy_ref,
176 requirement: OutputDeliveryRequirement::Required,
177 default_content_mode: OutputContentMode::ContentRefsOnly,
178 allowed_content_modes: vec![
179 OutputContentMode::ContentRefsOnly,
180 OutputContentMode::RedactedSummary,
181 ],
182 required_sink_ref: Some(sink_ref),
183 retry_policy_ref: None,
184 reconciliation_policy_ref: None,
185 raw_content_policy: RawOutputContentPolicy::deny(),
186 }
187 }
188
189 /// Returns an updated value with optional configured.
190 /// This is data-only and does not perform I/O, call host ports, append journals, publish
191 /// events, or start processes.
192 pub fn optional(policy_ref: PolicyRef) -> Self {
193 Self {
194 policy_ref,
195 requirement: OutputDeliveryRequirement::Optional,
196 default_content_mode: OutputContentMode::RedactedSummary,
197 allowed_content_modes: vec![OutputContentMode::RedactedSummary],
198 required_sink_ref: None,
199 retry_policy_ref: None,
200 reconciliation_policy_ref: None,
201 raw_content_policy: RawOutputContentPolicy::deny(),
202 }
203 }
204
205 /// Returns an updated value with disabled configured.
206 /// This is data-only and does not perform I/O, call host ports, append journals, publish
207 /// events, or start processes.
208 pub fn disabled(policy_ref: PolicyRef) -> Self {
209 Self {
210 policy_ref,
211 requirement: OutputDeliveryRequirement::Disabled,
212 default_content_mode: OutputContentMode::ContentRefsOnly,
213 allowed_content_modes: Vec::new(),
214 required_sink_ref: None,
215 retry_policy_ref: None,
216 reconciliation_policy_ref: None,
217 raw_content_policy: RawOutputContentPolicy::deny(),
218 }
219 }
220
221 /// Returns whether allows mode applies for this contract.
222 /// This is data-only and does not perform I/O, call host ports, append journals, publish
223 /// events, or start processes.
224 pub fn allows_mode(&self, mode: OutputContentMode) -> bool {
225 self.allowed_content_modes.contains(&mode)
226 }
227
228 /// Returns policy refs for callers that need to inspect the contract state.
229 /// This is data-only and does not perform I/O, call host ports, append journals, publish
230 /// events, or start processes.
231 pub fn policy_refs(&self) -> Vec<PolicyRef> {
232 let mut refs = vec![self.policy_ref.clone()];
233 if let Some(policy_ref) = &self.retry_policy_ref {
234 refs.push(policy_ref.clone());
235 }
236 if let Some(policy_ref) = &self.reconciliation_policy_ref {
237 refs.push(policy_ref.clone());
238 }
239 refs
240 }
241}
242
243#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
244/// Carries the raw output content policy record payload for journal, event, or fixture surfaces.
245/// 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.
246pub struct RawOutputContentPolicy {
247 /// Policy reference that must be resolved by the host or runtime before
248 /// execution.
249 pub policy_ref: PolicyRef,
250 /// Boolean policy/capability flag for whether allow raw content is
251 /// enabled.
252 pub allow_raw_content: bool,
253 /// Retention class for referenced content or records.
254 /// Stores and telemetry sinks use it to decide how long evidence may be kept.
255 pub retention_named: bool,
256 /// Whether redaction policy named is enabled.
257 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
258 pub redaction_policy_named: bool,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 /// Typed allowed sink ref reference. Resolving or executing it is a
261 /// separate policy-gated step.
262 pub allowed_sink_ref: Option<OutputSinkRef>,
263 /// Byte size or byte limit for byte limit.
264 /// Use it to enforce bounded reads, writes, summaries, or parser output.
265 pub byte_limit: u64,
266}
267
268impl RawOutputContentPolicy {
269 /// Returns an updated records::output_delivery value with deny applied.
270 /// This is data construction only and does not execute the configured
271 /// behavior.
272 pub fn deny() -> Self {
273 Self {
274 policy_ref: PolicyRef::new("policy.output_delivery.raw.deny"),
275 allow_raw_content: false,
276 retention_named: true,
277 redaction_policy_named: true,
278 allowed_sink_ref: None,
279 byte_limit: 0,
280 }
281 }
282
283 /// Returns an updated value with allow for sink configured.
284 /// This is data-only and does not perform I/O, call host ports, append journals, publish
285 /// events, or start processes.
286 pub fn allow_for_sink(policy_ref: PolicyRef, sink_ref: OutputSinkRef, byte_limit: u64) -> Self {
287 Self {
288 policy_ref,
289 allow_raw_content: true,
290 retention_named: true,
291 redaction_policy_named: true,
292 allowed_sink_ref: Some(sink_ref),
293 byte_limit,
294 }
295 }
296
297 /// Returns whether allows raw for applies for this contract.
298 /// This is data-only and does not perform I/O, call host ports, append journals, publish
299 /// events, or start processes.
300 pub fn allows_raw_for(&self, sink_ref: &OutputSinkRef, byte_len: usize) -> bool {
301 self.allow_raw_content
302 && self.retention_named
303 && self.redaction_policy_named
304 && self
305 .allowed_sink_ref
306 .as_ref()
307 .is_some_and(|allowed| allowed == sink_ref)
308 && self.byte_limit >= byte_len as u64
309 }
310}
311
312#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
313/// Carries the output delivery request record payload for journal, event, or fixture surfaces.
314/// 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.
315pub struct OutputDeliveryRequest {
316 /// Stable delivery id used for typed lineage, lookup, or dedupe.
317 pub delivery_id: OutputDeliveryId,
318 /// Stable effect id used for typed lineage, lookup, or dedupe.
319 pub effect_id: EffectId,
320 /// Run identifier used for lineage, filtering, replay, and dedupe.
321 pub run_id: RunId,
322 /// Agent identifier used for lineage, filtering, and ownership checks.
323 pub agent_id: AgentId,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 /// Turn identifier for one loop turn within a run.
326 pub turn_id: Option<TurnId>,
327 #[serde(skip_serializing_if = "Option::is_none")]
328 /// Attempt identifier for retry, repair, provider, or tool execution
329 /// evidence.
330 pub attempt_id: Option<AttemptId>,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 /// Stable source message id used for typed lineage, lookup, or dedupe.
333 pub source_message_id: Option<MessageId>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 /// Stable validated output id used for typed lineage, lookup, or dedupe.
336 pub validated_output_id: Option<ValidatedOutputId>,
337 /// Destination label or ref for this item; it is metadata and does not
338 /// deliver content by itself.
339 pub destination: DestinationRef,
340 /// Typed sink ref reference. Resolving or executing it is a separate
341 /// policy-gated step.
342 pub sink_ref: OutputSinkRef,
343 /// Output delivery setting or policy.
344 /// Delivery coordinators use it to decide sink mode, dedupe, and required evidence.
345 pub delivery_kind: OutputDeliveryKind,
346 /// Content mode used by this record or request.
347 pub content_mode: OutputContentMode,
348 #[serde(default, skip_serializing_if = "Vec::is_empty")]
349 /// Content references associated with this record; resolving them is a
350 /// separate policy-gated step.
351 pub content_refs: Vec<ContentRef>,
352 /// Redacted human-readable summary safe for events, telemetry, and logs.
353 pub redacted_summary: String,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 /// Raw content or raw-content control for this value.
356 /// Use it only when policy explicitly allows raw content capture or delivery.
357 pub raw_content: Option<String>,
358 /// Privacy class used for projection, telemetry, and raw-content access
359 /// decisions.
360 pub privacy: PrivacyClass,
361 /// Retention class used by hosts and sinks when storing or exporting this
362 /// item.
363 pub retention: RetentionClass,
364 #[serde(default, skip_serializing_if = "Vec::is_empty")]
365 /// Policy references that govern admission, projection, execution, or
366 /// delivery.
367 pub policy_refs: Vec<PolicyRef>,
368 #[serde(skip_serializing_if = "Option::is_none")]
369 /// Idempotency setting or key for deduping retries.
370 /// Use it to prevent duplicate side effects during replay or repair.
371 pub idempotency_key: Option<IdempotencyKey>,
372 /// Dedupe policy or key for a side-effecting operation.
373 /// Replay and repair use it to avoid sending or executing the same effect twice.
374 pub dedupe_key: DedupeKey,
375 /// Fingerprint of the runtime package snapshot in force when this value was produced.
376 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
377 /// not execute package behavior.
378 pub runtime_package_fingerprint: RuntimePackageFingerprint,
379}
380
381impl OutputDeliveryRequest {
382 /// Returns effect intent derived from the supplied state.
383 /// This is data-only and does not perform I/O, call host ports, append journals, publish
384 /// events, or start processes.
385 pub fn effect_intent(&self) -> EffectIntent {
386 let mut intent = EffectIntent::new(
387 self.effect_id.clone(),
388 EffectKind::OutputDelivery,
389 EntityRef::new(EntityKind::OutputDelivery, self.delivery_id.as_str()),
390 SourceRef::with_kind(crate::domain::SourceKind::Sdk, "source.sdk.output_delivery"),
391 self.redacted_summary.clone(),
392 );
393 intent.destination = Some(self.destination.clone());
394 intent.policy_refs = self.policy_refs.clone();
395 intent.idempotency_key = self.idempotency_key.clone();
396 intent.dedupe_key = Some(self.dedupe_key.clone());
397 intent.content_refs = self.content_refs.clone();
398 intent
399 }
400
401 /// Returns whether carries raw content applies for this contract.
402 /// This is data-only and does not perform I/O, call host ports, append journals, publish
403 /// events, or start processes.
404 pub fn carries_raw_content(&self) -> bool {
405 self.raw_content.is_some()
406 }
407}
408
409#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
410/// Carries the output delivery receipt record payload for journal, event, or fixture surfaces.
411/// 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.
412pub struct OutputDeliveryReceipt {
413 /// Stable delivery id used for typed lineage, lookup, or dedupe.
414 pub delivery_id: OutputDeliveryId,
415 /// Finite status for this record or lifecycle stage.
416 pub status: OutputDispatchStatus,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 /// Typed ack ref reference. Resolving or executing it is a separate
419 /// policy-gated step.
420 pub ack_ref: Option<String>,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 /// Cursor identifying a replay, export, or subscription position.
423 /// Use it to resume without widening the original scope.
424 pub destination_cursor: Option<String>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 /// Stable external operation id used for typed lineage, lookup, or
427 /// dedupe.
428 pub external_operation_id: Option<String>,
429 #[serde(skip_serializing_if = "Option::is_none")]
430 /// Typed reconciliation ref reference. Resolving or executing it is a
431 /// separate policy-gated step.
432 pub reconciliation_ref: Option<String>,
433 /// Redacted human-readable summary safe for events, telemetry, and logs.
434 pub redacted_summary: String,
435}
436
437impl OutputDeliveryReceipt {
438 /// Returns an updated value with completed configured.
439 /// This is data-only and does not perform I/O, call host ports, append journals, publish
440 /// events, or start processes.
441 pub fn completed(delivery_id: OutputDeliveryId, ack_ref: impl Into<String>) -> Self {
442 Self {
443 delivery_id,
444 status: OutputDispatchStatus::Completed,
445 ack_ref: Some(ack_ref.into()),
446 destination_cursor: None,
447 external_operation_id: None,
448 reconciliation_ref: None,
449 redacted_summary: "output delivery completed".to_string(),
450 }
451 }
452
453 /// Builds the unknown record or result value.
454 /// This is data-only and does not perform I/O, call host ports, append journals, publish
455 /// events, or start processes.
456 pub fn unknown(delivery_id: OutputDeliveryId, reconciliation_ref: impl Into<String>) -> Self {
457 Self {
458 delivery_id,
459 status: OutputDispatchStatus::ReconciliationNeeded,
460 ack_ref: None,
461 destination_cursor: None,
462 external_operation_id: None,
463 reconciliation_ref: Some(reconciliation_ref.into()),
464 redacted_summary: "output delivery outcome unknown".to_string(),
465 }
466 }
467}
468
469#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
470#[serde(rename_all = "snake_case")]
471/// Enumerates the finite output dispatch status cases.
472/// Serialized names are part of the SDK contract; update fixtures when variants change.
473pub enum OutputDispatchStatus {
474 /// Use this variant when the contract needs to represent requested; selecting it has no side effect by itself.
475 Requested,
476 /// Use this variant when the contract needs to represent completed; selecting it has no side effect by itself.
477 Completed,
478 /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
479 Failed,
480 /// Use this variant when the contract needs to represent deduped; selecting it has no side effect by itself.
481 Deduped,
482 /// Use this variant when the contract needs to represent host configuration needed; selecting it has no side effect by itself.
483 HostConfigurationNeeded,
484 /// Use this variant when the contract needs to represent policy denied; selecting it has no side effect by itself.
485 PolicyDenied,
486 /// Use this variant when the contract needs to represent skipped optional; selecting it has no side effect by itself.
487 SkippedOptional,
488 /// Use this variant when the contract needs to represent reconciliation needed; selecting it has no side effect by itself.
489 ReconciliationNeeded,
490}
491
492#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
493#[serde(rename_all = "snake_case")]
494/// Enumerates the finite output delivery event kind cases.
495/// Serialized names are part of the SDK contract; update fixtures when variants change.
496pub enum OutputDeliveryEventKind {
497 /// Use this variant when the contract needs to represent output dispatch requested; selecting it has no side effect by itself.
498 OutputDispatchRequested,
499 /// Use this variant when the contract needs to represent output dispatch completed; selecting it has no side effect by itself.
500 OutputDispatchCompleted,
501 /// Use this variant when the contract needs to represent output dispatch failed; selecting it has no side effect by itself.
502 OutputDispatchFailed,
503 /// Use this variant when the contract needs to represent output dispatch deduped; selecting it has no side effect by itself.
504 OutputDispatchDeduped,
505}
506
507#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
508/// Carries the output delivery intent record record payload for journal, event, or fixture surfaces.
509/// 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.
510pub struct OutputDeliveryIntentRecord {
511 /// Stable delivery id used for typed lineage, lookup, or dedupe.
512 pub delivery_id: OutputDeliveryId,
513 /// Effect intent used by this record or request.
514 pub effect_intent: EffectIntent,
515 /// Destination label or ref for this item; it is metadata and does not
516 /// deliver content by itself.
517 pub destination: DestinationRef,
518 /// Typed sink ref reference. Resolving or executing it is a separate
519 /// policy-gated step.
520 pub sink_ref: OutputSinkRef,
521 /// Typed desired sink ref reference. Resolving or executing it is a
522 /// separate policy-gated step.
523 pub desired_sink_ref: OutputSinkRef,
524 /// Output delivery setting or policy.
525 /// Delivery coordinators use it to decide sink mode, dedupe, and required evidence.
526 pub delivery_kind: OutputDeliveryKind,
527 /// Content mode used by this record or request.
528 pub content_mode: OutputContentMode,
529 #[serde(default, skip_serializing_if = "Vec::is_empty")]
530 /// Content references associated with this record; resolving them is a
531 /// separate policy-gated step.
532 pub content_refs: Vec<ContentRef>,
533 /// Redacted human-readable summary safe for events, telemetry, and logs.
534 pub redacted_summary: String,
535 /// Privacy class used for projection, telemetry, and raw-content access
536 /// decisions.
537 pub privacy: PrivacyClass,
538 /// Retention class used by hosts and sinks when storing or exporting this
539 /// item.
540 pub retention: RetentionClass,
541 #[serde(default, skip_serializing_if = "Vec::is_empty")]
542 /// Policy references that govern admission, projection, execution, or
543 /// delivery.
544 pub policy_refs: Vec<PolicyRef>,
545 #[serde(skip_serializing_if = "Option::is_none")]
546 /// Idempotency setting or key for deduping retries.
547 /// Use it to prevent duplicate side effects during replay or repair.
548 pub idempotency_key: Option<IdempotencyKey>,
549 /// Dedupe policy or key for a side-effecting operation.
550 /// Replay and repair use it to avoid sending or executing the same effect twice.
551 pub dedupe_key: DedupeKey,
552 /// Fingerprint of the runtime package snapshot in force when this value was produced.
553 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
554 /// not execute package behavior.
555 pub runtime_package_fingerprint: RuntimePackageFingerprint,
556}
557
558impl OutputDeliveryIntentRecord {
559 /// Constructs this value from request. Use it when adapting
560 /// canonical SDK records without introducing a second behavior
561 /// path.
562 pub fn from_request(request: &OutputDeliveryRequest) -> Self {
563 Self {
564 delivery_id: request.delivery_id.clone(),
565 effect_intent: request.effect_intent(),
566 destination: request.destination.clone(),
567 sink_ref: request.sink_ref.clone(),
568 desired_sink_ref: request.sink_ref.clone(),
569 delivery_kind: request.delivery_kind.clone(),
570 content_mode: request.content_mode,
571 content_refs: request.content_refs.clone(),
572 redacted_summary: request.redacted_summary.clone(),
573 privacy: request.privacy,
574 retention: request.retention,
575 policy_refs: request.policy_refs.clone(),
576 idempotency_key: request.idempotency_key.clone(),
577 dedupe_key: request.dedupe_key.clone(),
578 runtime_package_fingerprint: request.runtime_package_fingerprint.clone(),
579 }
580 }
581
582 /// Converts this value into journal record data.
583 /// This is data-only and does not perform I/O, call host ports, append journals, publish
584 /// events, or start processes.
585 pub fn to_journal_record(&self, base: OutputDeliveryJournalBase) -> JournalRecord {
586 output_delivery_effect_record(
587 base,
588 JournalRecordKind::OutputDispatch,
589 "output_dispatch_requested",
590 Some(self.idempotency_key.clone()).flatten(),
591 Some(self.dedupe_key.clone()),
592 self.effect_intent.content_refs.clone(),
593 JournalRecordPayload::OutputDelivery(OutputDeliveryRecord::Intent(self.clone())),
594 EntityRef::new(EntityKind::OutputDelivery, self.delivery_id.as_str()),
595 self.destination.clone(),
596 self.policy_refs.clone(),
597 self.privacy,
598 )
599 }
600}
601
602#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603/// Carries the output delivery result record record payload for journal, event, or fixture surfaces.
604/// 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.
605pub struct OutputDeliveryResultRecord {
606 /// Stable delivery id used for typed lineage, lookup, or dedupe.
607 pub delivery_id: OutputDeliveryId,
608 /// Effect result used by this record or request.
609 pub effect_result: EffectResult,
610 /// Destination label or ref for this item; it is metadata and does not
611 /// deliver content by itself.
612 pub destination: DestinationRef,
613 /// Typed sink ref reference. Resolving or executing it is a separate
614 /// policy-gated step.
615 pub sink_ref: OutputSinkRef,
616 /// Dispatch status used by this record or request.
617 pub dispatch_status: OutputDispatchStatus,
618 #[serde(skip_serializing_if = "Option::is_none")]
619 /// Typed ack ref reference. Resolving or executing it is a separate
620 /// policy-gated step.
621 pub ack_ref: Option<String>,
622 #[serde(skip_serializing_if = "Option::is_none")]
623 /// Stable external operation id used for typed lineage, lookup, or
624 /// dedupe.
625 pub external_operation_id: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
627 /// Typed error ref reference. Resolving or executing it is a separate
628 /// policy-gated step.
629 pub error_ref: Option<String>,
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
631 /// Content references associated with this record; resolving them is a
632 /// separate policy-gated step.
633 pub content_refs: Vec<ContentRef>,
634 /// Redacted human-readable summary safe for events, telemetry, and logs.
635 pub redacted_summary: String,
636 /// Retry classification used by this record or request.
637 pub retry_classification: RetryClassification,
638}
639
640impl OutputDeliveryResultRecord {
641 /// Returns an updated value with completed configured.
642 /// This is data-only and does not perform I/O, call host ports, append journals, publish
643 /// events, or start processes.
644 pub fn completed(request: &OutputDeliveryRequest, receipt: &OutputDeliveryReceipt) -> Self {
645 Self::from_status(
646 request,
647 OutputDispatchStatus::Completed,
648 EffectTerminalStatus::Completed,
649 receipt.ack_ref.clone(),
650 receipt.external_operation_id.clone(),
651 None,
652 None,
653 RetryClassification::NotRetryable,
654 receipt.redacted_summary.clone(),
655 )
656 }
657
658 /// Returns an updated value with failed configured.
659 /// This is data-only and does not perform I/O, call host ports, append journals, publish
660 /// events, or start processes.
661 pub fn failed(
662 request: &OutputDeliveryRequest,
663 status: OutputDispatchStatus,
664 error_ref: impl Into<String>,
665 retry_classification: RetryClassification,
666 ) -> Self {
667 Self::from_status(
668 request,
669 status,
670 EffectTerminalStatus::Failed,
671 None,
672 None,
673 None,
674 Some(error_ref.into()),
675 retry_classification,
676 "output delivery failed before host send or at sink boundary",
677 )
678 }
679
680 /// Builds the reconciliation needed record or result value.
681 /// This is data-only and does not perform I/O, call host ports, append journals, publish
682 /// events, or start processes.
683 pub fn reconciliation_needed(
684 request: &OutputDeliveryRequest,
685 receipt: &OutputDeliveryReceipt,
686 ) -> Self {
687 Self::from_status(
688 request,
689 OutputDispatchStatus::ReconciliationNeeded,
690 EffectTerminalStatus::Unknown,
691 receipt.ack_ref.clone(),
692 receipt.external_operation_id.clone(),
693 receipt.reconciliation_ref.clone(),
694 None,
695 RetryClassification::RepairNeeded,
696 receipt.redacted_summary.clone(),
697 )
698 }
699
700 #[expect(
701 clippy::too_many_arguments,
702 reason = "status projection mirrors output-delivery effect fields; grouping belongs with a dedicated result-builder API"
703 )]
704 fn from_status(
705 request: &OutputDeliveryRequest,
706 dispatch_status: OutputDispatchStatus,
707 terminal_status: EffectTerminalStatus,
708 ack_ref: Option<String>,
709 external_operation_id: Option<String>,
710 reconciliation_ref: Option<String>,
711 error_ref: Option<String>,
712 retry_classification: RetryClassification,
713 redacted_summary: impl Into<String>,
714 ) -> Self {
715 let redacted_summary = redacted_summary.into();
716 Self {
717 delivery_id: request.delivery_id.clone(),
718 effect_result: EffectResult {
719 effect_id: request.effect_id.clone(),
720 terminal_status,
721 external_operation_id: external_operation_id.clone(),
722 reconciliation_ref,
723 error_ref: error_ref.clone(),
724 content_refs: request.content_refs.clone(),
725 redacted_summary: redacted_summary.clone(),
726 },
727 destination: request.destination.clone(),
728 sink_ref: request.sink_ref.clone(),
729 dispatch_status,
730 ack_ref,
731 external_operation_id,
732 error_ref,
733 content_refs: request.content_refs.clone(),
734 redacted_summary,
735 retry_classification,
736 }
737 }
738
739 /// Converts this value into journal record data.
740 /// This is data-only and does not perform I/O, call host ports, append journals, publish
741 /// events, or start processes.
742 pub fn to_journal_record(&self, base: OutputDeliveryJournalBase) -> JournalRecord {
743 let event_kind = match self.dispatch_status {
744 OutputDispatchStatus::Completed => "output_dispatch_completed",
745 OutputDispatchStatus::HostConfigurationNeeded
746 | OutputDispatchStatus::PolicyDenied
747 | OutputDispatchStatus::Failed => "output_dispatch_failed",
748 OutputDispatchStatus::ReconciliationNeeded => "output_dispatch_reconciliation_needed",
749 OutputDispatchStatus::Deduped => "output_dispatch_deduped",
750 OutputDispatchStatus::Requested | OutputDispatchStatus::SkippedOptional => {
751 "output_dispatch_status"
752 }
753 };
754 output_delivery_effect_record(
755 base,
756 JournalRecordKind::OutputDispatch,
757 event_kind,
758 None,
759 None,
760 self.effect_result.content_refs.clone(),
761 JournalRecordPayload::OutputDelivery(OutputDeliveryRecord::Result(self.clone())),
762 EntityRef::new(EntityKind::OutputDelivery, self.delivery_id.as_str()),
763 self.destination.clone(),
764 Vec::new(),
765 PrivacyClass::ContentRefsOnly,
766 )
767 }
768}
769
770#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
771/// Carries the output delivery dedupe record record payload for journal, event, or fixture surfaces.
772/// 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.
773pub struct OutputDeliveryDedupeRecord {
774 /// Stable delivery id used for typed lineage, lookup, or dedupe.
775 pub delivery_id: OutputDeliveryId,
776 /// Dedupe policy or key for a side-effecting operation.
777 /// Replay and repair use it to avoid sending or executing the same effect twice.
778 pub dedupe_key: DedupeKey,
779 #[serde(skip_serializing_if = "Option::is_none")]
780 /// Stable prior delivery id used for typed lineage, lookup, or dedupe.
781 pub prior_delivery_id: Option<OutputDeliveryId>,
782 #[serde(skip_serializing_if = "Option::is_none")]
783 /// External sink operation id from a previous delivery attempt, when known.
784 /// Reconciliation uses it to avoid duplicate sends and to connect repaired evidence to the
785 /// prior external operation.
786 pub prior_external_operation_id: Option<String>,
787 /// Prior terminal status used by this record or request.
788 pub prior_terminal_status: OutputDispatchStatus,
789 /// Current status used by this record or request.
790 pub current_status: OutputDispatchStatus,
791 /// Redacted human-readable summary safe for events, telemetry, and logs.
792 pub redacted_summary: String,
793 #[serde(default, skip_serializing_if = "Vec::is_empty")]
794 /// Policy references that govern admission, projection, execution, or
795 /// delivery.
796 pub policy_refs: Vec<PolicyRef>,
797}
798
799impl OutputDeliveryDedupeRecord {
800 /// Converts this value into journal record data.
801 /// This is data-only and does not perform I/O, call host ports, append journals, publish
802 /// events, or start processes.
803 pub fn to_journal_record(
804 &self,
805 base: OutputDeliveryJournalBase,
806 destination: DestinationRef,
807 ) -> JournalRecord {
808 output_delivery_effect_record(
809 base,
810 JournalRecordKind::OutputDispatch,
811 "output_dispatch_deduped",
812 None,
813 Some(self.dedupe_key.clone()),
814 Vec::new(),
815 JournalRecordPayload::OutputDelivery(OutputDeliveryRecord::Dedupe(self.clone())),
816 EntityRef::new(EntityKind::OutputDelivery, self.delivery_id.as_str()),
817 destination,
818 self.policy_refs.clone(),
819 PrivacyClass::ContentRefsOnly,
820 )
821 }
822}
823
824#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
825/// Carries the output delivery reconciliation record record payload for journal, event, or fixture surfaces.
826/// 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.
827pub struct OutputDeliveryReconciliationRecord {
828 /// Stable delivery id used for typed lineage, lookup, or dedupe.
829 pub delivery_id: OutputDeliveryId,
830 /// Stable intent record id used for typed lineage, lookup, or dedupe.
831 pub intent_record_id: String,
832 /// Kind discriminator for side effect kind.
833 /// Use it to route finite match arms without parsing display text.
834 pub side_effect_kind: EffectKind,
835 #[serde(skip_serializing_if = "Option::is_none")]
836 /// Idempotency setting or key for deduping retries.
837 /// Use it to prevent duplicate side effects during replay or repair.
838 pub idempotency_key: Option<IdempotencyKey>,
839 /// Dedupe policy or key for a side-effecting operation.
840 /// Replay and repair use it to avoid sending or executing the same effect twice.
841 pub dedupe_key: DedupeKey,
842 #[serde(skip_serializing_if = "Option::is_none")]
843 /// Stable external operation id used for typed lineage, lookup, or
844 /// dedupe.
845 pub external_operation_id: Option<String>,
846 /// Terminal status used by this record or request.
847 pub terminal_status: OutputDispatchStatus,
848 /// Terminal append status used by this record or request.
849 pub terminal_append_status: TerminalAppendStatus,
850 #[serde(skip_serializing_if = "Option::is_none")]
851 /// Optional reconciliation adapter value.
852 /// When absent, callers should use the documented default or skip that optional behavior.
853 pub reconciliation_adapter: Option<OutputSinkRef>,
854 /// Reason a pending side effect is unsafe to retry automatically.
855 /// Recovery uses it to require repair or reconciliation before continuing.
856 pub unsafe_pending_reason: String,
857 /// Replay decision used by this record or request.
858 pub replay_decision: ReplayRepairDecision,
859 /// Allowlist for this policy or contract.
860 /// Validation uses it to reject undeclared or policy-denied values.
861 pub resend_allowed: bool,
862}
863
864impl OutputDeliveryReconciliationRecord {
865 /// Converts this value into journal record data.
866 /// This is data-only and does not perform I/O, call host ports, append journals, publish
867 /// events, or start processes.
868 pub fn to_journal_record(
869 &self,
870 base: OutputDeliveryJournalBase,
871 destination: DestinationRef,
872 ) -> JournalRecord {
873 output_delivery_effect_record(
874 base,
875 JournalRecordKind::OutputDispatch,
876 "output_dispatch_reconciliation_needed",
877 self.idempotency_key.clone(),
878 Some(self.dedupe_key.clone()),
879 Vec::new(),
880 JournalRecordPayload::OutputDelivery(OutputDeliveryRecord::Reconciliation(
881 self.clone(),
882 )),
883 EntityRef::new(EntityKind::OutputDelivery, self.delivery_id.as_str()),
884 destination,
885 Vec::new(),
886 PrivacyClass::ContentRefsOnly,
887 )
888 }
889}
890
891#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
892#[serde(rename_all = "snake_case")]
893/// Enumerates the finite terminal append status cases.
894/// Serialized names are part of the SDK contract; update fixtures when variants change.
895pub enum TerminalAppendStatus {
896 /// Use this variant when the contract needs to represent not attempted; selecting it has no side effect by itself.
897 NotAttempted,
898 /// Use this variant when the contract needs to represent appended; selecting it has no side effect by itself.
899 Appended,
900 /// Use this variant when the contract needs to represent append failed; selecting it has no side effect by itself.
901 AppendFailed,
902}
903
904#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
905#[serde(rename_all = "snake_case")]
906/// Enumerates the finite replay repair decision cases.
907/// Serialized names are part of the SDK contract; update fixtures when variants change.
908pub enum ReplayRepairDecision {
909 /// Use this variant when the contract needs to represent completed by dedupe proof; selecting it has no side effect by itself.
910 CompletedByDedupeProof,
911 /// Use this variant when the contract needs to represent requires host reconciliation; selecting it has no side effect by itself.
912 RequiresHostReconciliation,
913 /// Use this variant when the contract needs to represent unsafe pending; selecting it has no side effect by itself.
914 UnsafePending,
915}
916
917#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
918/// Carries the output delivery event record record payload for journal, event, or fixture surfaces.
919/// 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.
920pub struct OutputDeliveryEventRecord {
921 /// Kind discriminator for event kind.
922 /// Use it to route finite match arms without parsing display text.
923 pub event_kind: OutputDeliveryEventKind,
924 /// Stable delivery id used for typed lineage, lookup, or dedupe.
925 pub delivery_id: OutputDeliveryId,
926 /// Destination label or ref for this item; it is metadata and does not
927 /// deliver content by itself.
928 pub destination: DestinationRef,
929 /// Typed sink ref reference. Resolving or executing it is a separate
930 /// policy-gated step.
931 pub sink_ref: OutputSinkRef,
932 /// Dedupe policy or key for a side-effecting operation.
933 /// Replay and repair use it to avoid sending or executing the same effect twice.
934 pub dedupe_key: DedupeKey,
935 #[serde(skip_serializing_if = "Option::is_none")]
936 /// Stable source message id used for typed lineage, lookup, or dedupe.
937 pub source_message_id: Option<MessageId>,
938 /// Dispatch status used by this record or request.
939 pub dispatch_status: OutputDispatchStatus,
940 #[serde(skip_serializing_if = "Option::is_none")]
941 /// Typed ack ref reference. Resolving or executing it is a separate
942 /// policy-gated step.
943 pub ack_ref: Option<String>,
944 #[serde(skip_serializing_if = "Option::is_none")]
945 /// Optional reconciliation status value.
946 /// When absent, callers should use the documented default or skip that optional behavior.
947 pub reconciliation_status: Option<ReplayRepairDecision>,
948 /// Redacted human-readable summary safe for events, telemetry, and logs.
949 pub redacted_summary: String,
950}
951
952#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
953#[serde(tag = "record_type", content = "record", rename_all = "snake_case")]
954/// Enumerates the finite output delivery record cases.
955/// Serialized names are part of the SDK contract; update fixtures when variants change.
956#[expect(
957 clippy::large_enum_variant,
958 reason = "output-delivery records are durable serde payloads; direct variants stay explicit until a fixture-reviewed envelope migration"
959)]
960pub enum OutputDeliveryRecord {
961 /// Use this variant when the contract needs to represent intent; selecting it has no side effect by itself.
962 Intent(OutputDeliveryIntentRecord),
963 /// Use this variant when the contract needs to represent result; selecting it has no side effect by itself.
964 Result(OutputDeliveryResultRecord),
965 /// Use this variant when the contract needs to represent dedupe; selecting it has no side effect by itself.
966 Dedupe(OutputDeliveryDedupeRecord),
967 /// Use this variant when the contract needs to represent reconciliation; selecting it has no side effect by itself.
968 Reconciliation(OutputDeliveryReconciliationRecord),
969 /// Use this variant when the contract needs to represent event; selecting it has no side effect by itself.
970 Event(OutputDeliveryEventRecord),
971}
972
973#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
974/// Carries the output delivery journal base record payload for journal, event, or fixture surfaces.
975/// 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.
976pub struct OutputDeliveryJournalBase {
977 /// Journal seq used by this record or request.
978 pub journal_seq: u64,
979 /// Stable record id used for typed lineage, lookup, or dedupe.
980 pub record_id: String,
981 /// Run identifier used for lineage, filtering, replay, and dedupe.
982 pub run_id: RunId,
983 /// Agent identifier used for lineage, filtering, and ownership checks.
984 pub agent_id: AgentId,
985 #[serde(skip_serializing_if = "Option::is_none")]
986 /// Turn identifier for one loop turn within a run.
987 pub turn_id: Option<TurnId>,
988 #[serde(skip_serializing_if = "Option::is_none")]
989 /// Attempt identifier for retry, repair, provider, or tool execution
990 /// evidence.
991 pub attempt_id: Option<AttemptId>,
992 /// Source label or ref for this item; it is metadata and does not fetch
993 /// content by itself.
994 pub source: SourceRef,
995 /// Destination label or ref for this item; it is metadata and does not
996 /// deliver content by itself.
997 pub destination: DestinationRef,
998 /// Timestamp in milliseconds associated with this record.
999 /// Use it for ordering and diagnostics; durable causality still comes from ids and cursors.
1000 pub timestamp_millis: u64,
1001 /// Fingerprint of the runtime package snapshot in force when this value was produced.
1002 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
1003 /// not execute package behavior.
1004 pub runtime_package_fingerprint: RuntimePackageFingerprint,
1005 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
1006 pub redaction_policy_id: String,
1007}
1008
1009/// Builds the build output delivery dedupe key value.
1010/// This is data construction and performs no I/O, journal append, event publication, or process
1011pub fn build_output_delivery_dedupe_key(request: &OutputDeliveryRequest) -> DedupeKey {
1012 let content_refs = request
1013 .content_refs
1014 .iter()
1015 .map(|content_ref| content_ref.as_str())
1016 .collect::<Vec<_>>()
1017 .join(",");
1018 let policy_refs = request
1019 .policy_refs
1020 .iter()
1021 .map(|policy_ref| {
1022 format!(
1023 "{}:{}",
1024 policy_ref.as_str(),
1025 policy_ref.version.as_deref().unwrap_or("unversioned")
1026 )
1027 })
1028 .collect::<Vec<_>>()
1029 .join(",");
1030 let preimage = format!(
1031 "run={}|destination_kind={:?}|destination={}|sink={}|kind={}|message={}|validated={}|content={}|policies={}|package={}",
1032 request.run_id.as_str(),
1033 request.destination.kind,
1034 request.destination.as_str(),
1035 request.sink_ref.as_str(),
1036 request.delivery_kind.dedupe_fragment(),
1037 request
1038 .source_message_id
1039 .as_ref()
1040 .map(|id| id.as_str())
1041 .unwrap_or("none"),
1042 request
1043 .validated_output_id
1044 .as_ref()
1045 .map(|id| id.as_str())
1046 .unwrap_or("none"),
1047 content_refs,
1048 policy_refs,
1049 request.runtime_package_fingerprint.as_str(),
1050 );
1051 let digest = Sha256::digest(preimage.as_bytes());
1052 DedupeKey::new(format!("dedupe.output_delivery.{digest:x}"))
1053}
1054
1055#[expect(
1056 clippy::too_many_arguments,
1057 reason = "private output-delivery journal constructor mirrors effect and event lineage fields for auditability"
1058)]
1059fn output_delivery_effect_record(
1060 base: OutputDeliveryJournalBase,
1061 record_kind: JournalRecordKind,
1062 event_kind: &str,
1063 idempotency_key: Option<IdempotencyKey>,
1064 dedupe_key: Option<DedupeKey>,
1065 content_refs: Vec<ContentRef>,
1066 payload: JournalRecordPayload,
1067 subject_ref: EntityRef,
1068 destination: DestinationRef,
1069 _policy_refs: Vec<PolicyRef>,
1070 privacy: PrivacyClass,
1071) -> JournalRecord {
1072 let related_refs = content_refs
1073 .iter()
1074 .map(|content_ref| EntityRef::new(EntityKind::Content, content_ref.as_str()))
1075 .collect::<Vec<_>>();
1076 JournalRecord {
1077 journal_schema_version: JOURNAL_SCHEMA_VERSION,
1078 journal_seq: base.journal_seq,
1079 record_id: base.record_id,
1080 record_kind,
1081 run_id: base.run_id.clone(),
1082 agent_id: base.agent_id.clone(),
1083 turn_id: base.turn_id.clone(),
1084 attempt_id: base.attempt_id.clone(),
1085 subject_ref: subject_ref.clone(),
1086 related_refs: related_refs.clone(),
1087 causal_refs: Vec::new(),
1088 source: base.source.clone(),
1089 destination: Some(destination.clone()),
1090 correlation_keys: Vec::new(),
1091 tags: vec!["output_delivery".to_string()],
1092 delivery_semantics: "journal_backed".to_string(),
1093 event_index: EventIndexProjection {
1094 run_id: base.run_id,
1095 agent_id: base.agent_id,
1096 turn_id: base.turn_id,
1097 event_family: "output_delivery".to_string(),
1098 event_kind: event_kind.to_string(),
1099 source: base.source,
1100 destination: Some(destination),
1101 subject_ref,
1102 related_refs,
1103 correlation_keys: Vec::new(),
1104 tags: vec!["output_delivery".to_string()],
1105 privacy_class: privacy,
1106 delivery_semantics: "journal_backed".to_string(),
1107 },
1108 timestamp_millis: base.timestamp_millis,
1109 runtime_package_fingerprint: base.runtime_package_fingerprint.as_str().to_string(),
1110 privacy,
1111 content_refs,
1112 redaction_policy_id: base.redaction_policy_id,
1113 idempotency_key,
1114 dedupe_key,
1115 checkpoint_ref: None,
1116 payload,
1117 }
1118}