Skip to main content

macroonz_compiler/explanation/
type_guard.rs

1//! The explanation 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 two central claims structural.
4//! A view is completed here, after the coverage pass agreed, so there is no partial view for a reader to mistake for a complete one.
5//! Its parentage is taken here too — off the plan and the proof themselves — and its own identity is minted over the three, so a view naming a plan or a closure it was not answered over is a value nobody can build.
6
7use super::super::encode::seats_into;
8use super::super::establish::coverage_issues;
9use super::{
10    AnsweredOutput, EXPLANATION_ISSUE_LIMIT, ExplanationError, ExplanationIssue, UniversalAnswer,
11    UniversalQuestion, View,
12};
13use crate::bounded::{Bounded, Capped, Capping, NonEmpty, Overflow};
14use crate::closure::Closure;
15use crate::identity::{
16    self, ClosureId, ExplanationId, PlanId, Provenance, Transcript, encode_bytes,
17};
18use crate::kind::{Answer, Kind, Question, Role};
19use crate::plan::Plan;
20use crate::render::RenderedProjection;
21use core::marker::PhantomData;
22
23/// The refusal one established issue list amounts to, or nothing where the passes established none.
24///
25/// One road for everything [`View::complete`](super::View::complete) establishes: the coverage pass and the output pass co-establish into one list, so no pass can establish issues and then walk on past them, and no caller repairs one pass's findings only to meet the other's.
26fn refused(issues: Vec<ExplanationIssue>) -> Option<ExplanationError> {
27    let mut established = issues.into_iter();
28    let first = established.next()?;
29    Some(ExplanationError::over(first, established.collect()))
30}
31
32impl AnsweredOutput {
33    /// Every seat's half of the output-and-digest answer, in roster order, read off the proof itself.
34    ///
35    /// Roster order and never rendering order, so the answer does not turn on the sequence a renderer happened to write its units in.
36    /// The whole roster and never a chosen row: a kind may fill several seats, and an answer naming fewer than all of them would flatten the expansion's denominator to whichever row was picked.
37    /// Seated once: the request road composes its answer through this walk, and [`View::complete`] rebuilds the same walk to compare — one derivation, so the claim and its check cannot drift apart.
38    pub(crate) fn roster<R: Role>(rendered: &RenderedProjection<R>) -> Vec<Self> {
39        R::ALL
40            .iter()
41            .copied()
42            .filter_map(|role| rendered.under(role))
43            .map(|unit| Self {
44                output: Box::new(unit.reconstructed().output),
45                digest: unit.digest(),
46            })
47            .collect()
48    }
49}
50
51/// The issue the output answer establishes against the proof's own rendered roster, or nothing where it restates it exactly.
52///
53/// The lawful rows are derivable from the closure, so this pass rebuilds them and compares whole — count, order, members, and digests in one equality.
54/// An absent output answer is the coverage pass's finding and establishes nothing here.
55fn outputs_beside_proof<R: Role>(
56    universal: &[UniversalAnswer],
57    closure: &Closure<R>,
58) -> Vec<ExplanationIssue> {
59    let supplied = universal.iter().find_map(|answer| match answer {
60        UniversalAnswer::OutputAndDigest { outputs } => Some(outputs),
61        UniversalAnswer::Kind { .. }
62        | UniversalAnswer::Owner { .. }
63        | UniversalAnswer::CausingDeclarations { .. }
64        | UniversalAnswer::Profile { .. }
65        | UniversalAnswer::Assumptions { .. }
66        | UniversalAnswer::Invalidators { .. }
67        | UniversalAnswer::RelatedDispositions { .. }
68        | UniversalAnswer::Repairs { .. } => None,
69    });
70    let Some(supplied) = supplied else {
71        return Vec::new();
72    };
73    let lawful = AnsweredOutput::roster(closure.rendered());
74    if supplied.len() == lawful.len() && supplied.iter().eq(lawful.iter()) {
75        return Vec::new();
76    }
77    let diverges = supplied
78        .iter()
79        .zip(lawful.iter())
80        .position(|(offered, proved)| offered != proved)
81        .unwrap_or_else(|| supplied.len().min(lawful.len()));
82    vec![ExplanationIssue::OutputsBesideTheProof {
83        expected: u16::try_from(lawful.len()).unwrap_or(u16::MAX),
84        observed: u16::try_from(supplied.len()).unwrap_or(u16::MAX),
85        diverges: u16::try_from(diverges).unwrap_or(u16::MAX),
86    }]
87}
88
89/// The supplied answers, restated in their roster's own declared order.
90///
91/// Reached only after the coverage pass agreed, which is what makes it total: every row of the roster has exactly one answer and no answer stands outside it, so the walk seats each answer once and leaves none behind.
92/// The roster is the quantifier, so the result's order is the protocol's rather than a call site's.
93fn in_roster_order<Q: Question>(answers: Vec<Q::Answer>) -> Vec<Q::Answer> {
94    let mut supplied: Vec<Option<Q::Answer>> = answers.into_iter().map(Some).collect();
95    let mut ordered: Vec<Q::Answer> = Vec::with_capacity(supplied.len());
96    for question in Q::ALL {
97        let seated = supplied.iter_mut().find(|held| {
98            held.as_ref()
99                .is_some_and(|answer| answer.question() == *question)
100        });
101        if let Some(answer) = seated.and_then(Option::take) {
102            ordered.push(answer);
103        }
104    }
105    ordered
106}
107
108impl ExplanationError {
109    /// The refusal one established issue makes.
110    pub fn of(issue: ExplanationIssue) -> Self {
111        Self {
112            body: Capped::all(NonEmpty::one(issue)),
113        }
114    }
115
116    /// The refusal a pass whose checks co-establish makes.
117    ///
118    /// 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.
119    pub fn over(first: ExplanationIssue, rest: Vec<ExplanationIssue>) -> Self {
120        Self {
121            body: Capped::first_n(first, rest.into_iter()),
122        }
123    }
124
125    /// The refusal a seat bound makes, out of the two counts the overflow already carries.
126    pub fn bounded(overflow: Overflow) -> Self {
127        Self::of(ExplanationIssue::SeatBoundExceeded {
128            bound: u64::try_from(overflow.capacity).unwrap_or(u64::MAX),
129            observed: u64::try_from(overflow.offered).unwrap_or(u64::MAX),
130        })
131    }
132
133    /// The first issue the pass established, which every refusal has.
134    #[must_use]
135    pub fn first_issue(&self) -> &ExplanationIssue {
136        self.body.items().first()
137    }
138
139    /// Every issue this refusal carries, in the order the pass established them; structurally at least one.
140    #[must_use]
141    pub fn issues(&self) -> &NonEmpty<ExplanationIssue, EXPLANATION_ISSUE_LIMIT> {
142        self.body.items()
143    }
144
145    /// Whether this refusal carries every issue its pass established.
146    #[must_use]
147    pub const fn capping(&self) -> Capping {
148        self.body.capping()
149    }
150}
151
152impl<K: Kind> View<K> {
153    /// Complete one view over the universal questions and the kind's own, answered over one plan and the proof of its rendering.
154    ///
155    /// # The parentage is taken, never supplied
156    ///
157    /// The plan arrives as the PLAN and the closure as the PROOF, and both identities are read off them here.
158    /// A road that took two identities beside the answers would take two values any caller can spell, and the view it built would name a parentage it was never written over — which is a complete, well-formed explanation about something else.
159    /// A [`Closure`] is reachable only by proving a rendering against a plan, so a caller standing here has done that or has nothing to hand in, and its role roster is the kind's own.
160    ///
161    /// # The explanation transcript
162    ///
163    /// This is a mint site, so its content grammar is stated in full.
164    /// The identity is derived under [`Role::Explanation`](crate::identity::Role::Explanation), anchored on the CLOSURE's identity at full width — an explanation is written after a closure and over it — at position zero, over
165    ///
166    /// ```text
167    /// content = bytes(plan) || u64be(universal seats) || seat* || u64be(declared seats) || seat*
168    /// ```
169    ///
170    /// where each `seat` is the question's roster position in two big-endian bytes followed by the answer's own canonical bytes, in that roster's declared order.
171    /// The two rosters are written behind two counts, so the split between them is framed rather than inferred: a universal seat and a declared seat may share a position.
172    /// Human prose is not a member — a rendered line is a projection of a typed answer, so a preimage carrying one would commit to a rendering rather than to what was answered.
173    ///
174    /// # Errors
175    ///
176    /// Returns [`ExplanationError`] naming every unanswered question, every doubled question, every answer standing outside its own roster, the seat bound where a kind's roster outgrows it, and an output answer that does not restate the proof's own rendered roster.
177    /// All of them together: a caller repairing a view one question per attempt is a caller the protocol failed.
178    pub fn complete(
179        plan: &Plan<K>,
180        closure: &Closure<K::Role>,
181        universal: Vec<UniversalAnswer>,
182        declared: Vec<<K::Question as Question>::Answer>,
183    ) -> Result<Self, ExplanationError> {
184        let mut issues = coverage_issues::<K>(&universal, &declared);
185        issues.extend(outputs_beside_proof(&universal, closure));
186        if let Some(refusal) = refused(issues) {
187            return Err(refusal);
188        }
189        let seated_universal = in_roster_order::<UniversalQuestion>(universal);
190        let seated_declared = in_roster_order::<K::Question>(declared);
191
192        let plan_identity = plan.identity();
193        let closure_identity = closure.identity();
194        let mut content = Vec::new();
195        encode_bytes(plan_identity.as_bytes(), &mut content);
196        seats_into(&seated_universal, &mut content);
197        seats_into(&seated_declared, &mut content);
198        let (derived, provenance) =
199            ExplanationId::derived_with_provenance(Transcript::under_projection(
200                identity::Role::Explanation,
201                &closure_identity,
202                &content,
203                0,
204            ));
205
206        let held_universal = Bounded::new(seated_universal).map_err(ExplanationError::bounded)?;
207        let held_declared = Bounded::new(seated_declared).map_err(ExplanationError::bounded)?;
208        Ok(Self {
209            plan: plan_identity,
210            closure: closure_identity,
211            universal: held_universal,
212            declared: held_declared,
213            identity: derived,
214            provenance,
215            kind: PhantomData,
216        })
217    }
218
219    /// This view's own identity — the name a binding commits to.
220    #[must_use]
221    pub const fn identity(&self) -> ExplanationId {
222        self.identity
223    }
224
225    /// The record of how that identity was derived.
226    #[must_use]
227    pub const fn provenance(&self) -> &Provenance {
228        &self.provenance
229    }
230
231    /// The plan this view was answered over.
232    ///
233    /// Read back so a binding establishes that the plan it seals is the plan the answers are about, rather than assuming it.
234    #[must_use]
235    pub const fn plan(&self) -> PlanId {
236        self.plan
237    }
238
239    /// The proved closure this view was answered over, on the same terms.
240    #[must_use]
241    pub const fn closure(&self) -> ClosureId {
242        self.closure
243    }
244
245    /// The universal answers, in the compiler's roster order.
246    #[must_use]
247    pub fn universal(&self) -> &[UniversalAnswer] {
248        self.universal.as_slice()
249    }
250
251    /// The kind's own answers, in the kind's declared roster order.
252    #[must_use]
253    pub fn declared(&self) -> &[<K::Question as Question>::Answer] {
254        self.declared.as_slice()
255    }
256
257    /// How many seats this view fills, across both rosters.
258    #[must_use]
259    pub fn seats(&self) -> usize {
260        self.universal.len().saturating_add(self.declared.len())
261    }
262}