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