Skip to main content

macroonz_compiler/request/
decide.rs

1//! What one request decides before a token of Rust exists, and the identity chain it mints doing so.
2//!
3//! Pure functions over values their types already inform.
4//! No caller supplies the primary capture, kind, content, member, or plan identity: each is derived from the informed values this road receives.
5//! Dependency captures and publication addresses cross as typed citations because their owners are independent declarations, and they never substitute for an identity this request mints.
6
7use super::Door;
8use super::SELECTION_FACT;
9use super::types::{Selection, Statements};
10use crate::bounded::{Bounded, Overflow};
11use crate::identity::{
12    self, Identity, OwnerFact, OwnerIdentity, Profile, Transcript, encode_bytes,
13};
14use crate::kind::{Destination, Kind, Role};
15use crate::origin::{
16    DecisionTrace, OriginEdge, OriginRelation, OriginTrail, TRACE_ENTRY_LIMIT, TraceDecision,
17    TraceEntry,
18};
19use crate::plan::{
20    Account, BoundAxis, ContentBinding, Context, DigestContract, Membership, Plan, PlanDecisions,
21    PlanError, PlanIssue, PlannedMember, PlannedOutput,
22};
23use crate::request::Producer;
24use crate::token::CapturedInput;
25
26impl<'request, R: Role> Statements<'request, R> {
27    /// Borrows the request facts planning consumes without minting another owner for them.
28    pub(super) const fn from_request(
29        assumptions: &'request [OwnerFact],
30        addresses: &'request [(R, OwnerIdentity)],
31        selection: &'request Selection<R>,
32    ) -> Self {
33        Self {
34            assumptions,
35            addresses,
36            selection,
37        }
38    }
39}
40
41/// Plan one request: the account it stands on, the context it is decided under, one member per selected seat, and the record of why.
42///
43/// The watch set travels inside the plan and nowhere beside it: the plan owns the value, and every later reading — the explanation's included — is read off that one seat, so no second copy exists for a later normalization to disagree with.
44///
45/// # Errors
46///
47/// Returns the planning refusal where the declared dependency set, the watch set, the output set, or the decision trace outgrows its magnitude, where the kind's roster declares no seat at all, and one [`PlanIssue::AddressInert`] per stated address whose seat no publication act consumes.
48pub(super) fn planned<K: Kind>(
49    capture: &CapturedInput,
50    content: K::Content,
51    door: &Door,
52    dependencies: Vec<Identity<identity::CapturedDeclaration>>,
53    profile: Profile,
54    statements: &Statements<'_, K::Role>,
55) -> Result<Plan<K>, PlanError> {
56    let roles = selected_roles(statements.selection);
57    consumable(statements.addresses, &roles)?;
58    let account = Account::standing_on(bound_content(capture, content, door), dependencies)?;
59    let stands_over = account.commitment();
60    let content_commitment = account.content_commitment();
61    let kind = account.kind();
62    let decided_under = Context::under(profile);
63    let invalidation = decided_under.watch_set(&account)?;
64    let authored = account.origin_node();
65    let membership = membership(
66        stands_over,
67        content_commitment,
68        authored,
69        profile,
70        statements.addresses,
71        kind,
72        &roles,
73    )?;
74    let origin = OriginTrail::from_edge(OriginEdge {
75        from: authored,
76        relation: OriginRelation::AuthoredDeclaration,
77        to: seat_node(
78            kind,
79            content_commitment,
80            stands_over,
81            membership.first().role,
82        ),
83    });
84    let trace = trace(
85        traced(kind, content_commitment, stands_over),
86        statements.assumptions,
87    )?;
88    Ok(Plan::planned(
89        account,
90        decided_under,
91        PlanDecisions {
92            membership,
93            invalidation,
94            trace,
95            origin,
96            nonclaims: Bounded::empty(),
97        },
98    ))
99}
100
101/// The complete output set: one member per selected role, canonicalized into kind-roster order.
102///
103/// # Errors
104///
105/// Returns [`PlanIssue::UnknownKind`] where the roster declares no seat — a kind with nothing to render is a kind this door was handed no implementation of — and the output magnitude where it declares more seats than a plan admits.
106fn membership<R: Role>(
107    stands_over: Identity<identity::CapturedDeclaration>,
108    content: Identity<identity::ProjectionContent>,
109    authored: Identity<identity::OriginNode>,
110    profile: Profile,
111    addresses: &[(R, OwnerIdentity)],
112    kind: Identity<identity::ProjectionKind>,
113    roles: &[R],
114) -> Result<Membership<R>, PlanError> {
115    let mut seats = roles.iter().copied();
116    let Some(head) = seats.next() else {
117        return Err(PlanError::of(PlanIssue::UnknownKind { named: kind }));
118    };
119    let rest = seats
120        .map(|role| {
121            member(
122                kind,
123                content,
124                stands_over,
125                authored,
126                profile,
127                role,
128                addresses,
129            )
130        })
131        .collect();
132    Membership::declared(
133        member(
134            kind,
135            content,
136            stands_over,
137            authored,
138            profile,
139            head,
140            addresses,
141        ),
142        rest,
143    )
144}
145
146/// The selected roles in canonical kind-roster order.
147fn selected_roles<R: Role>(selection: &Selection<R>) -> Vec<R> {
148    let mut roles = match selection {
149        Selection::All => R::ALL.to_vec(),
150        Selection::Declared { first, rest } => {
151            let mut roles = vec![*first];
152            roles.extend(rest.iter().copied());
153            roles
154        }
155    };
156    roles.sort_by_key(|role| role.slot());
157    roles
158}
159
160/// One planned member: what the seat's unit will be, where it came from, who renders it, and what its digest must satisfy.
161fn member<R: Role>(
162    kind: Identity<identity::ProjectionKind>,
163    content: Identity<identity::ProjectionContent>,
164    stands_over: Identity<identity::CapturedDeclaration>,
165    authored: Identity<identity::OriginNode>,
166    profile: Profile,
167    role: R,
168    addresses: &[(R, OwnerIdentity)],
169) -> PlannedMember<R> {
170    let key = semantic_key(kind, content, stands_over, role);
171    PlannedMember {
172        role,
173        output: PlannedOutput {
174            semantic_key: key,
175            origin: OriginTrail::from_edge(OriginEdge {
176                from: authored,
177                relation: OriginRelation::SemanticDerivation,
178                to: seat_node(kind, content, stands_over, role),
179            }),
180            expected_profile: profile,
181            address: addressed(role, addresses),
182            digest_contract: DigestContract { anchored_to: key },
183        },
184    }
185}
186
187/// Whether every stated address names a seat some publication act will consume.
188///
189/// An address enters the plan's, the rendering's, and the closure's identities, so one that nothing consumes is not loose metadata — it is a claim with no act.
190/// A seat consumes an address only where the roster declares it and its delivery is a publication artifact; an address stated anywhere else refuses here, before any identity commits to it.
191///
192/// # Errors
193///
194/// Returns one [`PlanIssue::AddressInert`] per address whose seat never publishes.
195fn consumable<R: Role>(addresses: &[(R, OwnerIdentity)], selected: &[R]) -> Result<(), PlanError> {
196    let mut inert = addresses
197        .iter()
198        .filter(|(seat, _)| {
199            !selected.contains(seat) || seat.destination() != Destination::PublicationArtifact
200        })
201        .map(|(seat, _)| PlanIssue::AddressInert { seat: seat.name() });
202    match inert.next() {
203        Some(issue) => Err(PlanError::over(issue, inert.collect())),
204        None => Ok(()),
205    }
206}
207
208/// The address a seat's unit is written to, where the caller stated one.
209fn addressed<R: Role>(role: R, addresses: &[(R, OwnerIdentity)]) -> Option<OwnerIdentity> {
210    addresses
211        .iter()
212        .find(|(seat, _)| *seat == role)
213        .map(|(_, address)| *address)
214}
215
216/// The decisions that produced the plan: this home's selection rule, then every fact the caller says the projection rests on.
217///
218/// # Errors
219///
220/// Returns the planning refusal naming [`BoundAxis::TraceEntries`] where the assumed facts outrun what one trace records.
221fn trace(
222    subject: Identity<identity::Traced>,
223    assumptions: &[OwnerFact],
224) -> Result<DecisionTrace, PlanError> {
225    let mut entries = vec![TraceEntry {
226        subject,
227        decision: TraceDecision::SelectedBecause(SELECTION_FACT),
228    }];
229    entries.extend(assumptions.iter().map(|fact| TraceEntry {
230        subject,
231        decision: TraceDecision::SelectedBecause(*fact),
232    }));
233    let offered = entries.len();
234    DecisionTrace::recorded(entries).map_err(|_| {
235        PlanError::bounded(
236            BoundAxis::TraceEntries,
237            Overflow {
238                capacity: TRACE_ENTRY_LIMIT,
239                offered,
240            },
241        )
242    })
243}
244
245/// The identity of the material one request walked in with.
246///
247/// Over the capture's own canonical bytes exactly as they were handed over: a consumer that names a narrower reading of its declaration hands the narrower capture.
248///
249/// # Authority
250///
251/// **This is the one derivation of a captured declaration's identity**, and it is public for exactly one further caller: a door stating a request's DEPENDENCIES hands over the identities of the further captures it read content from, and those identities must be this derivation over those captures — a second spelling of the rule beside this one would agree until one of them was edited.
252#[must_use]
253pub fn committed(capture: &CapturedInput) -> Identity<identity::CapturedDeclaration> {
254    Identity::derived(Transcript::rooted(
255        identity::Role::CapturedDeclaration,
256        &capture.canonical_bytes(),
257        0,
258    ))
259}
260
261/// The identity of one helper capture read beside a semantic declaration.
262///
263/// The declaration's commitment is the anchor, the helper capture's complete canonical bytes are the material, and the caller supplies the position its helper grammar declares.
264/// This is the one derivation of a captured helper's identity, so descriptor and adopter roads do not restate the preimage beside this owner.
265#[must_use]
266pub fn committed_helper(
267    declaration: &CapturedInput,
268    helper: &CapturedInput,
269    position: u32,
270) -> Identity<identity::CapturedHelper> {
271    let anchor = committed(declaration);
272    Identity::derived(Transcript::under_projection(
273        identity::Role::CapturedHelper,
274        &anchor,
275        &helper.canonical_bytes(),
276        position,
277    ))
278}
279
280/// What one seat's identities are derived over: the owner-qualified kind, the content commitment, and the seat's own name, each framed.
281///
282/// Framed rather than raw, which is what keeps a seat named `content` at position zero from deriving the origin node an account already stands at.
283/// The owner-qualified kind identity is an ancestor on purpose: roles are open and [`SoleRole`](crate::kind::SoleRole) is reusable by any one-unit kind, so two kinds sharing one capture and one roster would otherwise share a semantic key — and if their bytes agreed, a rendered-unit identity too — while the public contract calls them different generation kinds.
284fn seat_material<R: Role>(
285    kind: Identity<identity::ProjectionKind>,
286    content: Identity<identity::ProjectionContent>,
287    role: R,
288) -> Vec<u8> {
289    let mut material = Vec::new();
290    encode_bytes(kind.as_bytes(), &mut material);
291    encode_bytes(content.as_bytes(), &mut material);
292    encode_bytes(role.name().as_bytes(), &mut material);
293    material
294}
295
296/// What the unit under one seat IS, independently of any bytes.
297fn semantic_key<R: Role>(
298    kind: Identity<identity::ProjectionKind>,
299    content: Identity<identity::ProjectionContent>,
300    stands_over: Identity<identity::CapturedDeclaration>,
301    role: R,
302) -> Identity<identity::GeneratedUnit> {
303    Identity::derived(Transcript::under_projection(
304        identity::Role::GeneratedUnit,
305        &stands_over,
306        &seat_material(kind, content, role),
307        u32::from(role.slot()),
308    ))
309}
310
311/// The origin node one seat's unit stands at.
312fn seat_node<R: Role>(
313    kind: Identity<identity::ProjectionKind>,
314    content: Identity<identity::ProjectionContent>,
315    stands_over: Identity<identity::CapturedDeclaration>,
316    role: R,
317) -> Identity<identity::OriginNode> {
318    Identity::derived(Transcript::under_projection(
319        identity::Role::OriginNode,
320        &stands_over,
321        &seat_material(kind, content, role),
322        u32::from(role.slot()),
323    ))
324}
325
326/// The subject every decision of one request is recorded against.
327fn traced(
328    kind: Identity<identity::ProjectionKind>,
329    content: Identity<identity::ProjectionContent>,
330    stands_over: Identity<identity::CapturedDeclaration>,
331) -> Identity<identity::Traced> {
332    let mut material = Vec::new();
333    encode_bytes(kind.as_bytes(), &mut material);
334    encode_bytes(content.as_bytes(), &mut material);
335    Identity::derived(Transcript::under_projection(
336        identity::Role::Plan,
337        &stands_over,
338        &material,
339        0,
340    ))
341}
342
343/// The kind a request names, by the one fact of a kind that reaches an identity.
344fn named<K: Kind>(producer: Producer) -> Identity<identity::ProjectionKind> {
345    let mut material = Vec::new();
346    encode_bytes(producer.namespace.as_bytes(), &mut material);
347    encode_bytes(producer.name.as_bytes(), &mut material);
348    encode_bytes(K::NAME.as_bytes(), &mut material);
349    Identity::derived(Transcript::rooted(
350        identity::Role::ProjectionKind,
351        &material,
352        0,
353    ))
354}
355
356/// Bind one kind's content to the exact captured declaration and door-qualified kind it was presented under.
357pub fn bound_content<K: Kind>(
358    capture: &CapturedInput,
359    content: K::Content,
360    door: &Door,
361) -> ContentBinding<K> {
362    ContentBinding::bound(committed(capture), named::<K>(door.producer()), content)
363}