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}