Skip to main content

macroonz_compiler/plan/
types.rs

1//! The plan home's declarations: the account a request walked in with, the context it is decided under, its complete output set, what invalidates it, the plan itself, and how planning refuses.
2//!
3//! Declarations only.
4//! Every road that reaches a private field lives in `type_guard.rs`, this file's own child, which is what makes the output firewall structural: a plan's declared set is whatever one of those roads admitted, and there is no other way in.
5
6use crate::bounded::{Bounded, Capped, NonEmpty};
7use crate::identity::{self, Identity, OwnerFact, OwnerIdentity, PlanId, Profile, Provenance};
8use crate::kind::{Kind, Role};
9use crate::origin::{DecisionTrace, Nonclaim, OriginTrail};
10
11#[path = "type_guard.rs"]
12mod guard;
13
14/// Captured declarations one account may name beside its own commitment.
15///
16/// A cause list cut to fit is byte for byte the shape of a complete one, so an account past this refuses rather than narrating a partial cause.
17pub const DEPENDENCY_LIMIT: usize = 64;
18
19/// Triggers one plan may watch.
20///
21/// The shared derivation alone reaches sixty-seven — the content commitment, up to sixty-four declared dependencies, the profile, and the generator — and a kind adds whatever its own anchors require on top, so the roster is wider than the derived part rather than exactly it.
22pub const TRIGGER_LIMIT: usize = 128;
23
24/// Outputs one plan may declare.
25pub const MEMBERSHIP_LIMIT: usize = 32;
26
27/// Nonclaims one plan may state.
28pub const NONCLAIM_LIMIT: usize = 16;
29
30/// Issues one planning refusal carries before it begins counting the rest.
31///
32/// One per doubled seat — sixteen, since doubling spends two members of a membership of thirty-two — one per bound axis, and one of each remaining kind.
33pub const PLAN_ISSUE_LIMIT: usize = 32;
34
35/// What a request MEANT: its owner-qualified kind over the content commitment it was meant for.
36///
37/// Two requests that meant the same thing derive one of these, whatever machinery would realize them, which is why this is the layer equivalence is compared at.
38pub type Intent = Identity<identity::ProjectionIntent>;
39
40/// The triggers one plan watches.
41pub type InvalidationSet = NonEmpty<InvalidationTrigger, TRIGGER_LIMIT>;
42
43/// One kind's content bound to the exact captured declaration and owner-qualified kind it was presented under.
44///
45/// Holding one proves the compiler derived the content commitment from all three together.
46/// It claims nothing about whether the content is a correct semantic reading of the captured declaration; that is the declaring adapter's authority.
47#[must_use = "a content binding is the only material an account accepts"]
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ContentBinding<K: Kind> {
50    capture: Identity<identity::CapturedDeclaration>,
51    kind: Identity<identity::ProjectionKind>,
52    commitment: Identity<identity::ProjectionContent>,
53    content: K::Content,
54}
55
56/// The one account of the content a request walked in with: what that content IS, and what it declares it stands on.
57///
58/// Every reading of a request's content reads THIS value — the intent derived from it, the triggers that watch it, the declaration that caused it, the node it stands at — and none of them keeps a copy.
59/// A second list of what content depends on would agree with this one until it did not, and nothing downstream could tell which of the two a plan was planned over.
60///
61/// # Nonclaims
62///
63/// It says nothing about whether the commitment is current, reachable, or admitted anywhere: it is the address the caller handed over, read exactly.
64#[must_use = "the account is what a plan is planned over, and every reading reads it"]
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct Account<K: Kind> {
67    binding: ContentBinding<K>,
68    dependencies: Bounded<Identity<identity::CapturedDeclaration>, DEPENDENCY_LIMIT>,
69}
70
71/// The exact facts every plan is decided under, whatever its kind.
72///
73/// What a plan was planned OVER is not here: that is the account's, and a context naming it too would be the second holder of one fact.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub struct Context {
76    profile: Profile,
77    generator: Identity<identity::GeneratorVersion>,
78}
79
80/// One thing whose change makes a plan stale, and exactly which thing it watches.
81///
82/// A relevant change invalidates loudly and names what moved; a change no row watches — formatting, declaration order, an alias — touches nothing, because nothing watches those.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub enum InvalidationTrigger {
85    /// A captured declaration the plan stands on.
86    CapturedDeclaration {
87        /// The watched capture.
88        watched: Identity<identity::CapturedDeclaration>,
89    },
90    /// The profile the plan was decided under, at the version it was decided at.
91    Profile {
92        /// The watched profile.
93        watched: Profile,
94    },
95    /// The generator that produced the plan.
96    Generator {
97        /// The watched generator version.
98        watched: Identity<identity::GeneratorVersion>,
99    },
100    /// The kind-specific content commitment the plan was decided over.
101    ProjectionContent {
102        /// The watched content commitment.
103        watched: Identity<identity::ProjectionContent>,
104    },
105    /// Anything else a consumer declared this plan watches.
106    ///
107    /// One row rather than a row per consumer noun: a mechanism profile, a work formula, and a fixture population are three consumers' facts, and a compiler that enumerated them would be minting vocabulary for meanings it does not own.
108    Declared {
109        /// The consumer's declared name for what moved.
110        name: &'static str,
111        /// The identity that moving is watched by.
112        watched: OwnerIdentity,
113    },
114}
115
116/// What the eventual digest of one member must satisfy, stated before a byte of it exists.
117///
118/// A plan holds no rendered bytes and therefore no digest of them: it names the member the digest must be anchored to, and closure recomputes the digest over the rendered bytes at [`Role::OutputBytes`](crate::identity::Role::OutputBytes) and compares.
119/// A digest anchored anywhere else belongs to a different member.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub struct DigestContract {
122    /// The member identity the digest must be anchored to.
123    pub anchored_to: Identity<identity::GeneratedUnit>,
124}
125
126/// One declared output of a plan — logical, and only logical.
127///
128/// What it IS, where it came from, who is expected to materialize it, the address a publication writes it to, and what its eventual digest must satisfy.
129/// No rendered bytes and no digest of them: those are the rendering's facts and they live on the rendered unit.
130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub struct PlannedOutput {
132    /// What this member is, independently of any bytes.
133    pub semantic_key: Identity<identity::GeneratedUnit>,
134    /// The non-empty derivation trail this output carries.
135    pub origin: OriginTrail,
136    /// The profile expected to render it.
137    pub expected_profile: Profile,
138    /// The address a publication writes it to, where the member's seat is one that writes to an address.
139    pub address: Option<OwnerIdentity>,
140    /// What the eventual digest must satisfy.
141    pub digest_contract: DigestContract,
142}
143
144/// One planned member: the seat it stands under, and the output planned there.
145///
146/// The seat is what closure matches on, so a rendering that produced the right NUMBER of units in the wrong seats is caught by the seat rather than passing a count.
147/// It is also where the member's delivery is read from ([`Role::destination`]); a plan declares no delivery of its own, so two plans of one kind cannot disagree about which build compiles a seat's unit.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct PlannedMember<R: Role> {
150    /// The seat this member stands under.
151    pub role: R,
152    /// The output planned there.
153    pub output: PlannedOutput,
154}
155
156/// The complete declared output set of one plan — the output firewall.
157///
158/// The declared set is the whole set: a sibling that is not in it was not planned, and nothing downstream may materialize one.
159/// Structurally non-empty, because a plan that would generate nothing is a disposition rather than a plan.
160///
161/// Every member's seat is in the kind's declared roster, because admission refuses one that is not: every walk downstream — encoding, proof, reconstruction, delivery — quantifies over that roster, and a member outside it would be a unit those walks never look at, held by a proof that claims the whole set.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct Membership<R: Role> {
164    members: NonEmpty<PlannedMember<R>, MEMBERSHIP_LIMIT>,
165}
166
167/// Everything one plan decided, as the one value those seats travel in.
168///
169/// Five seats, in the order a plan's transcript writes them, and every one of them required: a construction that leaves one out stops compiling exactly where a missing argument would, and a seat added to a plan is added here and breaks every construction again.
170#[must_use = "the decided seats are what one plan is planned from, whole"]
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct PlanDecisions<R: Role> {
173    /// The complete declared output set.
174    pub membership: Membership<R>,
175    /// The triggers whose change invalidates the plan.
176    pub invalidation: InvalidationSet,
177    /// The decisions that produced the plan, in selection order.
178    pub trace: DecisionTrace,
179    /// Where the plan itself came from, in walk order.
180    pub origin: OriginTrail,
181    /// What the plan explicitly does not claim.
182    pub nonclaims: Bounded<Nonclaim, NONCLAIM_LIMIT>,
183}
184
185/// One plan: the complete output set of one request, named before any syntax exists.
186///
187/// Every seat is required, and the seats that could have been empty are shapes that cannot be — the output set, the watch set, the trace, and the trail are all structurally non-empty.
188/// Only the nonclaims may be empty, because a plan that claims exactly what it does has none to state.
189///
190/// The account is not a copy of anything: it is the value the caller walked in with, moved into the plan, so the plan's own answer to "what were you planned over" is what its identity, its watch set, and its origin edges were all read off.
191#[must_use = "a plan is the complete declared output set nothing may be rendered without"]
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct Plan<K: Kind> {
194    identity: PlanId,
195    provenance: Provenance,
196    account: Account<K>,
197    context: Context,
198    membership: Membership<K::Role>,
199    invalidation: InvalidationSet,
200    trace: DecisionTrace,
201    origin: OriginTrail,
202    nonclaims: Bounded<Nonclaim, NONCLAIM_LIMIT>,
203}
204
205/// Which declared magnitude a plan overran.
206///
207/// A bound refusal names its axis, so "too big" is never an unlocated word.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209pub enum BoundAxis {
210    /// The captured declarations one account may name.
211    Declarations,
212    /// The outputs one plan may declare.
213    Outputs,
214    /// The triggers one plan may watch.
215    Triggers,
216    /// The entries one decision trace may record.
217    TraceEntries,
218    /// The edges one origin trail may draw.
219    OriginEdges,
220}
221
222/// The two facts a contradiction stands between.
223///
224/// Neither side is elected as the offender: the disagreement is the fact, and naming one of them wrong is a judgment this compiler has no standing to make.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
226pub struct ContradictionPair {
227    /// The first constraining fact.
228    pub left: OwnerFact,
229    /// The second constraining fact.
230    pub right: OwnerFact,
231}
232
233/// One way planning refuses.
234///
235/// No issue is payload-free: an issue names what it observed, because a bare row makes the reader guess.
236/// Several are reachable only where a plan arrives decoded rather than built through the roads here, since the typed roads cannot express an unimplemented kind, an orphaned unit, or an incomplete membership.
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
238pub enum PlanIssue {
239    /// Two facts that decided this plan disagree.
240    ContradictoryFacts {
241        /// The disagreeing pair.
242        between: ContradictionPair,
243    },
244    /// The plan names a kind this compiler was not handed an implementation of.
245    UnknownKind {
246        /// The named kind's identity.
247        named: Identity<identity::ProjectionKind>,
248    },
249    /// The profile the request selected offers no such projection.
250    ProfileUnsupported {
251        /// The profile that offers it not.
252        profile: Profile,
253    },
254    /// A declared magnitude was exceeded.
255    BoundExceeded {
256        /// Which magnitude.
257        axis: BoundAxis,
258        /// The declared bound.
259        bound: u64,
260        /// The observed count.
261        observed: u64,
262    },
263    /// A declared sibling output is absent from the membership.
264    MembershipIncomplete {
265        /// The absent unit.
266        absent: Identity<identity::GeneratedUnit>,
267    },
268    /// A generated unit arrived with no origin.
269    OrphanGeneratedNode {
270        /// The orphaned unit.
271        node: Identity<identity::GeneratedUnit>,
272    },
273    /// Two planned members stand under one seat.
274    ///
275    /// Closure matches a rendered unit to a planned member BY SEAT, so a seat carrying two members leaves that match electing one of them and proving nothing about the other.
276    MembershipDoubled {
277        /// The doubled seat's position in its kind's roster.
278        role_slot: u16,
279        /// How many members stood under it.
280        observed: u32,
281    },
282    /// An origin trail's edges do not join: the edge at this position starts at a node the edge before it did not produce.
283    ///
284    /// A walk with a gap in it is not a shorter walk — it is two walks presented as one, and whichever end a reader trusts, the other end is provenance nobody established.
285    TrailDiscontinuous {
286        /// The position of the edge that does not join its predecessor, counted from the trail's first edge.
287        at: u32,
288    },
289    /// A narrow one-trigger reading was asked of an account that names more than one independent cause.
290    ///
291    /// A watch covering the first cause and no other reads exactly like a complete one, so the reading refuses rather than issuing a claim about the causes it dropped.
292    CauseSetUnwatchable {
293        /// How many independent causes the account names.
294        named: u32,
295        /// How many of them the reading can watch.
296        watchable: u32,
297    },
298    /// A planned member stands under a seat the kind's roster does not declare.
299    ///
300    /// The roster is the denominator of every downstream walk — encoding, proof, reconstruction, delivery.
301    /// A member outside it would render, vanish from all of them, and leave the closure proving a set it never examined whole, so the member refuses at admission instead.
302    MembershipForeign {
303        /// The undeclared seat's own declared name.
304        seat: &'static str,
305    },
306    /// An address was stated for a seat no publication act consumes.
307    ///
308    /// An address is a claim about where an artifact will be written, and only a seat delivering to a publication artifact ever writes to one.
309    /// Stated anywhere else — a declaration site, a test carrier, or a seat outside the roster entirely — the address would still enter every identity while no act ever consumed it: a writable claim with no product act, which is exactly the shape this plan refuses to hold.
310    AddressInert {
311        /// The seat the address was stated for, by its own declared name.
312        seat: &'static str,
313    },
314}
315
316/// How planning says no.
317///
318/// Planning issues are independent and co-establishable — one pass may find a doubled seat and an overrun magnitude at once — so the body carries every issue the pass established, and says so where it kept only what fits.
319/// No issue is elected as the primary one, and a body with nothing in it is unrepresentable.
320#[must_use = "a planning refusal carries every issue the pass established"]
321#[derive(Debug, Clone, PartialEq, Eq, Hash)]
322pub struct PlanError {
323    body: Capped<PlanIssue, PLAN_ISSUE_LIMIT>,
324}