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}