Skip to main content

macroonz_compiler/render/
type_guard.rs

1//! The render home's invariant nucleus: every road that reaches a private field.
2//!
3//! Declared inside `types.rs` as its own child, which is what makes this home's central claim structural.
4//! A unit's identity and the digest of its bytes are taken here, over the tree's own canonical bytes, under the semantic key the planned member declares — so a renderer cannot hand in a digest of bytes it did not emit, and cannot answer to a seat no plan declared.
5
6use super::{Output, RENDERED_BYTE_LIMIT, RenderError, RenderedProjection, RenderedUnit};
7use crate::bounded::{NonEmpty, NonEmptyError};
8use crate::identity::{self, Identity, OwnerIdentity, Profile, Transcript};
9use crate::kind::{Destination, Kind, Role};
10use crate::origin::OriginTrail;
11use crate::plan::{DigestContract, MEMBERSHIP_LIMIT, Plan, PlannedMember, PlannedOutput};
12use crate::token::GeneratedTree;
13
14impl<R: Role> RenderedUnit<R> {
15    /// Materialize one planned member out of the tree a renderer produced.
16    ///
17    /// A rendered unit IS a planned member plus the bytes that answer it, so every fact the member already states is read off it rather than restated at the call — nothing here can pair one seat's key with another seat's origin.
18    /// The digest and this unit's own identity are both taken here, over the tree's canonical bytes, under that key at that seat's roster position.
19    ///
20    /// # Errors
21    ///
22    /// Returns [`RenderError::BytesUnbounded`] where the rendered bytes pass [`RENDERED_BYTE_LIMIT`].
23    pub fn materialized(
24        planned: &PlannedMember<R>,
25        tree: GeneratedTree,
26    ) -> Result<Self, RenderError> {
27        let material = tree.canonical_bytes();
28        if material.len() > RENDERED_BYTE_LIMIT {
29            return Err(RenderError::BytesUnbounded {
30                role: planned.role.name(),
31                bound: RENDERED_BYTE_LIMIT,
32                observed: material.len(),
33            });
34        }
35        let output = &planned.output;
36        let position = u32::from(planned.role.slot());
37        let digest = Identity::derived(Transcript::under_projection(
38            identity::Role::OutputBytes,
39            &output.semantic_key,
40            &material,
41            position,
42        ));
43        let derived = Identity::derived(Transcript::under_projection(
44            identity::Role::RenderedUnit,
45            &output.semantic_key,
46            &material,
47            position,
48        ));
49        Ok(Self {
50            role: planned.role,
51            identity: derived,
52            semantic_key: output.semantic_key,
53            profile: output.expected_profile,
54            origin: output.origin.clone(),
55            address: output.address,
56            tree,
57            digest,
58        })
59    }
60
61    /// The seat this unit was rendered under.
62    #[must_use]
63    pub const fn role(&self) -> R {
64        self.role
65    }
66
67    /// This rendered unit's own identity.
68    #[must_use]
69    pub const fn identity(&self) -> Identity<identity::RenderedUnit> {
70        self.identity
71    }
72
73    /// The semantic key this unit answers to.
74    #[must_use]
75    pub const fn semantic_key(&self) -> Identity<identity::GeneratedUnit> {
76        self.semantic_key
77    }
78
79    /// Which delivery this unit lands in.
80    ///
81    /// Read off the seat and never stored: a delivery a unit could disagree with its own role about would be a second answer to a question the roster already answers.
82    #[must_use]
83    pub fn destination(&self) -> Destination {
84        self.role.destination()
85    }
86
87    /// The profile this unit was rendered under.
88    #[must_use]
89    pub const fn profile(&self) -> Profile {
90        self.profile
91    }
92
93    /// Where this unit came from.
94    #[must_use]
95    pub const fn origin(&self) -> &OriginTrail {
96        &self.origin
97    }
98
99    /// The address a publication writes this unit to, where its seat is one that writes to an address.
100    pub const fn address(&self) -> Option<OwnerIdentity> {
101        self.address
102    }
103
104    /// The token tree this unit is.
105    #[must_use]
106    pub const fn tree(&self) -> &GeneratedTree {
107        &self.tree
108    }
109
110    /// The digest over this unit's canonical bytes.
111    #[must_use]
112    pub const fn digest(&self) -> Identity<identity::OutputBytes> {
113        self.digest
114    }
115
116    /// This unit's canonical bytes — the exact material the digest was taken over.
117    ///
118    /// Derived from the tree on every reading rather than kept beside it, so there is no second copy of one unit's bytes to disagree with the tree.
119    #[must_use]
120    pub fn bytes(&self) -> Vec<u8> {
121        self.tree.canonical_bytes()
122    }
123
124    /// The membership row this unit reconstructs — the renderer's own answer to what it materialized, in exactly the shape a plan states it.
125    #[must_use]
126    pub fn reconstructed(&self) -> PlannedMember<R> {
127        PlannedMember {
128            role: self.role,
129            output: PlannedOutput {
130                semantic_key: self.semantic_key,
131                origin: self.origin.clone(),
132                expected_profile: self.profile,
133                address: self.address,
134                digest_contract: DigestContract {
135                    anchored_to: self.semantic_key,
136                },
137            },
138        }
139    }
140
141    /// The digest recomputed from the bytes this unit carries, under one stated contract.
142    ///
143    /// A proof compares this against [`RenderedUnit::digest`]: a digest that does not survive being recomputed under the plan's own contract is a digest of something else.
144    #[must_use]
145    pub fn digest_under(&self, contract: DigestContract) -> Identity<identity::OutputBytes> {
146        let material = self.tree.canonical_bytes();
147        Identity::derived(Transcript::under_projection(
148            identity::Role::OutputBytes,
149            &contract.anchored_to,
150            &material,
151            u32::from(self.role.slot()),
152        ))
153    }
154}
155
156impl<R: Role> RenderedProjection<R> {
157    /// The one-unit rendering. Total: one unit always fits.
158    #[must_use]
159    pub fn of_one(unit: RenderedUnit<R>) -> Self {
160        Self {
161            units: NonEmpty::one(unit),
162        }
163    }
164
165    /// The several-unit rendering.
166    ///
167    /// # Errors
168    ///
169    /// Returns [`RenderError::NothingRendered`] where no unit was offered, and [`RenderError::UnitsUnbounded`] where the rendering outgrows [`MEMBERSHIP_LIMIT`] — the two counts read off the collection that refused rather than restated here.
170    pub fn materialized(units: Vec<RenderedUnit<R>>) -> Result<Self, RenderError> {
171        NonEmpty::new(units)
172            .map(|admitted| Self { units: admitted })
173            .map_err(|refusal| match refusal {
174                NonEmptyError::Empty(_) => RenderError::NothingRendered,
175                NonEmptyError::Overflow(overflow) => RenderError::UnitsUnbounded {
176                    bound: overflow.capacity,
177                    observed: overflow.offered,
178                },
179            })
180    }
181
182    /// The guaranteed first unit.
183    #[must_use]
184    pub fn first(&self) -> &RenderedUnit<R> {
185        self.units.first()
186    }
187
188    /// The rendered units, in the order the renderer produced them; structurally at least one.
189    #[must_use]
190    pub fn units(&self) -> &NonEmpty<RenderedUnit<R>, MEMBERSHIP_LIMIT> {
191        &self.units
192    }
193
194    /// The unit rendered under one seat, where one was.
195    pub fn under(&self, role: R) -> Option<&RenderedUnit<R>> {
196        self.units().iter().find(|unit| unit.role() == role)
197    }
198
199    /// Every unit rendered under one seat, in rendering order.
200    ///
201    /// The road a set comparison walks: comparing two renderings by their first unit per seat would agree about two renderings that differ in their second, which is exactly what a doubled seat produces.
202    pub fn units_under(&self, role: R) -> impl Iterator<Item = &RenderedUnit<R>> {
203        self.units().iter().filter(move |unit| unit.role() == role)
204    }
205
206    /// How many units were rendered under one seat.
207    #[must_use]
208    pub fn count_under(&self, role: R) -> usize {
209        self.units_under(role).count()
210    }
211
212    /// Every unit this rendering materialized into one delivery, in ROSTER order.
213    ///
214    /// A unit reaches a delivery through its seat's own constant answer, so this road elects nothing and interprets nothing.
215    ///
216    /// # Ordering
217    ///
218    /// Roster order and never rendering order: the roster is declared and a renderer's own sequencing is not, so what a join writes is stable under a renderer that happened to produce its units in another order.
219    /// Every unit standing under a seat is yielded rather than the first, because a rendering that doubled a seat is one the proof refuses and a reading that quietly dropped the second unit would hide the doubling from anybody looking here instead.
220    pub fn units_to(&self, destination: Destination) -> impl Iterator<Item = &RenderedUnit<R>> {
221        R::ALL
222            .iter()
223            .copied()
224            .filter(move |role| role.destination() == destination)
225            .flat_map(move |role| self.units_under(role))
226    }
227
228    /// How many units this rendering materialized into one delivery.
229    #[must_use]
230    pub fn count_to(&self, destination: Destination) -> usize {
231        self.units_to(destination).count()
232    }
233
234    /// How many units were rendered; structurally at least one.
235    #[must_use]
236    pub fn count(&self) -> usize {
237        self.units.count()
238    }
239}
240
241impl<'plan, K: Kind> Output<'plan, K> {
242    /// The empty output one plan's renderer writes into.
243    #[must_use]
244    pub const fn over(plan: &'plan Plan<K>) -> Self {
245        Self {
246            plan,
247            units: Vec::new(),
248        }
249    }
250
251    /// Materialize the unit that fills one seat.
252    ///
253    /// Naming the seat is the whole call: the key the unit answers to, where it came from, the profile expected to render it, and the address it publishes to are that seat's planned member's, read here.
254    ///
255    /// # Errors
256    ///
257    /// Returns [`RenderError::SeatUnplanned`] where this plan declares no member under the seat, and [`RenderError::BytesUnbounded`] where the tokens pass [`RENDERED_BYTE_LIMIT`].
258    pub fn unit(&mut self, role: K::Role, tree: GeneratedTree) -> Result<(), RenderError> {
259        let plan = self.plan;
260        let planned = plan
261            .membership()
262            .under(role)
263            .ok_or_else(|| RenderError::SeatUnplanned { role: role.name() })?;
264        let rendered = RenderedUnit::materialized(planned, tree)?;
265        self.units.push(rendered);
266        Ok(())
267    }
268
269    /// Everything the renderer wrote, as the rendering a proof closes over.
270    ///
271    /// # Errors
272    ///
273    /// Returns [`RenderError::NothingRendered`] where the renderer wrote no unit at all, and [`RenderError::UnitsUnbounded`] where it wrote past the membership magnitude.
274    /// It does not answer for a seat left unfilled or filled twice: those are disagreements between this rendering and the plan, and the proof that compares the two is what states them.
275    pub fn rendered(self) -> Result<RenderedProjection<K::Role>, RenderError> {
276        RenderedProjection::materialized(self.units)
277    }
278}