1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Expr, Result, Symbol};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum RenderClass {
8 Structural,
10 Frame,
12 Data,
14 Evidence,
16 Review,
18 Vote,
20 Patch,
22 Fetch,
24 Return,
26 Receipt,
28 Extension,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum AuthorityClass {
35 Data,
37 Normative,
39 Callable,
41 Evidence,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
47pub enum UnknownPolicy {
48 Reject,
50 PreserveDataOnly,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct BridgePartSpec {
57 pub kind: Symbol,
59 pub shape_expr: Expr,
61 pub render_class: RenderClass,
63 pub authority_class: AuthorityClass,
65 pub unknown_policy: UnknownPolicy,
67}
68
69impl BridgePartSpec {
70 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 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
101pub struct BridgePartBook {
102 specs: BTreeMap<Symbol, BridgePartSpec>,
103}
104
105impl BridgePartBook {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn register(&mut self, spec: BridgePartSpec) {
113 self.specs.insert(spec.kind.clone(), spec);
114 }
115
116 pub fn spec(&self, kind: &Symbol) -> Option<&BridgePartSpec> {
118 self.specs.get(kind)
119 }
120
121 pub fn specs(&self) -> impl Iterator<Item = &BridgePartSpec> {
123 self.specs.values()
124 }
125
126 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#[derive(Clone, Debug, PartialEq, Eq)]
135pub struct BridgeBook {
136 pub parts: BridgePartBook,
138 pub moves: crate::BridgeMoveBook,
140 pub frames: crate::BridgeFrameBook,
142 pub profiles: crate::BridgeProfileBook,
144 pub warrant_policy: crate::BridgeWarrantPolicy,
146}
147
148impl BridgeBook {
149 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 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 pub fn with_part(mut self, spec: BridgePartSpec) -> Self {
177 self.parts.register(spec);
178 self
179 }
180
181 pub fn with_frame(mut self, spec: crate::FrameSpec) -> Self {
183 self.frames.register(spec);
184 self
185 }
186
187 pub fn with_profile(mut self, spec: crate::BridgeProfileSpec) -> Self {
189 self.profiles.register(spec);
190 self
191 }
192
193 pub fn with_warrant_policy(mut self, policy: crate::BridgeWarrantPolicy) -> Self {
195 self.warrant_policy = policy;
196 self
197 }
198
199 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
273pub 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}