1use 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 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
41pub(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
101fn 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
146fn 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
160fn 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
187fn 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
208fn 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
216fn 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#[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#[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
280fn 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
296fn 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
311fn 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
326fn 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
343fn 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
356pub 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}