runtime/sieve/record.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What the sieve answers with: the text the model sees, the way back
7//! to the original, and the account of every stage — kept, no-op,
8//! refused, or unavailable — so a filter that stops working can be
9//! read off the Ledger afterwards. The payload holds integers and
10//! strings only.
11
12use kernel::{AxError, Locator, Payload};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum PassReason {
17 BelowFloor,
18 NothingShrank,
19}
20
21/// The sieve's answer. `Passed` returns the input byte for byte, and
22/// carries the account of whatever ran before it decided not to cut.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Sieved {
25 Passed {
26 text: String,
27 reason: PassReason,
28 /// The stages that ran, with their counts. `None` below the
29 /// floor, where no stage runs at all and the reason is the whole
30 /// account.
31 account: Option<SieveAccount>,
32 },
33 Cut(SieveRecord),
34}
35
36/// What the model sees, the way back, and the account of every stage.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct SieveRecord {
39 pub text: String,
40 pub original: Locator,
41 pub rest_path: String,
42 pub filter: String,
43 pub lines_in: u64,
44 pub lines_out: u64,
45 pub bytes_in: u64,
46 pub bytes_out: u64,
47 pub stages: Vec<StageReport>,
48}
49
50/// The stages a result passes through, in the words the ledger holds.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum Stage {
54 StripAnsi,
55 FoldBlank,
56 DedupTemplate,
57 DiffPrevious,
58 Filter,
59 CutLongLine,
60 Truncate,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(tag = "outcome", rename_all = "snake_case")]
65pub enum StageOutcome {
66 Applied {
67 bytes_before: u64,
68 bytes_after: u64,
69 },
70 Noop,
71 /// The stage would have grown the text, so its result was refused.
72 Rejected {
73 grew_to: u64,
74 },
75 Unavailable {
76 reason: String,
77 },
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct StageReport {
82 pub stage: Stage,
83 #[serde(flatten)]
84 pub outcome: StageOutcome,
85}
86
87/// What the sieve cut out of one result, beyond where it went.
88///
89/// Four keys or eleven: a result that left the window without the
90/// sieve carries the first four and nothing else, which is why this
91/// half is one value with a name rather than seven fields that are all
92/// present or all absent.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct SieveAccount {
95 /// The filter table this result was cut by.
96 pub filter: String,
97 pub lines_in: u64,
98 pub lines_out: u64,
99 /// Every stage the text passed through: applied, no-op, refused or
100 /// unavailable. Below the floor no stage ran, and the reason on
101 /// [`Sieved::Passed`] is the whole account.
102 pub stages: Vec<StageReport>,
103}
104
105/// `result_offloaded`: where a result went when it left the window,
106/// and what the sieve did to it on the way.
107///
108/// The one authority for this line's keys: the sieve and the plain
109/// offload path both write through it, so a reader folding a history
110/// meets one shape for one kind.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct ResultOffloaded {
113 /// Where the whole result is kept.
114 pub original: Locator,
115 /// How long the result was.
116 pub len: u64,
117 /// How long what the model sees is.
118 pub substitute_len: u64,
119 /// Where the rest of it was written.
120 pub rest_path: String,
121 /// The sieve's account, absent on the plain offload path.
122 #[serde(flatten, default, skip_serializing_if = "Option::is_none")]
123 pub sieve: Option<SieveAccount>,
124}
125
126impl ResultOffloaded {
127 /// The payload, through the one door a ledger line is written by.
128 ///
129 /// # Errors
130 /// Propagates whatever `Payload::of` says about the encoding.
131 pub fn payload(&self) -> Result<Payload, AxError> {
132 Payload::of(self)
133 }
134}
135
136impl SieveRecord {
137 /// The `result_offloaded` account for a result the sieve cut.
138 pub fn offloaded(&self) -> ResultOffloaded {
139 ResultOffloaded {
140 original: self.original.clone(),
141 len: self.bytes_in,
142 substitute_len: self.bytes_out,
143 rest_path: self.rest_path.clone(),
144 sieve: Some(SieveAccount {
145 filter: self.filter.clone(),
146 lines_in: self.lines_in,
147 lines_out: self.lines_out,
148 stages: self.stages.clone(),
149 }),
150 }
151 }
152}