Skip to main content

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}