macroonz_compiler/closure/type_guard.rs
1//! The closure 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 the home's central claim structural.
4//! A closure is built here, after the reconstruction agreed and over the deliveries this file joins and keeps, so the exact token stream each build receives is inside what was proved rather than assembled afterwards.
5//! The road from a closure to those deliveries is crate-internal, so a caller reaches tokens through the expansion that binds the plan, the proof, and the explanation, or it does not reach them.
6//! No other seam in the crate produces any of these values.
7
8use super::super::encode::claim;
9use super::super::prove::{addressed, examined, units_to};
10use super::{
11 CLOSURE_ISSUE_LIMIT, CarriedTokens, Closure, ClosureError, ClosureIssue, PartitionCargo,
12 PartitionedEmission,
13};
14use crate::bounded::{Capped, Capping, NonEmpty};
15use crate::identity::{self, ClosureId, Identity, PlanId, Provenance, Transcript};
16use crate::kind::{Destination, Kind, Role};
17use crate::plan::{Membership, Plan, PlannedMember};
18use crate::render::RenderedProjection;
19use crate::token::GeneratedTree;
20
21impl CarriedTokens {
22 /// The tokens one delivery carries, with the digest taken here over exactly those bytes.
23 ///
24 /// Private to the guard, and the one road: no caller supplies a digest, so a delivery cannot carry the digest of bytes it does not carry.
25 /// The digest is anchored on the PLAN and positioned at the delivery's own roster position, so two deliveries of one plan that happened to join to the same bytes are still two digests — which is what keeps an expansion's declaration-site answer from standing in for its carrier's.
26 fn joined(plan: PlanId, destination: Destination, tree: GeneratedTree) -> Self {
27 let raw = tree.canonical_bytes();
28 let digest = Identity::derived(Transcript::under_projection(
29 identity::Role::OutputBytes,
30 &plan,
31 &raw,
32 delivery_position(destination),
33 ));
34 Self { tree, digest }
35 }
36
37 /// The tokens themselves.
38 #[must_use]
39 pub const fn tree(&self) -> &GeneratedTree {
40 &self.tree
41 }
42
43 /// The digest of exactly these bytes, as the proving closure's identity commits to it.
44 #[must_use]
45 pub const fn digest(&self) -> Identity<identity::OutputBytes> {
46 self.digest
47 }
48}
49
50impl PartitionCargo {
51 /// The tokens this delivery carries, where it carries any.
52 ///
53 /// # Nonclaims
54 ///
55 /// It answers with nothing where the plan declared no member into this delivery.
56 /// That is a stated posture rather than a missing value, and this road never turns "nothing was planned here" into "a cargo of no tokens".
57 #[must_use]
58 pub const fn tokens(&self) -> Option<&GeneratedTree> {
59 match self {
60 Self::NothingPlanned => None,
61 Self::Carried(carried) => Some(carried.tree()),
62 }
63 }
64}
65
66impl PartitionedEmission {
67 /// Split one proved rendering across the deliveries its seats declared.
68 ///
69 /// Private to the guard, with one caller: [`Closure::proved`].
70 ///
71 /// # Ordering
72 ///
73 /// The delivery roster is the quantifier: every joined delivery is built whether or not anything was planned into it, so a delivery that carries nothing says so rather than being left out of the walk.
74 ///
75 /// # Errors
76 ///
77 /// Returns [`ClosureIssue::JoinedTreeUnbounded`] naming the delivery whose joined tree outgrew the declared token magnitude.
78 fn over<R: Role>(
79 plan: PlanId,
80 rendered: &RenderedProjection<R>,
81 ) -> Result<Self, ClosureIssue<R>> {
82 Ok(Self {
83 declaration_site: joined_cargo(plan, rendered, Destination::DeclarationSite)?,
84 test_carrier: joined_cargo(plan, rendered, Destination::TestCarrier)?,
85 bench_carrier: joined_cargo(plan, rendered, Destination::BenchCarrier)?,
86 })
87 }
88
89 /// What the declaration site expands into — the tokens the consumer's normal build compiles.
90 pub const fn declaration_site(&self) -> &PartitionCargo {
91 &self.declaration_site
92 }
93
94 /// The deferred cargo the consumer's test target invokes.
95 pub const fn test_carrier(&self) -> &PartitionCargo {
96 &self.test_carrier
97 }
98
99 /// The deferred cargo the consumer's bench target invokes.
100 pub const fn bench_carrier(&self) -> &PartitionCargo {
101 &self.bench_carrier
102 }
103
104 /// The cargo one joined delivery carries.
105 ///
106 /// Exhaustive over the roster on purpose: a delivery added to [`Destination`] stops compiling HERE until somebody says what it carries, so no delivery can be admitted and left unrouted.
107 ///
108 /// # Nonclaims
109 ///
110 /// It answers with nothing for the publication delivery, which is not joined: a published artifact is its rendered unit at the address the plan named for it, and it is read as one.
111 #[must_use]
112 pub const fn joined(&self, destination: Destination) -> Option<&PartitionCargo> {
113 match destination {
114 Destination::DeclarationSite => Some(&self.declaration_site),
115 Destination::TestCarrier => Some(&self.test_carrier),
116 Destination::BenchCarrier => Some(&self.bench_carrier),
117 Destination::PublicationArtifact => None,
118 }
119 }
120}
121
122impl<R: Role> ClosureError<R> {
123 /// The refusal one established issue makes.
124 pub fn of(issue: ClosureIssue<R>) -> Self {
125 Self {
126 body: Capped::all(NonEmpty::one(issue)),
127 }
128 }
129
130 /// The refusal a pass whose checks co-establish makes.
131 ///
132 /// The caller arrives holding every issue its pass established, so the posture the body writes is about the REPORT and never about the pass: where the issues fit it carries all of them, and where they do not it carries what fits and counts the rest.
133 pub fn over(first: ClosureIssue<R>, rest: Vec<ClosureIssue<R>>) -> Self {
134 Self {
135 body: Capped::first_n(first, rest.into_iter()),
136 }
137 }
138
139 /// The first issue the pass established, which every refusal has.
140 #[must_use]
141 pub fn first_issue(&self) -> &ClosureIssue<R> {
142 self.body.items().first()
143 }
144
145 /// Every issue this refusal carries, in the order the pass established them; structurally at least one.
146 #[must_use]
147 pub fn issues(&self) -> &NonEmpty<ClosureIssue<R>, CLOSURE_ISSUE_LIMIT> {
148 self.body.items()
149 }
150
151 /// Whether this refusal carries every issue its pass established.
152 #[must_use]
153 pub const fn capping(&self) -> Capping {
154 self.body.capping()
155 }
156}
157
158impl<R: Role> Closure<R> {
159 /// Prove the closure between one plan's membership and one rendering.
160 ///
161 /// # Construction
162 ///
163 /// The identity is derived at [`Role::Closure`](crate::identity::Role::Closure), anchored on the plan's own identity, over the complete claim: the planned membership in roster order, the roster's own length, the identity and digest of the unit that stood under each seat, and every joined delivery's digest.
164 /// So the identity names the whole agreement rather than a sample of it, and the bytes a caller emits into any build are bytes this identity names.
165 ///
166 /// # The two halves are one value
167 ///
168 /// The proof takes the PLAN, not a plan identity beside a membership.
169 /// Separate arguments are separable: nothing in the types would stop a caller handing one plan's identity beside another's membership, and the closure would be born naming the first while proving the second.
170 ///
171 /// # Errors
172 ///
173 /// Returns [`ClosureError`] naming every seat the two disagree at, the delivery whose joined tree outgrew its magnitude, or the address two published units stand at.
174 /// Every disagreement of one pass is reported together: a caller repairing a rendering one seat per attempt is a caller the check failed.
175 pub fn proved<K: Kind<Role = R>>(
176 plan: &Plan<K>,
177 rendered: RenderedProjection<R>,
178 ) -> Result<Self, ClosureError<R>> {
179 let planned = plan.membership();
180 let named = plan.identity();
181 let (issues, rows) = examined(planned, &rendered);
182 if let Some(refusal) = refused(issues) {
183 return Err(refusal);
184 }
185 let reconstructed = rebuilt(rows)?;
186
187 // The theorem, stated over the whole set: seat by seat, the rebuild and
188 // the plan hold the same members. Every check above is about one seat;
189 // this one is about the collection, which a first-per-seat walk could
190 // never establish.
191 let disagreements: Vec<ClosureIssue<R>> = R::ALL
192 .iter()
193 .copied()
194 .filter(|role| !reconstructed.agrees_under(planned, *role))
195 .map(|role| ClosureIssue::MembershipDisagreement { role })
196 .collect();
197 if let Some(refusal) = refused(disagreements) {
198 return Err(refusal);
199 }
200
201 // Occupancy in the publication delivery is occupancy by ADDRESS, so it
202 // is established after the seats agree and before anything is joined:
203 // two units at one address is a defect in what the rendering would
204 // WRITE rather than in what it rendered.
205 if let Some(refusal) = refused(addressed(&rendered)) {
206 return Err(refusal);
207 }
208
209 let emission = PartitionedEmission::over(named, &rendered).map_err(ClosureError::of)?;
210 let material = claim(planned, &rendered, &emission);
211 let (derived, provenance) = ClosureId::derived_with_provenance(
212 Transcript::under_projection(identity::Role::Closure, &named, &material, 0),
213 );
214 Ok(Self {
215 plan: named,
216 reconstructed,
217 rendered,
218 emission,
219 identity: derived,
220 provenance,
221 })
222 }
223
224 /// The membership rebuilt out of the rendered units.
225 #[must_use]
226 pub const fn reconstructed(&self) -> &Membership<R> {
227 &self.reconstructed
228 }
229
230 /// What the renderer produced.
231 #[must_use]
232 pub const fn rendered(&self) -> &RenderedProjection<R> {
233 &self.rendered
234 }
235
236 /// The deliveries this closure proved, joined in roster order and owned here.
237 ///
238 /// Crate-internal, with one caller: the binding that seals an expansion.
239 /// This is the closure's own proof material rather than a road to tokens — a caller that could read it here would be emitting off a proof without the plan it was proved against or the explanation written over it, which is the binding's whole reason to exist.
240 pub(crate) const fn emission(&self) -> &PartitionedEmission {
241 &self.emission
242 }
243
244 /// The plan this closure was proved against.
245 #[must_use]
246 pub const fn plan(&self) -> PlanId {
247 self.plan
248 }
249
250 /// This closure's own identity.
251 #[must_use]
252 pub const fn identity(&self) -> ClosureId {
253 self.identity
254 }
255
256 /// The record of how that identity was derived.
257 #[must_use]
258 pub const fn provenance(&self) -> &Provenance {
259 &self.provenance
260 }
261}
262
263/// The refusal one established issue list amounts to, or nothing where the list is empty.
264///
265/// One road for every pass in [`Closure::proved`], so no pass can establish issues and then walk on past them.
266fn refused<R: Role>(issues: Vec<ClosureIssue<R>>) -> Option<ClosureError<R>> {
267 let mut established = issues.into_iter();
268 let first = established.next()?;
269 Some(ClosureError::over(first, established.collect()))
270}
271
272/// The rows the per-seat pass rebuilt, declared as a complete output set.
273///
274/// Reached only after that pass established nothing, so every seat holds at most one member and the two ways declaring can still fail are the two named here.
275fn rebuilt<R: Role>(rows: Vec<PlannedMember<R>>) -> Result<Membership<R>, ClosureError<R>> {
276 let observed = u32::try_from(rows.len()).unwrap_or(u32::MAX);
277 let mut members = rows.into_iter();
278 let Some(first) = members.next() else {
279 return Err(ClosureError::of(ClosureIssue::ReconstructionEmpty));
280 };
281 Membership::declared(first, members.collect())
282 .map_err(|_| ClosureError::of(ClosureIssue::ReconstructionUndeclarable { observed }))
283}
284
285/// The cargo one delivery of one rendering carries.
286///
287/// Private to the guard, with one caller: the partitioning inside [`Closure::proved`].
288/// The join is a step inside the proof, so there is no second road to a joined tree the closure identity says nothing about.
289fn joined_cargo<R: Role>(
290 plan: PlanId,
291 rendered: &RenderedProjection<R>,
292 destination: Destination,
293) -> Result<PartitionCargo, ClosureIssue<R>> {
294 let mut joined: Option<GeneratedTree> = None;
295 for unit in units_to(rendered, destination) {
296 joined = Some(match joined {
297 Some(tree) => tree
298 .joined(unit.tree())
299 .map_err(|_| ClosureIssue::JoinedTreeUnbounded { destination })?,
300 None => unit.tree().clone(),
301 });
302 }
303 let Some(tree) = joined else {
304 return Ok(PartitionCargo::NothingPlanned);
305 };
306 Ok(PartitionCargo::Carried(CarriedTokens::joined(
307 plan,
308 destination,
309 tree,
310 )))
311}
312
313/// The position one delivery's joined tokens are digested at, inside one plan's own sequence.
314///
315/// Preimage material, so a row is APPENDED and never renumbered.
316const fn delivery_position(destination: Destination) -> u32 {
317 match destination {
318 Destination::DeclarationSite => 0,
319 Destination::TestCarrier => 1,
320 Destination::BenchCarrier => 2,
321 Destination::PublicationArtifact => 3,
322 }
323}