agent_sdk_core/records/telemetry.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 telemetry portion of that contract.
5//!
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 domain::{
10 AgentId, AttemptId, DedupeKey, DestinationRef, EntityRef, EventId, JournalCursor,
11 PolicyRef, PrivacyClass, RetentionClass, RunId, SourceRef, SpanId, TraceId, TurnId,
12 },
13 event::{EventCursor, EventFamily, EventKind},
14 provider::ProviderUsage,
15};
16
17/// Constant value for the records::telemetry contract. Use it to keep
18/// SDK records and tests aligned on the same stable value.
19pub const TELEMETRY_SCHEMA_VERSION: u16 = 1;
20
21macro_rules! telemetry_id {
22 ($name:ident, $debug:literal) => {
23 #[doc = concat!(
24 "Typed telemetry identifier for `",
25 stringify!($name),
26 "`. Use it for telemetry projection, export, usage, or cost records; ",
27 "constructing it is data-only and performs no side effects."
28 )]
29 #[derive(Clone, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
30 #[serde(transparent)]
31 pub struct $name(String);
32
33 impl $name {
34 /// Creates a new records::telemetry value with explicit
35 /// caller-provided inputs. This constructor is data-only
36 /// and performs no I/O or external side effects.
37 pub fn new(value: impl Into<String>) -> Self {
38 Self(value.into())
39 }
40
41 /// Returns this value as str. The accessor is side-effect
42 /// free and keeps ownership with the caller.
43 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46 }
47
48 impl core::fmt::Debug for $name {
49 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 formatter.write_str(concat!($debug, "(redacted)"))
51 }
52 }
53
54 impl core::fmt::Display for $name {
55 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 formatter.write_str(concat!($debug, "(redacted)"))
57 }
58 }
59 };
60}
61
62telemetry_id!(TelemetryProjectionId, "TelemetryProjectionId");
63telemetry_id!(TelemetryRecordId, "TelemetryRecordId");
64telemetry_id!(TelemetrySinkId, "TelemetrySinkId");
65telemetry_id!(TelemetryExportAttemptId, "TelemetryExportAttemptId");
66telemetry_id!(TelemetryUsageRecordId, "TelemetryUsageRecordId");
67telemetry_id!(TelemetryCostRecordId, "TelemetryCostRecordId");
68telemetry_id!(RateTableVersion, "RateTableVersion");
69
70#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
71#[serde(rename_all = "snake_case")]
72/// Enumerates the finite telemetry sink kind cases.
73/// Serialized names are part of the SDK contract; update fixtures when variants change.
74pub enum TelemetrySinkKind {
75 /// Use this variant when the contract needs to represent open telemetry; selecting it has no side effect by itself.
76 OpenTelemetry,
77 /// Use this variant when the contract needs to represent durable trace; selecting it has no side effect by itself.
78 DurableTrace,
79 /// Use this variant when the contract needs to represent local diagnostic; selecting it has no side effect by itself.
80 LocalDiagnostic,
81 /// Use this variant when the contract needs to represent cli summary; selecting it has no side effect by itself.
82 CliSummary,
83 /// Use this variant when the contract needs to represent test; selecting it has no side effect by itself.
84 Test,
85}
86
87#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
88#[serde(rename_all = "snake_case")]
89/// Enumerates the finite telemetry content capture mode cases.
90/// Serialized names are part of the SDK contract; update fixtures when variants change.
91pub enum TelemetryContentCaptureMode {
92 /// Use this variant when the contract needs to represent off; selecting it has no side effect by itself.
93 #[default]
94 Off,
95 /// Use this variant when the contract needs to represent metadata only; selecting it has no side effect by itself.
96 MetadataOnly,
97 /// Use this variant when the contract needs to represent redacted summary; selecting it has no side effect by itself.
98 RedactedSummary,
99 /// Use this variant when the contract needs to represent payload refs; selecting it has no side effect by itself.
100 PayloadRefs,
101 /// Use this variant when the contract needs to represent raw content; selecting it has no side effect by itself.
102 RawContent,
103}
104
105impl TelemetryContentCaptureMode {
106 /// Returns whether captures raw content applies for this contract.
107 /// This is data-only and does not perform I/O, call host ports, append journals, publish
108 /// events, or start processes.
109 pub fn captures_raw_content(&self) -> bool {
110 matches!(self, Self::RawContent)
111 }
112}
113
114#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "snake_case")]
116/// Enumerates the finite telemetry projection kind cases.
117/// Serialized names are part of the SDK contract; update fixtures when variants change.
118pub enum TelemetryProjectionKind {
119 /// Use this variant when the contract needs to represent progress; selecting it has no side effect by itself.
120 Progress,
121 /// Use this variant when the contract needs to represent run terminal; selecting it has no side effect by itself.
122 RunTerminal,
123 /// Use this variant when the contract needs to represent usage; selecting it has no side effect by itself.
124 Usage,
125 /// Use this variant when the contract needs to represent cost estimate; selecting it has no side effect by itself.
126 CostEstimate,
127 /// Use this variant when the contract needs to represent cost correction; selecting it has no side effect by itself.
128 CostCorrection,
129 /// Use this variant when the contract needs to represent sink health; selecting it has no side effect by itself.
130 SinkHealth,
131 /// Use this variant when the contract needs to represent repair cursor; selecting it has no side effect by itself.
132 RepairCursor,
133}
134
135impl TelemetryProjectionKind {
136 /// Reports whether this value is terminal preserved. The check is
137 /// pure and does not mutate SDK or host state.
138 pub fn is_terminal_preserved(&self) -> bool {
139 matches!(
140 self,
141 Self::RunTerminal
142 | Self::Usage
143 | Self::CostEstimate
144 | Self::CostCorrection
145 | Self::SinkHealth
146 | Self::RepairCursor
147 )
148 }
149}
150
151#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
152/// Carries the telemetry projection record payload for journal, event, or fixture surfaces.
153/// 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.
154pub struct TelemetryProjection {
155 /// Wire schema version used for compatibility checks.
156 pub schema_version: u16,
157 /// Stable projection id used for typed lineage, lookup, or dedupe.
158 pub projection_id: TelemetryProjectionId,
159 /// Projection controls for exposing data to a provider or subscriber.
160 /// Use it to keep provider-visible data separate from private SDK state.
161 pub projection_kind: TelemetryProjectionKind,
162 /// Source record used by this record or request.
163 pub source_record: TelemetrySourceRecord,
164 /// Run identifier used for lineage, filtering, replay, and dedupe.
165 pub run_id: RunId,
166 /// Agent identifier used for lineage, filtering, and ownership checks.
167 pub agent_id: AgentId,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 /// Turn identifier for one loop turn within a run.
170 pub turn_id: Option<TurnId>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 /// Attempt identifier for retry, repair, provider, or tool execution
173 /// evidence.
174 pub attempt_id: Option<AttemptId>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 /// Event identifier used to correlate live events with journal or replay
177 /// evidence.
178 pub event_id: Option<EventId>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 /// Cursor identifying a replay, export, or subscription position.
181 /// Use it to resume without widening the original scope.
182 pub journal_cursor: Option<JournalCursor>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 /// Stable trace id used for typed lineage, lookup, or dedupe.
185 pub trace_id: Option<TraceId>,
186 #[serde(skip_serializing_if = "Option::is_none")]
187 /// Stable span id used for typed lineage, lookup, or dedupe.
188 pub span_id: Option<SpanId>,
189 /// Fingerprint of the runtime package snapshot in force when this value was produced.
190 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
191 /// not execute package behavior.
192 pub runtime_package_fingerprint: String,
193 /// Source label or ref for this item; it is metadata and does not fetch
194 /// content by itself.
195 pub source: SourceRef,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 /// Destination label or ref for this item; it is metadata and does not
198 /// deliver content by itself.
199 pub destination: Option<DestinationRef>,
200 /// Typed subject ref reference. Resolving or executing it is a separate
201 /// policy-gated step.
202 pub subject_ref: EntityRef,
203 #[serde(default, skip_serializing_if = "Vec::is_empty")]
204 /// Policy references that govern admission, projection, execution, or
205 /// delivery.
206 pub policy_refs: Vec<PolicyRef>,
207 /// Privacy class used for projection, telemetry, and raw-content access
208 /// decisions.
209 pub privacy: PrivacyClass,
210 /// Retention class used by hosts and sinks when storing or exporting this
211 /// item.
212 pub retention: RetentionClass,
213 /// Content capture used by this record or request.
214 pub content_capture: TelemetryContentCaptureMode,
215 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
216 pub redaction_policy_id: String,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 /// Stable provider id used for typed lineage, lookup, or dedupe.
219 pub provider_id: Option<String>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 /// Stable model id used for typed lineage, lookup, or dedupe.
222 pub model_id: Option<String>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 /// Optional tool name value.
225 /// When absent, callers should use the documented default or skip that optional behavior.
226 pub tool_name: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 /// Optional usage value.
229 /// When absent, callers should use the documented default or skip that optional behavior.
230 pub usage: Option<UsageUnits>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 /// Optional cost value.
233 /// When absent, callers should use the documented default or skip that optional behavior.
234 pub cost: Option<CostUnits>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 /// Optional terminal status value.
237 /// When absent, callers should use the documented default or skip that optional behavior.
238 pub terminal_status: Option<TelemetryTerminalStatus>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 /// Optional sink health value.
241 /// When absent, callers should use the documented default or skip that optional behavior.
242 pub sink_health: Option<TelemetrySinkHealth>,
243 /// Redacted human-readable summary safe for events, telemetry, and logs.
244 pub redacted_summary: String,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 /// Raw content or raw-content control for this value.
247 /// Use it only when policy explicitly allows raw content capture or delivery.
248 pub raw_content: Option<String>,
249}
250
251impl TelemetryProjection {
252 /// Reports whether this value is terminal preserved. The check is
253 /// pure and does not mutate SDK or host state.
254 pub fn is_terminal_preserved(&self) -> bool {
255 self.projection_kind.is_terminal_preserved()
256 }
257
258 /// Returns an updated value with without raw content configured.
259 /// This is data-only and does not perform I/O, call host ports, append journals, publish
260 /// events, or start processes.
261 pub fn without_raw_content(mut self) -> Self {
262 self.raw_content = None;
263 if self.content_capture.captures_raw_content() {
264 self.content_capture = TelemetryContentCaptureMode::RedactedSummary;
265 }
266 self
267 }
268}
269
270#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
271/// Carries the telemetry source record record payload for journal, event, or fixture surfaces.
272/// 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.
273pub struct TelemetrySourceRecord {
274 /// Event family used by this record or request.
275 pub event_family: EventFamily,
276 /// Kind discriminator for event kind.
277 /// Use it to route finite match arms without parsing display text.
278 pub event_kind: EventKind,
279 #[serde(skip_serializing_if = "Option::is_none")]
280 /// Cursor identifying a replay, export, or subscription position.
281 /// Use it to resume without widening the original scope.
282 pub event_cursor: Option<EventCursor>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 /// Cursor identifying the source event or journal position.
285 /// Use it to connect projections back to durable evidence.
286 pub source_cursor: Option<TelemetrySourceCursor>,
287}
288
289#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
290#[serde(tag = "type", content = "cursor", rename_all = "snake_case")]
291/// Enumerates the finite telemetry source cursor cases.
292/// Serialized names are part of the SDK contract; update fixtures when variants change.
293pub enum TelemetrySourceCursor {
294 /// Use this variant when the contract needs to represent journal; selecting it has no side effect by itself.
295 Journal(JournalCursor),
296 /// Use this variant when the contract needs to represent event; selecting it has no side effect by itself.
297 Event(EventCursor),
298 /// Use this variant when the contract needs to represent archive; selecting it has no side effect by itself.
299 Archive(String),
300 /// Use this variant when the contract needs to represent usage; selecting it has no side effect by itself.
301 Usage(TelemetryUsageRecordId),
302 /// Use this variant when the contract needs to represent cost; selecting it has no side effect by itself.
303 Cost(TelemetryCostRecordId),
304}
305
306#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
307/// Carries the telemetry export cursor record payload for journal, event, or fixture surfaces.
308/// 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.
309pub struct TelemetryExportCursor {
310 /// Stable sink id used for typed lineage, lookup, or dedupe.
311 pub sink_id: TelemetrySinkId,
312 /// Export seq used by this record or request.
313 pub export_seq: u64,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 /// Optional last acknowledged source value.
316 /// When absent, callers should use the documented default or skip that optional behavior.
317 pub last_acknowledged_source: Option<TelemetrySourceCursor>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 /// Attempt identifier or attempt history for bounded retry/repair.
320 /// Use it to preserve ordering and avoid retry loops that cannot be audited.
321 pub last_attempted_source: Option<TelemetrySourceCursor>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 /// Dedupe policy or key for a side-effecting operation.
324 /// Replay and repair use it to avoid sending or executing the same effect twice.
325 pub sink_dedupe_key: Option<DedupeKey>,
326}
327
328impl TelemetryExportCursor {
329 /// Creates a new records::telemetry value with explicit
330 /// caller-provided inputs. This constructor is data-only and
331 /// performs no I/O or external side effects.
332 pub fn new(sink_id: TelemetrySinkId) -> Self {
333 Self {
334 sink_id,
335 export_seq: 0,
336 last_acknowledged_source: None,
337 last_attempted_source: None,
338 sink_dedupe_key: None,
339 }
340 }
341
342 /// Returns an updated value with attempted configured.
343 /// This is data-only and does not perform I/O, call host ports, append journals, publish
344 /// events, or start processes.
345 pub fn attempted(mut self, source: Option<TelemetrySourceCursor>) -> Self {
346 self.last_attempted_source = source;
347 self
348 }
349
350 /// Returns an updated value with acknowledged configured.
351 /// This is data-only and does not perform I/O, call host ports, append journals, publish
352 /// events, or start processes.
353 pub fn acknowledged(mut self, source: Option<TelemetrySourceCursor>) -> Self {
354 self.export_seq += 1;
355 self.last_acknowledged_source = source;
356 self.last_attempted_source = None;
357 self
358 }
359}
360
361#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
362/// Carries the usage units record payload for journal, event, or fixture surfaces.
363/// 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.
364pub struct UsageUnits {
365 #[serde(skip_serializing_if = "Option::is_none")]
366 /// Optional input tokens value.
367 /// When absent, callers should use the documented default or skip that optional behavior.
368 pub input_tokens: Option<u32>,
369 #[serde(skip_serializing_if = "Option::is_none")]
370 /// Optional output tokens value.
371 /// When absent, callers should use the documented default or skip that optional behavior.
372 pub output_tokens: Option<u32>,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 /// Optional total tokens value.
375 /// When absent, callers should use the documented default or skip that optional behavior.
376 pub total_tokens: Option<u32>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 /// Byte size or byte limit for bytes.
379 /// Use it to enforce bounded reads, writes, summaries, or parser output.
380 pub bytes: Option<u64>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 /// media duration ms duration in milliseconds.
383 pub media_duration_ms: Option<u64>,
384}
385
386impl From<ProviderUsage> for UsageUnits {
387 fn from(usage: ProviderUsage) -> Self {
388 Self {
389 input_tokens: usage.input_tokens,
390 output_tokens: usage.output_tokens,
391 total_tokens: usage.total_tokens,
392 bytes: None,
393 media_duration_ms: None,
394 }
395 }
396}
397
398#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
399/// Carries the cost units record payload for journal, event, or fixture surfaces.
400/// 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.
401pub struct CostUnits {
402 /// Cost amount expressed in millionths of the currency unit.
403 /// Use micros to keep cost accounting deterministic and integer-based.
404 pub amount_micros: u64,
405 /// Currency code for the cost amount.
406 /// Cost accounting uses it with amount micros and rate-table version.
407 pub currency: String,
408 /// Version of the rate table used for cost estimation.
409 /// This distinguishes estimated cost from provider-reported billing.
410 pub rate_table_version: RateTableVersion,
411 /// Whether cost or usage values are estimated or provider-reported.
412 /// Use it to avoid treating estimates as final billing truth.
413 pub estimate_status: CostEstimateStatus,
414}
415
416#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
417#[serde(rename_all = "snake_case")]
418/// Enumerates the finite cost estimate status cases.
419/// Serialized names are part of the SDK contract; update fixtures when variants change.
420pub enum CostEstimateStatus {
421 /// Use this variant when the contract needs to represent estimated; selecting it has no side effect by itself.
422 Estimated,
423 /// Use this variant when the contract needs to represent provider reported; selecting it has no side effect by itself.
424 ProviderReported,
425 /// Use this variant when the contract needs to represent corrected; selecting it has no side effect by itself.
426 Corrected,
427}
428
429#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
430#[serde(rename_all = "snake_case")]
431/// Enumerates the finite telemetry terminal status cases.
432/// Serialized names are part of the SDK contract; update fixtures when variants change.
433pub enum TelemetryTerminalStatus {
434 /// Use this variant when the contract needs to represent completed; selecting it has no side effect by itself.
435 Completed,
436 /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
437 Failed,
438 /// Use this variant when the contract needs to represent cancelled; selecting it has no side effect by itself.
439 Cancelled,
440 /// Use this variant when the contract needs to represent timed out; selecting it has no side effect by itself.
441 TimedOut,
442 /// Use this variant when the contract needs to represent unknown; selecting it has no side effect by itself.
443 Unknown,
444}
445
446#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
447/// Carries the telemetry sink health record payload for journal, event, or fixture surfaces.
448/// 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.
449pub struct TelemetrySinkHealth {
450 /// Stable sink id used for typed lineage, lookup, or dedupe.
451 pub sink_id: TelemetrySinkId,
452 /// Kind discriminator for sink kind.
453 /// Use it to route finite match arms without parsing display text.
454 pub sink_kind: TelemetrySinkKind,
455 /// State used by this record or request.
456 pub state: TelemetrySinkHealthState,
457 /// Kind discriminator for failure kind.
458 /// Use it to route finite match arms without parsing display text.
459 pub failure_kind: Option<TelemetrySinkFailureKind>,
460 /// Whether terminal preserved is enabled.
461 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
462 pub terminal_preserved: bool,
463 /// Count of dropped items observed or included in this record.
464 pub dropped_count: u64,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 /// Cursor for telemetry export acknowledgement.
467 /// Sinks use it to ack exactly which projection was exported.
468 pub export_cursor: Option<TelemetryExportCursor>,
469 #[serde(skip_serializing_if = "Option::is_none")]
470 /// Reason a pending side effect is unsafe to retry automatically.
471 /// Recovery uses it to require repair or reconciliation before continuing.
472 pub unsafe_pending_reason: Option<String>,
473}
474
475#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
476#[serde(rename_all = "snake_case")]
477/// Enumerates the finite telemetry sink health state cases.
478/// Serialized names are part of the SDK contract; update fixtures when variants change.
479pub enum TelemetrySinkHealthState {
480 /// Use this variant when the contract needs to represent healthy; selecting it has no side effect by itself.
481 Healthy,
482 /// Use this variant when the contract needs to represent failed; selecting it has no side effect by itself.
483 Failed,
484 /// Use this variant when the contract needs to represent recovered; selecting it has no side effect by itself.
485 Recovered,
486}
487
488#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
489#[serde(rename_all = "snake_case")]
490/// Enumerates the finite telemetry sink failure kind cases.
491/// Serialized names are part of the SDK contract; update fixtures when variants change.
492pub enum TelemetrySinkFailureKind {
493 /// Use this variant when the contract needs to represent overflow; selecting it has no side effect by itself.
494 Overflow,
495 /// Use this variant when the contract needs to represent export rejected; selecting it has no side effect by itself.
496 ExportRejected,
497 /// Use this variant when the contract needs to represent serialization; selecting it has no side effect by itself.
498 Serialization,
499 /// Use this variant when the contract needs to represent schema mismatch; selecting it has no side effect by itself.
500 SchemaMismatch,
501 /// Use this variant when the contract needs to represent sink unavailable; selecting it has no side effect by itself.
502 SinkUnavailable,
503}
504
505#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
506/// Carries the telemetry record record payload for journal, event, or fixture surfaces.
507/// 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.
508pub struct TelemetryRecord {
509 /// Wire schema version used for compatibility checks.
510 pub schema_version: u16,
511 /// Stable record id used for typed lineage, lookup, or dedupe.
512 pub record_id: TelemetryRecordId,
513 /// Run identifier used for lineage, filtering, replay, and dedupe.
514 pub run_id: RunId,
515 /// Agent identifier used for lineage, filtering, and ownership checks.
516 pub agent_id: AgentId,
517 #[serde(skip_serializing_if = "Option::is_none")]
518 /// Cursor identifying the source event or journal position.
519 /// Use it to connect projections back to durable evidence.
520 pub source_cursor: Option<TelemetrySourceCursor>,
521 /// Fingerprint of the runtime package snapshot in force when this value was produced.
522 /// Use it for replay, dedupe, and package-lineage checks; the field is evidence and does
523 /// not execute package behavior.
524 pub runtime_package_fingerprint: String,
525 /// Privacy class used for projection, telemetry, and raw-content access
526 /// decisions.
527 pub privacy: PrivacyClass,
528 /// Retention class used by hosts and sinks when storing or exporting this
529 /// item.
530 pub retention: RetentionClass,
531 /// Content capture used by this record or request.
532 pub content_capture: TelemetryContentCaptureMode,
533 /// Stable redaction policy id used for typed lineage, lookup, or dedupe.
534 pub redaction_policy_id: String,
535 #[serde(default, skip_serializing_if = "Vec::is_empty")]
536 /// Policy references that govern admission, projection, execution, or
537 /// delivery.
538 pub policy_refs: Vec<PolicyRef>,
539 /// Payload carried by this record.
540 /// Use the surrounding policy and redaction fields to decide whether it can be exposed.
541 pub payload: TelemetryRecordPayload,
542}
543
544impl TelemetryRecord {
545 /// Returns usage derived from the supplied state.
546 /// This is data-only and does not perform I/O, call host ports, append journals, publish
547 /// events, or start processes.
548 pub fn usage(
549 record_id: TelemetryRecordId,
550 projection: &TelemetryProjection,
551 usage_record_id: TelemetryUsageRecordId,
552 ) -> Self {
553 Self::from_projection(
554 record_id,
555 projection,
556 TelemetryRecordPayload::Usage(UsageTelemetryRecord {
557 usage_record_id,
558 units: projection.usage.clone().unwrap_or_default(),
559 provider_id: projection.provider_id.clone(),
560 model_id: projection.model_id.clone(),
561 }),
562 )
563 }
564
565 /// Returns cost derived from the supplied state.
566 /// This is data-only and does not perform I/O, call host ports, append journals, publish
567 /// events, or start processes.
568 pub fn cost(
569 record_id: TelemetryRecordId,
570 projection: &TelemetryProjection,
571 cost_record_id: TelemetryCostRecordId,
572 correction_ref: Option<TelemetryCostRecordId>,
573 ) -> Self {
574 let payload = CostTelemetryRecord {
575 cost_record_id,
576 units: projection
577 .cost
578 .clone()
579 .expect("cost projection has cost units"),
580 correction_ref,
581 };
582 Self::from_projection(record_id, projection, TelemetryRecordPayload::Cost(payload))
583 }
584
585 /// Returns an updated value with sink failed configured.
586 /// This is data-only and does not perform I/O, call host ports, append journals, publish
587 /// events, or start processes.
588 pub fn sink_failed(
589 record_id: TelemetryRecordId,
590 projection: &TelemetryProjection,
591 failure: TelemetrySinkFailureRecord,
592 ) -> Self {
593 Self::from_projection(
594 record_id,
595 projection,
596 TelemetryRecordPayload::SinkFailed(failure),
597 )
598 }
599
600 /// Returns an updated value with sink recovered configured.
601 /// This is data-only and does not perform I/O, call host ports, append journals, publish
602 /// events, or start processes.
603 pub fn sink_recovered(
604 record_id: TelemetryRecordId,
605 projection: &TelemetryProjection,
606 recovery: TelemetrySinkRecoveryRecord,
607 ) -> Self {
608 Self::from_projection(
609 record_id,
610 projection,
611 TelemetryRecordPayload::SinkRecovered(recovery),
612 )
613 }
614
615 /// Returns an updated value with export cursor configured.
616 /// This is data-only and does not perform I/O, call host ports, append journals, publish
617 /// events, or start processes.
618 pub fn export_cursor(
619 record_id: TelemetryRecordId,
620 projection: &TelemetryProjection,
621 cursor: TelemetryExportCursor,
622 ) -> Self {
623 Self::from_projection(
624 record_id,
625 projection,
626 TelemetryRecordPayload::ExportCursor(TelemetryExportCursorRecord { cursor }),
627 )
628 }
629
630 fn from_projection(
631 record_id: TelemetryRecordId,
632 projection: &TelemetryProjection,
633 payload: TelemetryRecordPayload,
634 ) -> Self {
635 Self {
636 schema_version: TELEMETRY_SCHEMA_VERSION,
637 record_id,
638 run_id: projection.run_id.clone(),
639 agent_id: projection.agent_id.clone(),
640 source_cursor: projection.source_record.source_cursor.clone(),
641 runtime_package_fingerprint: projection.runtime_package_fingerprint.clone(),
642 privacy: projection.privacy,
643 retention: projection.retention,
644 content_capture: projection.content_capture.clone(),
645 redaction_policy_id: projection.redaction_policy_id.clone(),
646 policy_refs: projection.policy_refs.clone(),
647 payload,
648 }
649 }
650}
651
652#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
653#[serde(tag = "type", content = "record", rename_all = "snake_case")]
654/// Enumerates the finite telemetry record payload cases.
655/// Serialized names are part of the SDK contract; update fixtures when variants change.
656pub enum TelemetryRecordPayload {
657 /// Use this variant when the contract needs to represent usage; selecting it has no side effect by itself.
658 Usage(UsageTelemetryRecord),
659 /// Use this variant when the contract needs to represent cost; selecting it has no side effect by itself.
660 Cost(CostTelemetryRecord),
661 /// Use this variant when the contract needs to represent sink failed; selecting it has no side effect by itself.
662 SinkFailed(TelemetrySinkFailureRecord),
663 /// Use this variant when the contract needs to represent sink recovered; selecting it has no side effect by itself.
664 SinkRecovered(TelemetrySinkRecoveryRecord),
665 /// Use this variant when the contract needs to represent export cursor; selecting it has no side effect by itself.
666 ExportCursor(TelemetryExportCursorRecord),
667}
668
669#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
670/// Carries the usage telemetry record record payload for journal, event, or fixture surfaces.
671/// 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.
672pub struct UsageTelemetryRecord {
673 /// Stable usage record id used for typed lineage, lookup, or dedupe.
674 pub usage_record_id: TelemetryUsageRecordId,
675 /// Units used by this record or request.
676 pub units: UsageUnits,
677 #[serde(skip_serializing_if = "Option::is_none")]
678 /// Stable provider id used for typed lineage, lookup, or dedupe.
679 pub provider_id: Option<String>,
680 #[serde(skip_serializing_if = "Option::is_none")]
681 /// Stable model id used for typed lineage, lookup, or dedupe.
682 pub model_id: Option<String>,
683}
684
685#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
686/// Carries the cost telemetry record record payload for journal, event, or fixture surfaces.
687/// 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.
688pub struct CostTelemetryRecord {
689 /// Stable cost record id used for typed lineage, lookup, or dedupe.
690 pub cost_record_id: TelemetryCostRecordId,
691 /// Units used by this record or request.
692 pub units: CostUnits,
693 #[serde(skip_serializing_if = "Option::is_none")]
694 /// Typed correction ref reference. Resolving or executing it is a
695 /// separate policy-gated step.
696 pub correction_ref: Option<TelemetryCostRecordId>,
697}
698
699#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
700/// Carries the telemetry sink failure record record payload for journal, event, or fixture surfaces.
701/// 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.
702pub struct TelemetrySinkFailureRecord {
703 /// Stable sink id used for typed lineage, lookup, or dedupe.
704 pub sink_id: TelemetrySinkId,
705 /// Kind discriminator for sink kind.
706 /// Use it to route finite match arms without parsing display text.
707 pub sink_kind: TelemetrySinkKind,
708 /// Kind discriminator for failure kind.
709 /// Use it to route finite match arms without parsing display text.
710 pub failure_kind: TelemetrySinkFailureKind,
711 /// Whether terminal preserved is enabled.
712 /// Policy, validation, or routing code uses this flag to choose the explicit behavior.
713 pub terminal_preserved: bool,
714 /// Count of dropped items observed or included in this record.
715 pub dropped_count: u64,
716 /// Last telemetry export cursor acknowledged by the sink.
717 /// Repair uses it to resume after the last confirmed export.
718 pub last_acknowledged_cursor: Option<TelemetryExportCursor>,
719 /// Cursor where repair or reconciliation should resume.
720 /// Use it to continue recovery without replaying unrelated records.
721 pub repair_cursor: Option<TelemetrySourceCursor>,
722 /// Reason a pending side effect is unsafe to retry automatically.
723 /// Recovery uses it to require repair or reconciliation before continuing.
724 pub unsafe_pending_reason: Option<String>,
725 /// Redacted human-readable summary safe for events, telemetry, and logs.
726 pub redacted_summary: String,
727}
728
729#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
730/// Carries the telemetry sink recovery record record payload for journal, event, or fixture surfaces.
731/// 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.
732pub struct TelemetrySinkRecoveryRecord {
733 /// Stable sink id used for typed lineage, lookup, or dedupe.
734 pub sink_id: TelemetrySinkId,
735 /// Kind discriminator for sink kind.
736 /// Use it to route finite match arms without parsing display text.
737 pub sink_kind: TelemetrySinkKind,
738 /// Cursor for telemetry export acknowledgement.
739 /// Sinks use it to ack exactly which projection was exported.
740 pub export_cursor: TelemetryExportCursor,
741 /// Redacted human-readable summary safe for events, telemetry, and logs.
742 pub redacted_summary: String,
743}
744
745#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
746/// Carries the telemetry export cursor record record payload for journal, event, or fixture surfaces.
747/// 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.
748pub struct TelemetryExportCursorRecord {
749 /// Cursor identifying a replay, export, or subscription position.
750 /// Use it to resume without widening the original scope.
751 pub cursor: TelemetryExportCursor,
752}