Skip to main content

sim_lib_bridge/
materialize.rs

1use sim_codec_bridge::{BridgePacket, BridgePart};
2use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
3
4use crate::report::BridgeObligation;
5use crate::rx::effective_caps;
6
7/// Materialized data returned from a `Given` part under an explicit budget.
8#[derive(Clone, Debug, PartialEq)]
9pub struct GivenMaterialization {
10    /// Part id that was materialized.
11    pub id: Symbol,
12    /// Materialized expression payload.
13    pub payload: Expr,
14    /// Budget remaining after this materialization.
15    pub remaining_budget: usize,
16}
17
18/// Capability required to materialize a `Given` payload.
19pub fn bridge_given_materialize_capability() -> CapabilityName {
20    CapabilityName::new("bridge/given.materialize")
21}
22
23/// Capability required to expand context through a typed `Fetch` request.
24pub fn bridge_fetch_capability() -> CapabilityName {
25    CapabilityName::new("bridge/fetch")
26}
27
28/// Materializes one `Given` part. Callers must opt in and supply a budget.
29pub fn materialize_given(
30    cx: &mut Cx,
31    packet: &BridgePacket,
32    id: &Symbol,
33    budget: usize,
34) -> Result<GivenMaterialization> {
35    if budget == 0 {
36        return Err(Error::Eval(
37            "BRIDGE Given materialization budget exhausted".to_owned(),
38        ));
39    }
40    let caps = effective_caps(cx, packet)?;
41    if !caps.contains(&bridge_given_materialize_capability()) {
42        return Err(Error::CapabilityDenied {
43            capability: bridge_given_materialize_capability(),
44        });
45    }
46    let part = part_by_id(packet, id).ok_or_else(|| {
47        Error::Eval(format!(
48            "BRIDGE Given materialization missing part {}",
49            id.as_qualified_str()
50        ))
51    })?;
52    if part.kind != Symbol::qualified("bridge", "Given") {
53        return Err(Error::Eval(format!(
54            "BRIDGE materialization expects bridge/Given, found {}",
55            part.kind
56        )));
57    }
58    Ok(GivenMaterialization {
59        id: part.id.clone(),
60        payload: part.payload.clone(),
61        remaining_budget: budget - 1,
62    })
63}
64
65/// Builds the typed obligation used when context must be fetched explicitly.
66pub fn fetch_obligation(path: impl Into<String>, expected: impl Into<String>) -> BridgeObligation {
67    BridgeObligation::new(
68        path,
69        "typed context expansion requires Fetch",
70        expected,
71        "missing context",
72        vec![
73            "send Fetch packet".to_owned(),
74            "remove context reference".to_owned(),
75        ],
76    )
77}
78
79fn part_by_id<'a>(packet: &'a BridgePacket, id: &Symbol) -> Option<&'a BridgePart> {
80    packet.body.iter().find(|part| &part.id == id)
81}