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, JoinOrder, Kind, Role, rows_to, rows_under};
10use crate::origin::OriginTrail;
11use crate::plan::{DigestContract, MEMBERSHIP_LIMIT, Plan, PlannedMember, PlannedOutput};
12use crate::token::GeneratedTree;
13
14/// The role one rendered unit stands under.
15fn rendered_role<R: Role>(unit: &RenderedUnit<R>) -> R {
16    unit.role()
17}
18
19impl<R: Role> RenderedUnit<R> {
20    /// Materialize one planned member out of the tree a renderer produced.
21    ///
22    /// 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.
23    /// 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.
24    ///
25    /// # Errors
26    ///
27    /// Returns [`RenderError::BytesUnbounded`] where the rendered bytes pass [`RENDERED_BYTE_LIMIT`].
28    pub fn materialized(
29        planned: &PlannedMember<R>,
30        tree: GeneratedTree,
31    ) -> Result<Self, RenderError> {
32        let material = tree.canonical_bytes();
33        if material.len() > RENDERED_BYTE_LIMIT {
34            return Err(RenderError::BytesUnbounded {
35                role: planned.role.name(),
36                bound: RENDERED_BYTE_LIMIT,
37                observed: material.len(),
38            });
39        }
40        let output = &planned.output;
41        let position = u32::from(planned.role.slot());
42        let digest = Identity::derived(Transcript::under_projection(
43            identity::Role::OutputBytes,
44            &output.semantic_key,
45            &material,
46            position,
47        ));
48        let derived = Identity::derived(Transcript::under_projection(
49            identity::Role::RenderedUnit,
50            &output.semantic_key,
51            &material,
52            position,
53        ));
54        Ok(Self {
55            role: planned.role,
56            identity: derived,
57            semantic_key: output.semantic_key,
58            profile: output.expected_profile,
59            origin: output.origin.clone(),
60            address: output.address,
61            tree,
62            digest,
63        })
64    }
65
66    /// The seat this unit was rendered under.
67    #[must_use]
68    pub const fn role(&self) -> R {
69        self.role
70    }
71
72    /// This rendered unit's own identity.
73    #[must_use]
74    pub const fn identity(&self) -> Identity<identity::RenderedUnit> {
75        self.identity
76    }
77
78    /// The semantic key this unit answers to.
79    #[must_use]
80    pub const fn semantic_key(&self) -> Identity<identity::GeneratedUnit> {
81        self.semantic_key
82    }
83
84    /// Which delivery this unit lands in.
85    ///
86    /// 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.
87    #[must_use]
88    pub fn destination(&self) -> Destination {
89        self.role.destination()
90    }
91
92    /// The profile this unit was rendered under.
93    #[must_use]
94    pub const fn profile(&self) -> Profile {
95        self.profile
96    }
97
98    /// Where this unit came from.
99    #[must_use]
100    pub const fn origin(&self) -> &OriginTrail {
101        &self.origin
102    }
103
104    /// The address a publication writes this unit to, where its seat is one that writes to an address.
105    pub const fn address(&self) -> Option<OwnerIdentity> {
106        self.address
107    }
108
109    /// The token tree this unit is.
110    #[must_use]
111    pub const fn tree(&self) -> &GeneratedTree {
112        &self.tree
113    }
114
115    /// The digest over this unit's canonical bytes.
116    #[must_use]
117    pub const fn digest(&self) -> Identity<identity::OutputBytes> {
118        self.digest
119    }
120
121    /// This unit's canonical bytes — the exact material the digest was taken over.
122    ///
123    /// 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.
124    #[must_use]
125    pub fn bytes(&self) -> Vec<u8> {
126        self.tree.canonical_bytes()
127    }
128
129    /// The membership row this unit reconstructs — the renderer's own answer to what it materialized, in exactly the shape a plan states it.
130    #[must_use]
131    pub fn reconstructed(&self) -> PlannedMember<R> {
132        PlannedMember {
133            role: self.role,
134            output: PlannedOutput {
135                semantic_key: self.semantic_key,
136                origin: self.origin.clone(),
137                expected_profile: self.profile,
138                address: self.address,
139                digest_contract: DigestContract {
140                    anchored_to: self.semantic_key,
141                },
142            },
143        }
144    }
145
146    /// The digest recomputed from the bytes this unit carries, under one stated contract.
147    ///
148    /// 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.
149    #[must_use]
150    pub fn digest_under(&self, contract: DigestContract) -> Identity<identity::OutputBytes> {
151        let material = self.tree.canonical_bytes();
152        Identity::derived(Transcript::under_projection(
153            identity::Role::OutputBytes,
154            &contract.anchored_to,
155            &material,
156            u32::from(self.role.slot()),
157        ))
158    }
159}
160
161impl<R: Role> RenderedProjection<R> {
162    /// The one-unit rendering. Total: one unit always fits.
163    #[must_use]
164    pub fn of_one(unit: RenderedUnit<R>) -> Self {
165        Self {
166            units: NonEmpty::one(unit),
167        }
168    }
169
170    /// The several-unit rendering.
171    ///
172    /// # Errors
173    ///
174    /// 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.
175    pub fn materialized(units: Vec<RenderedUnit<R>>) -> Result<Self, RenderError> {
176        NonEmpty::new(units)
177            .map(|admitted| Self { units: admitted })
178            .map_err(|refusal| match refusal {
179                NonEmptyError::Empty(_) => RenderError::NothingRendered,
180                NonEmptyError::Overflow(overflow) => RenderError::UnitsUnbounded {
181                    bound: overflow.capacity,
182                    observed: overflow.offered,
183                },
184            })
185    }
186
187    /// The guaranteed first unit.
188    #[must_use]
189    pub fn first(&self) -> &RenderedUnit<R> {
190        self.units.first()
191    }
192
193    /// The rendered units, in the order the renderer produced them; structurally at least one.
194    #[must_use]
195    pub fn units(&self) -> &NonEmpty<RenderedUnit<R>, MEMBERSHIP_LIMIT> {
196        &self.units
197    }
198
199    /// The unit rendered under one seat, where one was.
200    pub fn under(&self, role: R) -> Option<&RenderedUnit<R>> {
201        self.units_under(role).next()
202    }
203
204    /// Every unit rendered under one seat, in rendering order.
205    ///
206    /// 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.
207    pub fn units_under(&self, role: R) -> impl Iterator<Item = &RenderedUnit<R>> {
208        rows_under(self.units(), role, rendered_role::<R>)
209    }
210
211    /// How many units were rendered under one seat.
212    #[must_use]
213    pub fn count_under(&self, role: R) -> usize {
214        self.units_under(role).count()
215    }
216
217    /// Every unit this rendering materialized into one delivery, in ROSTER order.
218    ///
219    /// A unit reaches a delivery through its seat's own constant answer, so this road elects nothing and interprets nothing.
220    ///
221    /// # Ordering
222    ///
223    /// 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.
224    /// 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.
225    pub fn units_to(&self, destination: Destination) -> impl Iterator<Item = &RenderedUnit<R>> {
226        rows_to(
227            self.units(),
228            destination,
229            JoinOrder::Roster(R::ALL),
230            rendered_role::<R>,
231        )
232    }
233
234    /// How many units this rendering materialized into one delivery.
235    #[must_use]
236    pub fn count_to(&self, destination: Destination) -> usize {
237        self.units_to(destination).count()
238    }
239
240    /// How many units were rendered; structurally at least one.
241    #[must_use]
242    pub fn count(&self) -> usize {
243        self.units.count()
244    }
245}
246
247impl<'plan, K: Kind> Output<'plan, K> {
248    /// The empty output one plan's renderer writes into.
249    #[must_use]
250    pub const fn over(plan: &'plan Plan<K>) -> Self {
251        Self {
252            plan,
253            units: Vec::new(),
254        }
255    }
256
257    /// Materialize the unit that fills one seat.
258    ///
259    /// 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.
260    ///
261    /// # Errors
262    ///
263    /// Returns [`RenderError::SeatUnplanned`] where this plan declares no member under the seat, and [`RenderError::BytesUnbounded`] where the tokens pass [`RENDERED_BYTE_LIMIT`].
264    pub fn unit(&mut self, role: K::Role, tree: GeneratedTree) -> Result<(), RenderError> {
265        let plan = self.plan;
266        let planned = plan
267            .membership()
268            .under(role)
269            .ok_or_else(|| RenderError::SeatUnplanned { role: role.name() })?;
270        let rendered = RenderedUnit::materialized(planned, tree)?;
271        self.units.push(rendered);
272        Ok(())
273    }
274
275    /// Everything the renderer wrote, as the rendering a proof closes over.
276    ///
277    /// # Errors
278    ///
279    /// Returns [`RenderError::NothingRendered`] where the renderer wrote no unit at all, and [`RenderError::UnitsUnbounded`] where it wrote past the membership magnitude.
280    /// 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.
281    pub fn rendered(self) -> Result<RenderedProjection<K::Role>, RenderError> {
282        RenderedProjection::materialized(self.units)
283    }
284}