Skip to main content

macroonz_compiler/identity/
types.rs

1//! The identity home's declarations: the subject roster, the role roster, one profile constant per preimage grammar, the transcript and its derivation record, the generator facts, and the two citation shapes.
2//!
3//! Declarations only.
4//! Every constructor that must see a private field lives in `type_guard.rs`, declared below as this file's own child so the invariant nucleus and the fields it protects are never separated by a module boundary.
5
6use crate::bounded::Bounded;
7use core::marker::PhantomData;
8
9#[path = "type_guard.rs"]
10mod guard;
11
12pub use guard::names_are_separating;
13pub(crate) use guard::{human_projection, static_bytes};
14
15/// The stem every subject and every grammar this compiler owns is declared under.
16pub const MACROONZ_STEM: &str = "macroonz/identity";
17
18/// One identity subject, by the name the derive-key grammar spells it with and the stem of whoever owns it.
19///
20/// The pair is what separates one subject's identities from another's, so both are DECLARED beside the marker rather than taken from the Rust spelling: a refactor that silently renamed every identity derived for a type would be a law change nobody wrote down.
21/// The trait is open, and a consumer's name that happens to match this compiler's roster is a different key space rather than a collision.
22pub trait Subject: Copy + 'static {
23    /// The subject's declared segment of the derive-key context.
24    const NAME: &'static str;
25
26    /// The stem of whoever declared it.
27    const STEM: &'static str;
28}
29
30/// Declares one roster of identity subjects under one stem, as the home's README shows.
31///
32/// Each row becomes a marker type carrying its declared name, and the roster settles both ways it could fail to separate while it compiles: a name outside the context grammar, and a name two rows declare.
33/// A declared name is lowercase ASCII letters and digits in `-`-joined segments, with no leading, trailing, or doubled separator.
34#[macro_export]
35macro_rules! subjects {
36    (stem = $stem:expr; $( $(#[$note:meta])* $name:ident = $declared:literal ),+ $(,)?) => {
37        $(
38            $(#[$note])*
39            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40            pub struct $name;
41
42            impl $crate::identity::Subject for $name {
43                const NAME: &'static str = $declared;
44                const STEM: &'static str = $stem;
45            }
46        )+
47
48        const _: () = ::core::assert!(
49            $crate::identity::names_are_separating(&[$($declared),+]),
50            "a subject name outside the derive-key grammar, or one two subjects declare",
51        );
52    };
53}
54
55subjects! {
56    stem = MACROONZ_STEM;
57    /// The token material one expansion was handed.
58    CapturedDeclaration = "captured-declaration",
59    /// One helper attribute's material, read beside a declaration.
60    CapturedHelper = "captured-helper",
61    /// What a request MEANT, ahead of anything decided about it.
62    ProjectionIntent = "projection-intent",
63    /// The canonical facts one kind-specific content value carries.
64    ProjectionContent = "projection-content",
65    /// One projection plan.
66    Plan = "plan",
67    /// One generated unit — the thing a plan declares it will materialize.
68    GeneratedUnit = "generated-unit",
69    /// One rendered unit — the thing a renderer actually materialized.
70    RenderedUnit = "rendered-unit",
71    /// The canonical bytes of one rendered unit.
72    OutputBytes = "output-bytes",
73    /// One proved closure between a plan's declared membership and what a renderer produced.
74    Closure = "closure",
75    /// One explanation, answered over a plan and the closure that proved its rendering.
76    Explanation = "explanation",
77    /// One closed expansion: the whole account one compilation produced.
78    ClosedExpansion = "closed-expansion",
79    /// One node of the origin graph.
80    OriginNode = "origin-node",
81    /// One subject a plan explicitly does not claim.
82    Nonclaim = "nonclaim",
83    /// One subject a decision trace entry is about.
84    Traced = "traced",
85    /// One stable name this compiler wrote down, standing for a value it declares.
86    DeclaredName = "declared-name",
87    /// One version of the generator itself.
88    GeneratorVersion = "generator-version",
89    /// One related issue a diagnostic points at.
90    RelatedIssue = "related-issue",
91    /// The whole refusal body one diagnostic's related set commits to, as opposed to any single issue inside it.
92    /// A separate subject from [`RelatedIssue`] because one key space holding two LEVELS over one material collides by construction: a body's preimage is the framing of its issues, so an issue whose own material happened to be that framing would derive the identity of the body it aliased.
93    RelatedBody = "related-body",
94    /// One projection profile — the posture a request ran under.
95    ProjectionProfile = "projection-profile",
96    /// One projection kind, named by identity where a decoded route may name a kind this compiler does not implement.
97    ProjectionKind = "projection-kind",
98    /// One contract a diagnostic expected to hold.
99    Contract = "contract",
100    /// One callable entry point.
101    ServiceEntry = "service-entry",
102}
103
104/// One identity this compiler derived, tagged by the subject it names.
105///
106/// Holding one means these thirty-two bytes came from a complete [`Transcript`] under the profile that transcript names, and would come out the same again from the same transcript on any machine.
107///
108/// # Authority
109///
110/// Collision resistance is claimed AS BLAKE3's, for the transcript as [`Transcript`] specifies it, at the [`Version`] the deriving [`Profile`] declares — and nothing broader.
111///
112/// # Construction
113///
114/// The only road is [`Identity::derived`], which takes a typed transcript; nothing wraps arbitrary bytes.
115/// `S` is a `PhantomData` parameter, so an identity naming one subject is a different type than one naming another regardless of bytes, and their derive-key contexts differ too — the separation is a runtime fact and not only a compile-time one.
116///
117/// # Nonclaims
118///
119/// It does not claim that two things this compiler considers different always have different transcripts; that is the transcript's completeness, which each mint site owns and documents.
120#[derive(Clone, Copy)]
121pub struct Identity<S: Subject>([u8; 32], PhantomData<S>);
122
123/// One projection plan's own identity.
124pub type PlanId = Identity<Plan>;
125
126/// One proved closure's own identity.
127pub type ClosureId = Identity<Closure>;
128
129/// One complete explanation's own identity.
130pub type ExplanationId = Identity<Explanation>;
131
132/// One closed expansion's own identity.
133pub type ClosedExpansionId = Identity<ClosedExpansion>;
134
135/// The seat one identity stands in inside its grammar.
136///
137/// A role is part of the derive-key context AND a member of every transcript, so two identities derived from one anchor under different roles are different twice over: separated before a byte of the transcript is read, and disagreeing inside it.
138///
139/// A row's declared name and slot are what the bytes carry, so a row is APPENDED and never renumbered — renumbering an occupied slot re-encodes transcripts that were already encoded.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub enum Role {
142    /// The token material one expansion was handed.
143    CapturedDeclaration,
144    /// One projection plan.
145    Plan,
146    /// One node of the origin graph.
147    OriginNode,
148    /// One generated unit a plan declares it will materialize.
149    GeneratedUnit,
150    /// One rendered unit a renderer actually materialized.
151    RenderedUnit,
152    /// The canonical bytes of one rendered unit.
153    OutputBytes,
154    /// One bundle materialized across a single publication boundary.
155    Bundle,
156    /// One proved closure between a plan and its rendering.
157    Closure,
158    /// One closed expansion.
159    ClosedExpansion,
160    /// One projection intent — what a request meant, ahead of what it decided.
161    ProjectionIntent,
162    /// One explanation, answered over a plan and its closure.
163    Explanation,
164    /// The documentation rows one captured declaration carries, read as a second fact over the surface its semantic commitment already names.
165    DeclarationDocumentation,
166    /// One stable name this compiler wrote down.
167    DeclaredName,
168    /// The generator's declared name and the shape it renders.
169    GeneratorVersion,
170    /// One refusal body, or one issue inside it, as a diagnostic points at it.
171    DiagnosticRelation,
172    /// One helper attribute's material, read as an independent fact over the surface its semantic commitment already names.
173    ///
174    /// Several helpers may stand here at once; they are separated by the roster position each one is derived at, never by a grammar of their own.
175    CapturedHelper,
176    /// One kind-specific content commitment.
177    ProjectionContent,
178    /// One projection kind qualified by the producer that owns its generated names.
179    ProjectionKind,
180}
181
182/// One position in one grammar's own order.
183///
184/// There is no `Ord`: positions of two different grammars are not comparable, and nothing here ranks them.
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
186pub struct Version(u32);
187
188/// One preimage grammar: which members a mint site writes, in what order, carrying what material.
189///
190/// A grammar exists because a preimage is genuinely its own, never because a type is.
191/// The stem sits ahead of the name, so one owner's `"plan"` and another's are two key spaces rather than one reached twice.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
193pub struct Profile {
194    stem: &'static str,
195    name: &'static str,
196    version: Version,
197}
198
199/// The grammar one captured declaration's SEMANTIC commitment is derived under.
200///
201/// The captured tree's own canonical encoding, rooted, with every documentation attribute dropped from the walk: a capture is the root of a derivation chain, and the material is the whole of what varies.
202/// Spans enter nothing — a handle is the producer's own table index, and two producers reading one declaration issue different ones.
203pub const CAPTURED_DECLARATION_PROFILE: Profile =
204    Profile::declared(MACROONZ_STEM, "captured-declaration", Version::declared(1));
205
206/// The grammar one captured declaration's DOCUMENTATION commitment is derived under.
207///
208/// The semantic commitment at the anchor, at full width, and over it the captured documentation rows in the order the walk read them — a second reading of one surface, so a declaration whose prose changes keeps its semantic name and takes a new documentation name.
209pub const DECLARATION_DOCUMENTATION_PROFILE: Profile = Profile::declared(
210    MACROONZ_STEM,
211    "declaration-documentation",
212    Version::declared(1),
213);
214
215/// The grammar one captured declaration's HELPER commitment is derived under.
216///
217/// The semantic commitment at the anchor, at full width, and over it the canonical bytes of one helper attribute's own captured trees, at the position that helper was declared at.
218/// Helper material says how a declaration is exercised rather than what contract it realizes, so it is dropped from the semantic walk and enters here.
219pub const CAPTURED_HELPER_PROFILE: Profile =
220    Profile::declared(MACROONZ_STEM, "captured-helper", Version::declared(1));
221
222/// The grammar one DECLARED STABLE NAME's identity is derived under.
223///
224/// The name's own bytes, exactly as this compiler wrote them down, rooted, at the position the declaring seat states.
225/// Several such names share this grammar and are separated by their subjects.
226pub const DECLARED_NAME_PROFILE: Profile =
227    Profile::declared(MACROONZ_STEM, "declared-name", Version::declared(1));
228
229/// The grammar one projection intent's identity is derived under.
230///
231/// The owner-qualified kind identity and the kind-specific content commitment it was meant over, rooted at position zero, and **nothing else** — no generator, no shape version, no delivery, no token grammar.
232/// So an intent survives upgrading the machinery that realizes it, which is the whole reason the layer exists: it is the one layer two distinct requests are allowed to agree at.
233pub const PROJECTION_INTENT_PROFILE: Profile =
234    Profile::declared(MACROONZ_STEM, "projection-intent", Version::declared(2));
235
236/// The grammar one owner-qualified projection kind is derived under.
237///
238/// The producer namespace, the producer name, and the kind's declared name, each framed, rooted at position zero.
239pub const PROJECTION_KIND_PROFILE: Profile =
240    Profile::declared(MACROONZ_STEM, "projection-kind", Version::declared(1));
241
242/// The grammar one kind-specific content commitment is derived under.
243///
244/// The owner-qualified kind identity and the content's complete canonical bytes, anchored under the exact captured declaration the content was paired with, at position zero.
245pub const PROJECTION_CONTENT_PROFILE: Profile =
246    Profile::declared(MACROONZ_STEM, "projection-content", Version::declared(1));
247
248/// The grammar one plan's identity is derived under.
249///
250/// The intent, the dependency set the account declares beside it, the context, the complete membership in role order, the invalidation set, the decision trace, the origin trail, and the nonclaims — anchored on the address the content walked in carrying.
251/// The context names the generator version the plan was produced under, so the generator reaches a plan's identity through the seat the plan declared it at and never through a member every grammar would have carried.
252pub const PLAN_PROFILE: Profile = Profile::declared(MACROONZ_STEM, "plan", Version::declared(2));
253
254/// The grammar one origin node's identity is derived under.
255///
256/// The declared material the node stands for, anchored on the address it is a node of, so one piece of content is one node wherever it is reached from.
257pub const ORIGIN_NODE_PROFILE: Profile =
258    Profile::declared(MACROONZ_STEM, "origin-node", Version::declared(2));
259
260/// The grammar one generated unit's semantic key is derived under.
261///
262/// The owner-qualified kind identity, the kind-specific content commitment, and the role's declared name, with the roster position of that role, anchored on what the plan hangs off — a member's LOGICAL identity, fixed before a byte of it exists.
263pub const GENERATED_UNIT_PROFILE: Profile =
264    Profile::declared(MACROONZ_STEM, "generated-unit", Version::declared(2));
265
266/// The grammar one rendered unit's identity and its output-bytes digest are both derived under.
267///
268/// The exact rendered bytes, under the semantic key they answer to, at the roster position of the role they were rendered under.
269/// Two roles read here, on the terms [`Role::profile`] states.
270pub const RENDERED_UNIT_PROFILE: Profile =
271    Profile::declared(MACROONZ_STEM, "rendered-unit", Version::declared(1));
272
273/// The grammar one bundle's identity is derived under.
274///
275/// The member plans a bundle names, as the set it publishes as one unit.
276pub const BUNDLE_PROFILE: Profile =
277    Profile::declared(MACROONZ_STEM, "bundle", Version::declared(1));
278
279/// The grammar one proved closure's identity is derived under.
280///
281/// The plan's identity at the anchor, and over it the complete planned membership in role order, the role roster's own length, the unit that stood under each role, and the partitioned emission's digests — the whole agreement rather than a sample of it.
282pub const CLOSURE_PROFILE: Profile =
283    Profile::declared(MACROONZ_STEM, "closure", Version::declared(1));
284
285/// The grammar one explanation's identity is derived under.
286///
287/// The closure's identity at the anchor, at full width, and over it the plan's identity, the number of answered seats, and every seat in the KIND's declared question order — the question's slot, the answer's discriminant, and that answer's typed material.
288/// The order is the roster's and never the caller's, so two views answering one kind's questions with one set of answers derive one identity whichever order they were supplied in.
289///
290/// Human prose is excluded: a rendered line is a projection of a typed answer, so a preimage carrying one would commit to a rendering and would rename every explanation the day a sentence was reworded.
291pub const EXPLANATION_PROFILE: Profile =
292    Profile::declared(MACROONZ_STEM, "explanation", Version::declared(1));
293
294/// The grammar one closed expansion's identity is derived under.
295///
296/// The closure's identity at the anchor, at full width, and over it exactly two members: the plan's identity and the explanation's.
297/// Every other candidate is already inside one of the three — the partitioned emission is committed by the anchor and the kind by the plan's intent — and two spellings of one fact are how a preimage drifts.
298pub const CLOSED_EXPANSION_PROFILE: Profile =
299    Profile::declared(MACROONZ_STEM, "closed-expansion", Version::declared(1));
300
301/// The grammar the GENERATOR VERSION identity is derived under.
302///
303/// The generator's declared name, framed, then its shape position in four big-endian bytes, rooted at position zero; the package version is absent, for the reason [`ShapeVersion`] states.
304pub const GENERATOR_VERSION_PROFILE: Profile =
305    Profile::declared(MACROONZ_STEM, "generator-version", Version::declared(1));
306
307/// The grammar a diagnostic's related identities are derived under, at both levels.
308///
309/// One refusal family's name and the framed material the level stands over — the issue's own canonical bytes at the issue level, and the framing of every issue in order at the body level — rooted at position zero.
310/// The two levels are separated by their subjects, [`RelatedIssue`] and [`RelatedBody`], which is what keeps a body's preimage from being reachable as an issue's.
311pub const DIAGNOSTIC_RELATION_PROFILE: Profile =
312    Profile::declared(MACROONZ_STEM, "diagnostic-relation", Version::declared(1));
313
314/// What one transcript hangs off.
315///
316/// Each posture is written as a distinct byte ahead of its commitment, so a rooted transcript can never encode as an anchored one whose anchor happened to be empty.
317/// The bytes are declared here rather than left in an encoder body, because an independent reader re-deriving a transcript needs them: [`Anchoring::Rooted`] is `0`, [`Anchoring::UnderOwner`] is `1`, [`Anchoring::UnderProjection`] is `2`, and a value is appended rather than renumbered.
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
319pub enum Anchoring {
320    /// No anchor at all — the root of one derivation chain, where the material is the whole of what varies.
321    Rooted,
322    /// Anchored under an identity a CONSUMER minted, carried at full width.
323    UnderOwner([u8; 32]),
324    /// Anchored under another identity this compiler derived, carried at full width.
325    UnderProjection([u8; 32]),
326}
327
328/// The COMPLETE preimage one [`Identity`] is derived from.
329///
330/// A transcript is the exact byte string handed to the digest, and the specification is complete: an independent implementation needs what follows and nothing else.
331///
332/// Two primitives.
333/// `u32be(n)` and `u64be(n)` are the integer in four or eight big-endian bytes; `bytes(x)` is `u64be(x.len())` followed by the bytes of `x`, and every variable-length member is written that way, so no two member sequences can be cut at a different boundary and produce one byte string.
334///
335/// The members, in exactly this order, with no separators and no padding:
336///
337/// | # | member | encoding |
338/// | - | ------ | -------- |
339/// | 1 | profile stem | `bytes(utf8)` of [`Profile::stem`] |
340/// | 2 | profile name | `bytes(utf8)` of [`Profile::name`] |
341/// | 3 | profile version | `u32be`, that grammar's own position |
342/// | 4 | subject | `bytes(utf8)` of [`Subject::NAME`] |
343/// | 5 | role | `bytes(utf8)` of [`Role::name`] |
344/// | 6 | role slot | one byte, [`Role::slot`] |
345/// | 7 | anchoring | one byte, [`Anchoring::slot`] |
346/// | 8 | anchor | `bytes(…)` — empty when rooted, else the full thirty-two |
347/// | 9 | material | `bytes(…)` — the full material, never a fold |
348/// | 10 | position | `u32be` |
349///
350/// The derive-key context is [`Profile::context_for`] over the same subject and role, and the identity is `blake3::derive_key(context, transcript)`.
351/// The subject's stem is a segment of that context and is not a member here, so two subjects spelled alike under different stems derive under different keys.
352/// The generator is not a member either: it is carried for the derivation record ([`Transcript::provenance`]) and written into no preimage.
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
354pub struct Transcript<'material> {
355    profile: Profile,
356    generator: GeneratorIdentity,
357    role: Role,
358    anchoring: Anchoring,
359    material: &'material [u8],
360    position: u32,
361}
362
363/// The inspectable record of ONE derivation.
364///
365/// The identity answers "which thing is this?" and is thirty-two bytes; the record answers "where did those thirty-two bytes come from?" and is inspection material.
366/// They are separate values so neither constrains the other: the transcript can be complete because it is not stored, and the record can be honest because it is written once where the derivation happened rather than copied everywhere the identity goes.
367///
368/// The material is stated by its LENGTH and not carried, because material is unbounded and a record that copied it would double every rendering in memory to say something the rendered unit already holds.
369/// That length is not a fold and identifies nothing; the identity is what commits to the material, at full width.
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
371pub struct Provenance {
372    subject_stem: &'static str,
373    subject: &'static str,
374    role: Role,
375    profile: Profile,
376    generator: GeneratorIdentity,
377    anchoring: Anchoring,
378    material_length: u64,
379    position: u32,
380}
381
382/// The version of the SHAPE a generator renders: a different token layout, a different set of roles, a different contract realized.
383///
384/// It is deliberately not the package version, which moves for reasons that cannot reach the output and is worthless as the fact a reader judges staleness by.
385/// **It is not a segment of any preimage** either: a bump renames no identity, because which generator rendered a thing is a fact ABOUT the derivation and rides [`Provenance`], while what the thing IS rides the preimage its grammar declares.
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
387pub struct ShapeVersion(u32);
388
389/// Which generator produced an identity, and under which rendered shape.
390///
391/// The name and the shape version are the two load-bearing facts a staleness comparison reads.
392/// The package version is recorded and read back but compared by nothing, because a report of "a different generator" on a version bump nobody's output noticed is noise dressed as provenance.
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
394pub struct GeneratorIdentity {
395    name: &'static str,
396    shape: ShapeVersion,
397    package: &'static str,
398}
399
400/// This generator, as every derivation record here names it.
401pub const GENERATOR: GeneratorIdentity = GeneratorIdentity::declared(
402    "macroonz",
403    ShapeVersion::declared(1),
404    env!("CARGO_PKG_VERSION"),
405);
406
407/// One identity a CONSUMER minted, cited by the subject the consumer names it under.
408///
409/// This compiler mints nothing for a consumer and checks nothing here: the bytes cross unchanged, and holding one says the compiler refers exactly to that identity and says nothing else — nothing about authority, freshness, availability, or equivalence.
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
411pub struct OwnerIdentity {
412    /// The subject the minting side names it under.
413    pub subject: &'static str,
414    /// The identity's declared raw-byte storage order.
415    pub bytes: [u8; 32],
416}
417
418/// One owning home and one fact it declares, by the stable names that home wrote down.
419///
420/// Every selection, omission, exclusion, and non-applicability in this compiler cites one.
421/// A bare boolean would say a decision happened without saying whose fact decided it, which is exactly the explanation the compiler owes.
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
423pub struct OwnerFact {
424    /// The owning home, by its declared name.
425    pub home: &'static str,
426    /// The fact that home declares, by its declared stable name.
427    pub name: &'static str,
428}
429
430/// Bytes one human projection may carry.
431///
432/// A projection that does not fit refuses rather than truncating, so the magnitude is the length past which a sentence is a different sentence and not a longer one.
433pub const HUMAN_TEXT_LIMIT: usize = 512;
434
435/// One bounded human-readable rendering of a typed value.
436///
437/// It is a projection and only a projection: derived from typed values, carried for a person to read, and never read back.
438/// No decision, no identity, and no refusal anywhere in this compiler consults one.
439#[derive(Debug, Clone, PartialEq, Eq, Hash)]
440pub struct HumanProjection(Bounded<u8, HUMAN_TEXT_LIMIT>);