Skip to main content

macroonz_compiler/identity/
type_guard.rs

1//! The identity home's invariant nucleus: every road that reaches a private field.
2//!
3//! This file is declared inside `types.rs` as its own child, so it sees the fields the declarations keep private and nothing else in the crate does.
4//! That is what makes the walls structural rather than reviewed: a road around one of them would have to be written here, and none is.
5
6use super::{
7    Anchoring, GENERATOR, GeneratorIdentity, HumanProjection, Identity, OwnerIdentity, Profile,
8    Provenance, Role, ShapeVersion, Subject, Transcript, Version,
9};
10use crate::bounded::{Bounded, Overflow};
11use core::fmt;
12use core::hash::{Hash, Hasher};
13use core::marker::PhantomData;
14
15impl<S: Subject> Identity<S> {
16    /// Derive one identity from its complete transcript.
17    /// Deterministic and total: every transcript names an identity.
18    #[must_use]
19    pub fn derived(transcript: Transcript<'_>) -> Self {
20        let context = transcript.profile().context_for::<S>(transcript.role());
21        Self(
22            blake3::derive_key(&context, &transcript.encoded::<S>()),
23            PhantomData,
24        )
25    }
26
27    /// Derive one identity and the record of how it was derived.
28    ///
29    /// The record is for a value that is going to keep it — one whose identity a reader may be handed on its own and asked to account for.
30    /// A caller with nowhere to put one takes [`Identity::derived`], and the record is simply not made rather than made and carried by everything.
31    #[must_use]
32    pub fn derived_with_provenance(transcript: Transcript<'_>) -> (Self, Provenance) {
33        (Self::derived(transcript), transcript.provenance::<S>())
34    }
35
36    /// The identity's thirty-two bytes, borrowed for comparison and for rendering.
37    #[must_use]
38    pub const fn as_bytes(&self) -> &[u8; 32] {
39        &self.0
40    }
41}
42
43impl<S: Subject> PartialEq for Identity<S> {
44    fn eq(&self, other: &Self) -> bool {
45        self.0 == other.0
46    }
47}
48
49impl<S: Subject> Eq for Identity<S> {}
50
51impl<S: Subject> Hash for Identity<S> {
52    fn hash<H: Hasher>(&self, into: &mut H) {
53        self.0.hash(into);
54    }
55}
56
57impl<S: Subject> fmt::Debug for Identity<S> {
58    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
59        into.debug_tuple(S::NAME).field(&self.0).finish()
60    }
61}
62
63impl Version {
64    /// The position the grammar's owner assigned.
65    #[must_use]
66    pub const fn declared(position: u32) -> Self {
67        Self(position)
68    }
69
70    /// The assigned position.
71    #[must_use]
72    pub const fn position(self) -> u32 {
73        self.0
74    }
75}
76
77impl Profile {
78    /// One grammar, under the stem of whoever owns it, at one version.
79    #[must_use]
80    pub const fn declared(stem: &'static str, name: &'static str, version: Version) -> Self {
81        Self {
82            stem,
83            name,
84            version,
85        }
86    }
87
88    /// The stem of whoever owns this grammar.
89    #[must_use]
90    pub const fn stem(self) -> &'static str {
91        self.stem
92    }
93
94    /// The grammar's declared name.
95    #[must_use]
96    pub const fn name(self) -> &'static str {
97        self.name
98    }
99
100    /// The declared version — this grammar's own position, and no other grammar's.
101    #[must_use]
102    pub const fn version(self) -> Version {
103        self.version
104    }
105
106    /// The derive-key context one SUBJECT derives under, at one role.
107    ///
108    /// # Authority
109    ///
110    /// **The subject is the TYPE's and never an argument.** A road that took the subject as text would let a caller derive under any key space it could spell — including one another subject already occupies — and the typed identity above it would be a promise the encoder never had to keep.
111    #[must_use]
112    pub fn context_for<S: Subject>(self, role: Role) -> String {
113        self.context_over(S::STEM, S::NAME, role)
114    }
115
116    /// The same context, over a subject a derivation record already carries.
117    ///
118    /// Crate-internal, with one caller: [`Provenance::context`], which renders what a derivation recorded rather than performing one.
119    pub(crate) fn context_over(self, subject_stem: &str, subject: &str, role: Role) -> String {
120        let Self {
121            stem,
122            name,
123            version,
124        } = self;
125        let position = version.position();
126        let seat = role.name();
127        format!("{stem}/{name}/v{position}/{subject_stem}/{subject}/{seat}")
128    }
129}
130
131impl ShapeVersion {
132    /// The shape position the generator's owner assigned.
133    #[must_use]
134    pub const fn declared(position: u32) -> Self {
135        Self(position)
136    }
137
138    /// The assigned position.
139    #[must_use]
140    pub const fn position(self) -> u32 {
141        self.0
142    }
143}
144
145impl GeneratorIdentity {
146    /// The generator under its declared name, rendered shape, and recorded package version.
147    #[must_use]
148    pub const fn declared(name: &'static str, shape: ShapeVersion, package: &'static str) -> Self {
149        Self {
150            name,
151            shape,
152            package,
153        }
154    }
155
156    /// The generator's stable name. One of the two facts a staleness comparison reads.
157    #[must_use]
158    pub const fn name(self) -> &'static str {
159        self.name
160    }
161
162    /// The rendered shape's version. The other fact a staleness comparison reads.
163    #[must_use]
164    pub const fn shape(self) -> ShapeVersion {
165        self.shape
166    }
167
168    /// The package version, recorded for a reader and compared by nothing.
169    #[must_use]
170    pub const fn package_version(self) -> &'static str {
171        self.package
172    }
173
174    /// Whether two generator identities name the same generator rendering the same shape.
175    ///
176    /// The comparison a staleness reading wants, and the reason it is a named road rather than `==`: equality compares the package version too, and the package version moves for reasons no output noticed.
177    #[must_use]
178    pub fn same_shape(self, other: Self) -> bool {
179        self.name == other.name && self.shape == other.shape
180    }
181}
182
183impl<'material> Transcript<'material> {
184    /// Write a transcript under a grammar the caller names.
185    ///
186    /// The road for a preimage whose grammar is the caller's own; the four roads below name a role and take this compiler's grammar for it.
187    #[must_use]
188    pub const fn under_profile(
189        profile: Profile,
190        role: Role,
191        anchoring: Anchoring,
192        material: &'material [u8],
193        position: u32,
194    ) -> Self {
195        Self {
196            profile,
197            generator: GENERATOR,
198            role,
199            anchoring,
200            material,
201            position,
202        }
203    }
204
205    /// Write a transcript under an anchoring the caller already decided.
206    ///
207    /// The road for a mint site whose anchor depends on a typed posture rather than on which family of identity it holds — a plan hangs off whatever caused it, and what caused it is a sum type.
208    #[must_use]
209    pub const fn under(
210        role: Role,
211        anchoring: Anchoring,
212        material: &'material [u8],
213        position: u32,
214    ) -> Self {
215        Self::under_profile(role.profile(), role, anchoring, material, position)
216    }
217
218    /// Derive under no anchor at all — the root of one derivation chain.
219    #[must_use]
220    pub const fn rooted(role: Role, material: &'material [u8], position: u32) -> Self {
221        Self::under(role, Anchoring::Rooted, material, position)
222    }
223
224    /// Derive under an identity a CONSUMER minted.
225    #[must_use]
226    pub const fn under_owner(
227        role: Role,
228        anchor: &OwnerIdentity,
229        material: &'material [u8],
230        position: u32,
231    ) -> Self {
232        Self::under(
233            role,
234            Anchoring::UnderOwner(anchor.bytes),
235            material,
236            position,
237        )
238    }
239
240    /// Derive under another identity this compiler derived.
241    #[must_use]
242    pub fn under_projection<S: Subject>(
243        role: Role,
244        anchor: &Identity<S>,
245        material: &'material [u8],
246        position: u32,
247    ) -> Self {
248        Self::under(
249            role,
250            Anchoring::UnderProjection(*anchor.as_bytes()),
251            material,
252            position,
253        )
254    }
255
256    /// The grammar this transcript is written under.
257    #[must_use]
258    pub const fn profile(&self) -> Profile {
259        self.profile
260    }
261
262    /// The generator this transcript records.
263    ///
264    /// Recorded and never written: the encoding carries no member for it, and this road exists so the derivation record can.
265    #[must_use]
266    pub const fn generator(&self) -> GeneratorIdentity {
267        self.generator
268    }
269
270    /// The seat this transcript stands in.
271    #[must_use]
272    pub const fn role(&self) -> Role {
273        self.role
274    }
275
276    /// What this transcript hangs off.
277    #[must_use]
278    pub const fn anchoring(&self) -> Anchoring {
279        self.anchoring
280    }
281
282    /// The varying material, at full length.
283    #[must_use]
284    pub const fn material(&self) -> &'material [u8] {
285        self.material
286    }
287
288    /// The position inside the anchor's declared sequence.
289    #[must_use]
290    pub const fn position(&self) -> u32 {
291        self.position
292    }
293
294    /// The derivation record this transcript leaves for one identity subject.
295    #[must_use]
296    pub fn provenance<S: Subject>(&self) -> Provenance {
297        Provenance {
298            subject_stem: S::STEM,
299            subject: S::NAME,
300            role: self.role,
301            profile: self.profile,
302            generator: self.generator,
303            anchoring: self.anchoring,
304            material_length: u64::try_from(self.material.len()).unwrap_or(u64::MAX),
305            position: self.position,
306        }
307    }
308}
309
310impl Provenance {
311    /// The stem of whoever owns the subject this derivation named.
312    #[must_use]
313    pub const fn subject_stem(&self) -> &'static str {
314        self.subject_stem
315    }
316
317    /// The identity subject this derivation named.
318    #[must_use]
319    pub const fn subject(&self) -> &'static str {
320        self.subject
321    }
322
323    /// The seat it stood in.
324    #[must_use]
325    pub const fn role(&self) -> Role {
326        self.role
327    }
328
329    /// The grammar and version it was derived under.
330    #[must_use]
331    pub const fn profile(&self) -> Profile {
332        self.profile
333    }
334
335    /// The generator that derived it.
336    #[must_use]
337    pub const fn generator(&self) -> GeneratorIdentity {
338        self.generator
339    }
340
341    /// What it hung off, anchor included.
342    #[must_use]
343    pub const fn anchoring(&self) -> Anchoring {
344        self.anchoring
345    }
346
347    /// How many bytes of material went into the transcript.
348    #[must_use]
349    pub const fn material_length(&self) -> u64 {
350        self.material_length
351    }
352
353    /// The position inside the anchor's declared sequence.
354    #[must_use]
355    pub const fn position(&self) -> u32 {
356        self.position
357    }
358
359    /// The derive-key context this derivation ran under.
360    #[must_use]
361    pub fn context(&self) -> String {
362        self.profile
363            .context_over(self.subject_stem, self.subject, self.role)
364    }
365
366    /// Whether this derivation was recorded under the generator and rendered shape this crate declares today.
367    ///
368    /// # Nonclaims
369    ///
370    /// A `false` says a different generator shape produced the record.
371    /// It says nothing about whether the material moved, whether the identity would re-derive the same, or whether anything needs redoing.
372    #[must_use]
373    pub fn under_current_shape(&self) -> bool {
374        self.generator.same_shape(GENERATOR)
375    }
376}
377
378impl HumanProjection {
379    /// Render one bounded human projection.
380    ///
381    /// # Errors
382    ///
383    /// Returns [`Overflow`] when the rendering runs past [`HUMAN_TEXT_LIMIT`](super::HUMAN_TEXT_LIMIT).
384    /// A projection that does not fit refuses rather than truncating: a silently cut explanation is a false one.
385    pub fn projected(text: &str) -> Result<Self, Overflow> {
386        Bounded::new(text.as_bytes().to_vec()).map(Self)
387    }
388
389    /// The seam behind [`human_projection!`](crate::identity::human_projection), which is the only road to it.
390    ///
391    /// The rendering arrives as a fixed-width array, and the width is the array's own TYPE, so this road carries no runtime count, returns no refusal, and has no branch where a rendering that did not fit becomes an empty one.
392    #[must_use]
393    pub(crate) fn proven<const N: usize>(rendered: [u8; N]) -> Self {
394        Self(Bounded::from_array(rendered))
395    }
396
397    /// The empty rendering.
398    /// Total: a caller with nothing to say for a person still owes a value rather than a hole.
399    #[must_use]
400    pub fn empty() -> Self {
401        Self(Bounded::empty())
402    }
403
404    /// The rendering's byte length.
405    #[must_use]
406    pub fn len(&self) -> usize {
407        self.0.len()
408    }
409
410    /// Whether the rendering carries no bytes.
411    #[must_use]
412    pub fn is_empty(&self) -> bool {
413        self.0.is_empty()
414    }
415
416    /// The rendering, for a caller to SHOW a person.
417    ///
418    /// The one lawful use of the bytes and a one-way road out: a frontend that must put a sentence in front of somebody calls this, and nothing inside the compiler calls it at all.
419    #[must_use]
420    pub fn shown(&self) -> String {
421        String::from_utf8_lossy(self.0.as_slice()).into_owned()
422    }
423}