Skip to main content

strop_trace/
event.rs

1//! Schema vocabulary shared by producers; payloads retain their domain's types.
2//!
3//! Schema 2 adds the closed forensic substream (`Replay` nodes, captured only
4//! under the `Full` content policy) and the always-present terminal `TraceEnd`
5//! marker that distinguishes a complete capture from a capped or failed one.
6use serde::{Deserialize, Serialize};
7
8pub const SCHEMA_VERSION: u32 = 2;
9
10/// Hard upper bounds a capture may use. They exist so a runaway producer
11/// cannot fill the disk; `start` refuses anything outside them, and the
12/// reader applies the same totals, so writer and reader agree everywhere.
13pub const MAX_CAPTURE_BYTES: usize = 64 * 1024 * 1024;
14pub const MAX_CAPTURE_EVENTS: u64 = 100_000;
15pub const MAX_RECORD_BYTES: usize = 256 * 1024;
16/// The writer always reserves this much of the byte budget for the
17/// terminal marker, so a capture that hits its cap still ends legibly.
18pub const TERMINAL_RESERVE: usize = 1024;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum EventKind {
23    SessionStart,
24    SessionEnd,
25    Input,
26    Paste,
27    State,
28    Document,
29    Mutation,
30    History,
31    Render,
32    Resize,
33    JobStarted,
34    JobFinished,
35    JobRejected,
36    LspMessage,
37    Replay,
38    TraceEnd,
39    Error,
40    Panic,
41}
42
43#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
44pub enum ContentPolicy {
45    /// Keys, paths and bounded previews are still sensitive; this is not redaction.
46    #[default]
47    Metadata,
48    /// Include whole document/paste payloads for reproduction, explicitly opted in.
49    Full,
50}
51
52/// Bounded capture: total bytes, total events, per-record bytes.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Limits {
55    pub bytes: usize,
56    pub events: u64,
57    pub record_bytes: usize,
58}
59
60impl Default for Limits {
61    fn default() -> Self {
62        Self {
63            bytes: MAX_CAPTURE_BYTES,
64            events: MAX_CAPTURE_EVENTS,
65            record_bytes: MAX_RECORD_BYTES,
66        }
67    }
68}
69
70impl Limits {
71    /// Hard bounds only — there is no CLI knob that lifts them.
72    pub fn valid(&self) -> bool {
73        self.bytes >= TERMINAL_RESERVE * 2
74            && self.bytes <= MAX_CAPTURE_BYTES
75            && self.events > 0
76            && self.events <= MAX_CAPTURE_EVENTS
77            && self.record_bytes > 0
78            && self.record_bytes <= MAX_RECORD_BYTES
79    }
80}
81
82#[derive(Debug, Default, Clone, Copy)]
83pub struct TraceOptions {
84    pub content: ContentPolicy,
85    pub limits: Limits,
86}
87
88/// UTF-8-safe bounded preview. The byte length and full text are separate fields.
89pub fn preview(text: &str) -> String {
90    let mut chars = text.chars();
91    let mut result: String = chars.by_ref().take(120).collect();
92    if chars.next().is_some() {
93        result.push('…');
94    }
95    result
96}