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 crate::bounded::{Bounded, Overflow};
10use crate::identity::{
11    self, Identity, OwnerFact, OwnerIdentity, Profile, Transcript, encode_bytes,
12};
13use crate::kind::{Destination, Kind, Role};
14use crate::origin::{
15    DecisionTrace, OriginEdge, OriginRelation, OriginTrail, TRACE_ENTRY_LIMIT, TraceDecision,
16    TraceEntry,
17};
18use crate::plan::{
19    Account, BoundAxis, ContentBinding, Context, DigestContract, Membership, Plan, PlanDecisions,
20    PlanError, PlanIssue, PlannedMember, PlannedOutput,
21};
22use crate::request::Producer;
23use crate::token::CapturedInput;
24
25/// Plan one request: the account it stands on, the context it is decided under, one member per declared seat, and the record of why.
26///
27/// 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.
28///
29/// # Errors
30///
31/// 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.
32pub(super) fn planned<K: Kind>(
33    capture: &CapturedInput,
34    content: K::Content,
35    door: &Door,
36    dependencies: Vec<Identity<identity::CapturedDeclaration>>,
37    profile: Profile,
38    assumptions: &[OwnerFact],
39    addresses: &[(K::Role, OwnerIdentity)],
40) -> Result<Plan<K>, PlanError> {
41    consumable::<K::Role>(addresses)?;
42    let account = Account::standing_on(bound_content(capture, content, door), dependencies)?;
43    let stands_over = account.commitment();
44    let content_commitment = account.content_commitment();
45    let kind = account.kind();
46    let decided_under = Context::under(profile);
47    let invalidation = decided_under.watch_set(&account)?;
48    let authored = account.origin_node();
49    let membership = membership(
50        stands_over,
51        content_commitment,
52        authored,
53        profile,
54        addresses,
55        kind,
56    )?;
57    let origin = OriginTrail::from_edge(OriginEdge {
58        from: authored,
59        relation: OriginRelation::AuthoredDeclaration,
60        to: seat_node(
61            kind,
62            content_commitment,
63            stands_over,
64            membership.first().role,
65        ),
66    });
67    let trace = trace(traced(kind, content_commitment, stands_over), assumptions)?;
68    Ok(Plan::planned(
69        account,
70        decided_under,
71        PlanDecisions {
72            membership,
73            invalidation,
74            trace,
75            origin,
76            nonclaims: Bounded::empty(),
77        },
78    ))
79}
80
81/// The complete output set: one member per row of the kind's roster, in roster order.
82///
83/// # Errors
84///
85/// 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.
86fn membership<R: Role>(
87    stands_over: Identity<identity::CapturedDeclaration>,
88    content: Identity<identity::ProjectionContent>,
89    authored: Identity<identity::OriginNode>,
90    profile: Profile,
91    addresses: &[(R, OwnerIdentity)],
92    kind: Identity<identity::ProjectionKind>,
93) -> Result<Membership<R>, PlanError> {
94    let mut seats = R::ALL.iter().copied();
95    let Some(head) = seats.next() else {
96        return Err(PlanError::of(PlanIssue::UnknownKind { named: kind }));
97    };
98    let rest = seats
99        .map(|role| {
100            member(
101                kind,
102                content,
103                stands_over,
104                authored,
105                profile,
106                role,
107                addresses,
108            )
109        })
110        .collect();
111    Membership::declared(
112        member(
113            kind,
114            content,
115            stands_over,
116            authored,
117            profile,
118            head,
119            addresses,
120        ),
121        rest,
122    )
123}
124
125/// One planned member: what the seat's unit will be, where it came from, who renders it, and what its digest must satisfy.
126fn member<R: Role>(
127    kind: Identity<identity::ProjectionKind>,
128    content: Identity<identity::ProjectionContent>,
129    stands_over: Identity<identity::CapturedDeclaration>,
130    authored: Identity<identity::OriginNode>,
131    profile: Profile,
132    role: R,
133    addresses: &[(R, OwnerIdentity)],
134) -> PlannedMember<R> {
135    let key = semantic_key(kind, content, stands_over, role);
136    PlannedMember {
137        role,
138        output: PlannedOutput {
139            semantic_key: key,
140            origin: OriginTrail::from_edge(OriginEdge {
141                from: authored,
142                relation: OriginRelation::SemanticDerivation,
143                to: seat_node(kind, content, stands_over, role),
144            }),
145            expected_profile: profile,
146            address: addressed(role, addresses),
147            digest_contract: DigestContract { anchored_to: key },
148        },
149    }
150}
151
152/// Whether every stated address names a seat some publication act will consume.
153///
154/// 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.
155/// 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.
156///
157/// # Errors
158///
159/// Returns one [`PlanIssue::AddressInert`] per address whose seat never publishes.
160fn consumable<R: Role>(addresses: &[(R, OwnerIdentity)]) -> Result<(), PlanError> {
161    let mut inert = addresses
162        .iter()
163        .filter(|(seat, _)| {
164            !R::ALL.contains(seat) || seat.destination() != Destination::PublicationArtifact
165        })
166        .map(|(seat, _)| PlanIssue::AddressInert { seat: seat.name() });
167    match inert.next() {
168        Some(issue) => Err(PlanError::over(issue, inert.collect())),
169        None => Ok(()),
170    }
171}
172
173/// The address a seat's unit is written to, where the caller stated one.
174fn addressed<R: Role>(role: R, addresses: &[(R, OwnerIdentity)]) -> Option<OwnerIdentity> {
175    addresses
176        .iter()
177        .find(|(seat, _)| *seat == role)
178        .map(|(_, address)| *address)
179}
180
181/// The decisions that produced the plan: this home's selection rule, then every fact the caller says the projection rests on.
182///
183/// # Errors
184///
185/// Returns the planning refusal naming [`BoundAxis::TraceEntries`] where the assumed facts outrun what one trace records.
186fn trace(
187    subject: Identity<identity::Traced>,
188    assumptions: &[OwnerFact],
189) -> Result<DecisionTrace, PlanError> {
190    let mut entries = vec![TraceEntry {
191        subject,
192        decision: TraceDecision::SelectedBecause(SELECTION_FACT),
193    }];
194    entries.extend(assumptions.iter().map(|fact| TraceEntry {
195        subject,
196        decision: TraceDecision::SelectedBecause(*fact),
197    }));
198    let offered = entries.len();
199    DecisionTrace::recorded(entries).map_err(|_| {
200        PlanError::bounded(
201            BoundAxis::TraceEntries,
202            Overflow {
203                capacity: TRACE_ENTRY_LIMIT,
204                offered,
205            },
206        )
207    })
208}
209
210/// The identity of the material one request walked in with.
211///
212/// 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.
213///
214/// # Authority
215///
216/// **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.
217#[must_use]
218pub fn committed(capture: &CapturedInput) -> Identity<identity::CapturedDeclaration> {
219    Identity::derived(Transcript::rooted(
220        identity::Role::CapturedDeclaration,
221        &capture.canonical_bytes(),
222        0,
223    ))
224}
225
226/// The identity of one helper capture read beside a semantic declaration.
227///
228/// 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.
229/// This is the one derivation of a captured helper's identity, so descriptor and adopter roads do not restate the preimage beside this owner.
230#[must_use]
231pub fn committed_helper(
232    declaration: &CapturedInput,
233    helper: &CapturedInput,
234    position: u32,
235) -> Identity<identity::CapturedHelper> {
236    let anchor = committed(declaration);
237    Identity::derived(Transcript::under_projection(
238        identity::Role::CapturedHelper,
239        &anchor,
240        &helper.canonical_bytes(),
241        position,
242    ))
243}
244
245/// What one seat's identities are derived over: the owner-qualified kind, the content commitment, and the seat's own name, each framed.
246///
247/// 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.
248/// 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.
249fn seat_material<R: Role>(
250    kind: Identity<identity::ProjectionKind>,
251    content: Identity<identity::ProjectionContent>,
252    role: R,
253) -> Vec<u8> {
254    let mut material = Vec::new();
255    encode_bytes(kind.as_bytes(), &mut material);
256    encode_bytes(content.as_bytes(), &mut material);
257    encode_bytes(role.name().as_bytes(), &mut material);
258    material
259}
260
261/// What the unit under one seat IS, independently of any bytes.
262fn semantic_key<R: Role>(
263    kind: Identity<identity::ProjectionKind>,
264    content: Identity<identity::ProjectionContent>,
265    stands_over: Identity<identity::CapturedDeclaration>,
266    role: R,
267) -> Identity<identity::GeneratedUnit> {
268    Identity::derived(Transcript::under_projection(
269        identity::Role::GeneratedUnit,
270        &stands_over,
271        &seat_material(kind, content, role),
272        u32::from(role.slot()),
273    ))
274}
275
276/// The origin node one seat's unit stands at.
277fn seat_node<R: Role>(
278    kind: Identity<identity::ProjectionKind>,
279    content: Identity<identity::ProjectionContent>,
280    stands_over: Identity<identity::CapturedDeclaration>,
281    role: R,
282) -> Identity<identity::OriginNode> {
283    Identity::derived(Transcript::under_projection(
284        identity::Role::OriginNode,
285        &stands_over,
286        &seat_material(kind, content, role),
287        u32::from(role.slot()),
288    ))
289}
290
291/// The subject every decision of one request is recorded against.
292fn traced(
293    kind: Identity<identity::ProjectionKind>,
294    content: Identity<identity::ProjectionContent>,
295    stands_over: Identity<identity::CapturedDeclaration>,
296) -> Identity<identity::Traced> {
297    let mut material = Vec::new();
298    encode_bytes(kind.as_bytes(), &mut material);
299    encode_bytes(content.as_bytes(), &mut material);
300    Identity::derived(Transcript::under_projection(
301        identity::Role::Plan,
302        &stands_over,
303        &material,
304        0,
305    ))
306}
307
308/// The kind a request names, by the one fact of a kind that reaches an identity.
309fn named<K: Kind>(producer: Producer) -> Identity<identity::ProjectionKind> {
310    let mut material = Vec::new();
311    encode_bytes(producer.namespace.as_bytes(), &mut material);
312    encode_bytes(producer.name.as_bytes(), &mut material);
313    encode_bytes(K::NAME.as_bytes(), &mut material);
314    Identity::derived(Transcript::rooted(
315        identity::Role::ProjectionKind,
316        &material,
317        0,
318    ))
319}
320
321/// Bind one kind's content to the exact captured declaration and door-qualified kind it was presented under.
322pub fn bound_content<K: Kind>(
323    capture: &CapturedInput,
324    content: K::Content,
325    door: &Door,
326) -> ContentBinding<K> {
327    ContentBinding::bound(committed(capture), named::<K>(door.producer()), content)
328}