Skip to main content

sim_codec_bridge/
part_book.rs

1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Expr, Result, Symbol};
4
5/// Rendering class for a registered BRIDGE part kind.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum RenderClass {
8    /// Structural line in the packet face.
9    Structural,
10    /// Fluent frame sentence rendered from a typed frame.
11    Frame,
12    /// Non-instruction data rendered through a fence.
13    Data,
14    /// Evidence or attestation material.
15    Evidence,
16    /// Review text or structured review material.
17    Review,
18    /// Vote material.
19    Vote,
20    /// Patch material.
21    Patch,
22    /// Fetch request material.
23    Fetch,
24    /// Return contract material.
25    Return,
26    /// Receipt material.
27    Receipt,
28    /// Extension material.
29    Extension,
30}
31
32/// Authority class for a registered BRIDGE part kind.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum AuthorityClass {
35    /// Data only; it carries no instruction authority.
36    Data,
37    /// Normative instruction or obligation.
38    Normative,
39    /// Callable tool or model request material.
40    Callable,
41    /// Evidence, receipt, or review material.
42    Evidence,
43}
44
45/// Policy for preserving a part kind that is not in the standard normative book.
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub enum UnknownPolicy {
48    /// Reject the part unless the kind is registered.
49    Reject,
50    /// Preserve the part as data only.
51    PreserveDataOnly,
52}
53
54/// Registered BRIDGE part-kind specification.
55#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct BridgePartSpec {
57    /// Part kind symbol.
58    pub kind: Symbol,
59    /// Shape expression for the part payload.
60    pub shape_expr: Expr,
61    /// Rendering class.
62    pub render_class: RenderClass,
63    /// Authority class.
64    pub authority_class: AuthorityClass,
65    /// Unknown preservation policy.
66    pub unknown_policy: UnknownPolicy,
67}
68
69impl BridgePartSpec {
70    /// Builds a normative registered part spec.
71    pub fn new(
72        kind: Symbol,
73        shape_expr: Expr,
74        render_class: RenderClass,
75        authority_class: AuthorityClass,
76        unknown_policy: UnknownPolicy,
77    ) -> Self {
78        Self {
79            kind,
80            shape_expr,
81            render_class,
82            authority_class,
83            unknown_policy,
84        }
85    }
86
87    /// Builds a data-only preserving extension spec.
88    pub fn preserve_data_only(kind: Symbol, shape_expr: Expr) -> Self {
89        Self::new(
90            kind,
91            shape_expr,
92            RenderClass::Extension,
93            AuthorityClass::Data,
94            UnknownPolicy::PreserveDataOnly,
95        )
96    }
97}
98
99/// Registry of BRIDGE part-kind specifications.
100#[derive(Clone, Debug, Default, PartialEq, Eq)]
101pub struct BridgePartBook {
102    specs: BTreeMap<Symbol, BridgePartSpec>,
103}
104
105impl BridgePartBook {
106    /// Builds an empty part book.
107    pub fn new() -> Self {
108        Self::default()
109    }
110
111    /// Registers a part spec, replacing any existing spec for the same kind.
112    pub fn register(&mut self, spec: BridgePartSpec) {
113        self.specs.insert(spec.kind.clone(), spec);
114    }
115
116    /// Returns the registered spec for `kind`.
117    pub fn spec(&self, kind: &Symbol) -> Option<&BridgePartSpec> {
118        self.specs.get(kind)
119    }
120
121    /// Returns all registered part specs.
122    pub fn specs(&self) -> impl Iterator<Item = &BridgePartSpec> {
123        self.specs.values()
124    }
125
126    /// Checks that `kind` is registered for decoding.
127    pub fn require_registered(&self, kind: &Symbol) -> Result<&BridgePartSpec> {
128        self.spec(kind)
129            .ok_or_else(|| Error::Eval(format!("unknown BRIDGE part kind {kind}")))
130    }
131}
132
133/// A BRIDGE book bundles the part book and move book used for a packet.
134#[derive(Clone, Debug, PartialEq, Eq)]
135pub struct BridgeBook {
136    /// Registered part-kind specs.
137    pub parts: BridgePartBook,
138    /// Registered dialogue move specs.
139    pub moves: crate::BridgeMoveBook,
140    /// Registered fluent frame specs.
141    pub frames: crate::BridgeFrameBook,
142    /// Registered packet profile specs.
143    pub profiles: crate::BridgeProfileBook,
144    /// Policy for warrant verification by receivers using this book.
145    pub warrant_policy: crate::BridgeWarrantPolicy,
146}
147
148impl BridgeBook {
149    /// Builds a book from explicit part, move, frame, and profile books.
150    pub fn new(
151        parts: BridgePartBook,
152        moves: crate::BridgeMoveBook,
153        frames: crate::BridgeFrameBook,
154        profiles: crate::BridgeProfileBook,
155    ) -> Self {
156        Self {
157            parts,
158            moves,
159            frames,
160            profiles,
161            warrant_policy: crate::BridgeWarrantPolicy::SharedTrust,
162        }
163    }
164
165    /// Builds the standard BRIDGE book.
166    pub fn standard() -> Self {
167        Self::new(
168            crate::standard_part_book(),
169            crate::standard_move_book(),
170            crate::standard_frame_book(),
171            crate::standard_profile_book(),
172        )
173    }
174
175    /// Returns a copy with one more part spec registered.
176    pub fn with_part(mut self, spec: BridgePartSpec) -> Self {
177        self.parts.register(spec);
178        self
179    }
180
181    /// Returns a copy with one more frame spec registered.
182    pub fn with_frame(mut self, spec: crate::FrameSpec) -> Self {
183        self.frames.register(spec);
184        self
185    }
186
187    /// Returns a copy with one more profile spec registered.
188    pub fn with_profile(mut self, spec: crate::BridgeProfileSpec) -> Self {
189        self.profiles.register(spec);
190        self
191    }
192
193    /// Returns a copy with the warrant verification policy set.
194    pub fn with_warrant_policy(mut self, policy: crate::BridgeWarrantPolicy) -> Self {
195        self.warrant_policy = policy;
196        self
197    }
198
199    /// Validates a packet against parts, moves, profiles, and warrant policy.
200    pub fn validate_packet(&self, packet: &crate::BridgePacket) -> Result<()> {
201        let part_kinds = packet
202            .body
203            .iter()
204            .map(|part| part.kind.clone())
205            .collect::<Vec<_>>();
206        for part in &packet.body {
207            self.validate_part(part)?;
208        }
209        let parent_moves = parent_move_evidence(&packet.header.parents)?;
210        self.moves
211            .check_move(&packet.header.move_kind, &parent_moves, &part_kinds)?;
212        self.validate_profile(packet)?;
213        self.validate_warrant(packet)
214    }
215
216    fn validate_part(&self, part: &crate::BridgePart) -> Result<()> {
217        self.parts.require_registered(&part.kind)?;
218        match &part.kind {
219            kind if *kind == Symbol::qualified("bridge", "Frame") => {
220                self.frames.validate_payload(&part.payload)?;
221            }
222            kind if *kind == Symbol::qualified("bridge", "Call") => {
223                crate::validate_call_payload(&part.payload)?;
224            }
225            kind if *kind == Symbol::qualified("bridge", "Weave") => {
226                crate::validate_weave_payload(&part.payload)?;
227            }
228            kind if collab_part(kind) => {
229                crate::validate_collab_payload(kind, &part.payload)?;
230            }
231            _ => {}
232        }
233        Ok(())
234    }
235
236    fn validate_profile(&self, packet: &crate::BridgePacket) -> Result<()> {
237        let matches = self.profiles.matching_profiles(packet);
238        match matches.as_slice() {
239            [_profile] => Ok(()),
240            [] => Err(Error::Eval(
241                "BRIDGE packet body matches no standard profile".to_owned(),
242            )),
243            many => Err(Error::Eval(format!(
244                "BRIDGE packet body matches multiple profiles: {}",
245                many.iter()
246                    .map(Symbol::as_qualified_str)
247                    .collect::<Vec<_>>()
248                    .join(", ")
249            ))),
250        }
251    }
252
253    fn validate_warrant(&self, packet: &crate::BridgePacket) -> Result<()> {
254        match (self.warrant_policy, &packet.warrant) {
255            (crate::BridgeWarrantPolicy::SharedTrust, _) => Ok(()),
256            (crate::BridgeWarrantPolicy::Verify, Some(warrant)) => {
257                let expected = crate::warrant_for_packet(self, packet)?;
258                if warrant == &expected {
259                    Ok(())
260                } else {
261                    Err(Error::Eval(
262                        "BRIDGE warrant does not match local books".to_owned(),
263                    ))
264                }
265            }
266            (crate::BridgeWarrantPolicy::Verify, None) => Err(Error::Eval(
267                "BRIDGE warrant is required by verify policy".to_owned(),
268            )),
269        }
270    }
271}
272
273/// Builds the standard BRIDGE part book.
274pub fn standard_part_book() -> BridgePartBook {
275    let mut book = BridgePartBook::new();
276    for spec in [
277        spec("Given", RenderClass::Data, AuthorityClass::Data),
278        spec("Frame", RenderClass::Frame, AuthorityClass::Normative),
279        spec("Call", RenderClass::Structural, AuthorityClass::Callable),
280        spec("Weave", RenderClass::Structural, AuthorityClass::Normative),
281        spec("Check", RenderClass::Structural, AuthorityClass::Normative),
282        spec("Evidence", RenderClass::Evidence, AuthorityClass::Evidence),
283        spec("Review", RenderClass::Review, AuthorityClass::Evidence),
284        spec("Vote", RenderClass::Vote, AuthorityClass::Evidence),
285        spec("Patch", RenderClass::Patch, AuthorityClass::Normative),
286        spec("Fetch", RenderClass::Fetch, AuthorityClass::Callable),
287        spec("Return", RenderClass::Return, AuthorityClass::Normative),
288        spec("Receipt", RenderClass::Receipt, AuthorityClass::Evidence),
289        spec("Attest", RenderClass::Evidence, AuthorityClass::Evidence),
290        spec("Extension", RenderClass::Extension, AuthorityClass::Data),
291    ] {
292        book.register(spec);
293    }
294    book
295}
296
297fn spec(name: &str, render_class: RenderClass, authority_class: AuthorityClass) -> BridgePartSpec {
298    let kind = Symbol::qualified("bridge", name);
299    BridgePartSpec::new(
300        kind.clone(),
301        Expr::Symbol(kind),
302        render_class,
303        authority_class,
304        UnknownPolicy::Reject,
305    )
306}
307
308fn collab_part(kind: &Symbol) -> bool {
309    matches!(
310        kind.name.as_ref(),
311        "Evidence" | "Review" | "Vote" | "Patch" | "Receipt" | "Attest"
312    )
313}
314
315fn parent_move_evidence(parents: &[String]) -> Result<Vec<Symbol>> {
316    parents
317        .iter()
318        .map(|parent| {
319            let (_cid, move_text) = parent.split_once("#move=").ok_or_else(|| {
320                Error::Eval(format!(
321                    "BRIDGE parent {parent} is missing #move=<intent> evidence"
322                ))
323            })?;
324            if move_text.is_empty() {
325                return Err(Error::Eval(format!(
326                    "BRIDGE parent {parent} has empty move evidence"
327                )));
328            }
329            Ok(parse_symbol(move_text))
330        })
331        .collect()
332}
333
334fn parse_symbol(text: &str) -> Symbol {
335    match text.split_once('/') {
336        Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
337            Symbol::qualified(namespace.to_owned(), name.to_owned())
338        }
339        _ => Symbol::new(text.to_owned()),
340    }
341}