1use 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
25pub(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
81fn 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
125fn 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
152fn 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
173fn 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
181fn 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#[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#[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
245fn 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
261fn 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
276fn 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
291fn 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
308fn 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
321pub 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}