use super::Door;
use super::SELECTION_FACT;
use super::types::{Selection, Statements};
use crate::bounded::{Bounded, Overflow};
use crate::identity::{
self, Identity, OwnerFact, OwnerIdentity, Profile, Transcript, encode_bytes,
};
use crate::kind::{Destination, Kind, Role};
use crate::origin::{
DecisionTrace, OriginEdge, OriginRelation, OriginTrail, TRACE_ENTRY_LIMIT, TraceDecision,
TraceEntry,
};
use crate::plan::{
Account, BoundAxis, ContentBinding, Context, DigestContract, Membership, Plan, PlanDecisions,
PlanError, PlanIssue, PlannedMember, PlannedOutput,
};
use crate::request::Producer;
use crate::token::CapturedInput;
impl<'request, R: Role> Statements<'request, R> {
pub(super) const fn from_request(
assumptions: &'request [OwnerFact],
addresses: &'request [(R, OwnerIdentity)],
selection: &'request Selection<R>,
) -> Self {
Self {
assumptions,
addresses,
selection,
}
}
}
pub(super) fn planned<K: Kind>(
capture: &CapturedInput,
content: K::Content,
door: &Door,
dependencies: Vec<Identity<identity::CapturedDeclaration>>,
profile: Profile,
statements: &Statements<'_, K::Role>,
) -> Result<Plan<K>, PlanError> {
let roles = selected_roles(statements.selection);
consumable(statements.addresses, &roles)?;
let account = Account::standing_on(bound_content(capture, content, door), dependencies)?;
let stands_over = account.commitment();
let content_commitment = account.content_commitment();
let kind = account.kind();
let decided_under = Context::under(profile);
let invalidation = decided_under.watch_set(&account)?;
let authored = account.origin_node();
let membership = membership(
stands_over,
content_commitment,
authored,
profile,
statements.addresses,
kind,
&roles,
)?;
let origin = OriginTrail::from_edge(OriginEdge {
from: authored,
relation: OriginRelation::AuthoredDeclaration,
to: seat_node(
kind,
content_commitment,
stands_over,
membership.first().role,
),
});
let trace = trace(
traced(kind, content_commitment, stands_over),
statements.assumptions,
)?;
Ok(Plan::planned(
account,
decided_under,
PlanDecisions {
membership,
invalidation,
trace,
origin,
nonclaims: Bounded::empty(),
},
))
}
fn membership<R: Role>(
stands_over: Identity<identity::CapturedDeclaration>,
content: Identity<identity::ProjectionContent>,
authored: Identity<identity::OriginNode>,
profile: Profile,
addresses: &[(R, OwnerIdentity)],
kind: Identity<identity::ProjectionKind>,
roles: &[R],
) -> Result<Membership<R>, PlanError> {
let mut seats = roles.iter().copied();
let Some(head) = seats.next() else {
return Err(PlanError::of(PlanIssue::UnknownKind { named: kind }));
};
let rest = seats
.map(|role| {
member(
kind,
content,
stands_over,
authored,
profile,
role,
addresses,
)
})
.collect();
Membership::declared(
member(
kind,
content,
stands_over,
authored,
profile,
head,
addresses,
),
rest,
)
}
fn selected_roles<R: Role>(selection: &Selection<R>) -> Vec<R> {
let mut roles = match selection {
Selection::All => R::ALL.to_vec(),
Selection::Declared { first, rest } => {
let mut roles = vec![*first];
roles.extend(rest.iter().copied());
roles
}
};
roles.sort_by_key(|role| role.slot());
roles
}
fn member<R: Role>(
kind: Identity<identity::ProjectionKind>,
content: Identity<identity::ProjectionContent>,
stands_over: Identity<identity::CapturedDeclaration>,
authored: Identity<identity::OriginNode>,
profile: Profile,
role: R,
addresses: &[(R, OwnerIdentity)],
) -> PlannedMember<R> {
let key = semantic_key(kind, content, stands_over, role);
PlannedMember {
role,
output: PlannedOutput {
semantic_key: key,
origin: OriginTrail::from_edge(OriginEdge {
from: authored,
relation: OriginRelation::SemanticDerivation,
to: seat_node(kind, content, stands_over, role),
}),
expected_profile: profile,
address: addressed(role, addresses),
digest_contract: DigestContract { anchored_to: key },
},
}
}
fn consumable<R: Role>(addresses: &[(R, OwnerIdentity)], selected: &[R]) -> Result<(), PlanError> {
let mut inert = addresses
.iter()
.filter(|(seat, _)| {
!selected.contains(seat) || seat.destination() != Destination::PublicationArtifact
})
.map(|(seat, _)| PlanIssue::AddressInert { seat: seat.name() });
match inert.next() {
Some(issue) => Err(PlanError::over(issue, inert.collect())),
None => Ok(()),
}
}
fn addressed<R: Role>(role: R, addresses: &[(R, OwnerIdentity)]) -> Option<OwnerIdentity> {
addresses
.iter()
.find(|(seat, _)| *seat == role)
.map(|(_, address)| *address)
}
fn trace(
subject: Identity<identity::Traced>,
assumptions: &[OwnerFact],
) -> Result<DecisionTrace, PlanError> {
let mut entries = vec![TraceEntry {
subject,
decision: TraceDecision::SelectedBecause(SELECTION_FACT),
}];
entries.extend(assumptions.iter().map(|fact| TraceEntry {
subject,
decision: TraceDecision::SelectedBecause(*fact),
}));
let offered = entries.len();
DecisionTrace::recorded(entries).map_err(|_| {
PlanError::bounded(
BoundAxis::TraceEntries,
Overflow {
capacity: TRACE_ENTRY_LIMIT,
offered,
},
)
})
}
#[must_use]
pub fn committed(capture: &CapturedInput) -> Identity<identity::CapturedDeclaration> {
Identity::derived(Transcript::rooted(
identity::Role::CapturedDeclaration,
&capture.canonical_bytes(),
0,
))
}
#[must_use]
pub fn committed_helper(
declaration: &CapturedInput,
helper: &CapturedInput,
position: u32,
) -> Identity<identity::CapturedHelper> {
let anchor = committed(declaration);
Identity::derived(Transcript::under_projection(
identity::Role::CapturedHelper,
&anchor,
&helper.canonical_bytes(),
position,
))
}
fn seat_material<R: Role>(
kind: Identity<identity::ProjectionKind>,
content: Identity<identity::ProjectionContent>,
role: R,
) -> Vec<u8> {
let mut material = Vec::new();
encode_bytes(kind.as_bytes(), &mut material);
encode_bytes(content.as_bytes(), &mut material);
encode_bytes(role.name().as_bytes(), &mut material);
material
}
fn semantic_key<R: Role>(
kind: Identity<identity::ProjectionKind>,
content: Identity<identity::ProjectionContent>,
stands_over: Identity<identity::CapturedDeclaration>,
role: R,
) -> Identity<identity::GeneratedUnit> {
Identity::derived(Transcript::under_projection(
identity::Role::GeneratedUnit,
&stands_over,
&seat_material(kind, content, role),
u32::from(role.slot()),
))
}
fn seat_node<R: Role>(
kind: Identity<identity::ProjectionKind>,
content: Identity<identity::ProjectionContent>,
stands_over: Identity<identity::CapturedDeclaration>,
role: R,
) -> Identity<identity::OriginNode> {
Identity::derived(Transcript::under_projection(
identity::Role::OriginNode,
&stands_over,
&seat_material(kind, content, role),
u32::from(role.slot()),
))
}
fn traced(
kind: Identity<identity::ProjectionKind>,
content: Identity<identity::ProjectionContent>,
stands_over: Identity<identity::CapturedDeclaration>,
) -> Identity<identity::Traced> {
let mut material = Vec::new();
encode_bytes(kind.as_bytes(), &mut material);
encode_bytes(content.as_bytes(), &mut material);
Identity::derived(Transcript::under_projection(
identity::Role::Plan,
&stands_over,
&material,
0,
))
}
fn named<K: Kind>(producer: Producer) -> Identity<identity::ProjectionKind> {
let mut material = Vec::new();
encode_bytes(producer.namespace.as_bytes(), &mut material);
encode_bytes(producer.name.as_bytes(), &mut material);
encode_bytes(K::NAME.as_bytes(), &mut material);
Identity::derived(Transcript::rooted(
identity::Role::ProjectionKind,
&material,
0,
))
}
pub fn bound_content<K: Kind>(
capture: &CapturedInput,
content: K::Content,
door: &Door,
) -> ContentBinding<K> {
ContentBinding::bound(committed(capture), named::<K>(door.producer()), content)
}