use super::ClosureIssue;
use crate::identity::{self, Identity, OwnerIdentity, Transcript};
use crate::kind::{Destination, Role};
use crate::plan::{DigestContract, Membership, PlannedMember, PlannedOutput};
use crate::render::{RenderedProjection, RenderedUnit};
pub(super) fn examined<R: Role>(
planned: &Membership<R>,
rendered: &RenderedProjection<R>,
) -> (Vec<ClosureIssue<R>>, Vec<PlannedMember<R>>) {
let mut issues: Vec<ClosureIssue<R>> = Vec::new();
let mut rebuilt: Vec<PlannedMember<R>> = Vec::new();
for role in R::ALL.iter().copied() {
let declared = planned.count_under(role);
if declared > 1 {
issues.push(ClosureIssue::MemberPlannedTwice {
role,
observed: counted(declared),
});
continue;
}
let materialized = count_under(rendered, role);
if materialized > 1 {
issues.push(ClosureIssue::MemberDuplicated {
role,
observed: counted(materialized),
});
continue;
}
match (planned.under(role), under(rendered, role)) {
(Some(_), None) => issues.push(ClosureIssue::MemberMissing { role }),
(None, Some(_)) => issues.push(ClosureIssue::MemberUnplanned { role }),
(None, None) => {}
(Some(member), Some(unit)) => match compared(member, unit, role) {
Ok(agreed) => rebuilt.push(agreed),
Err(issue) => issues.push(issue),
},
}
}
(issues, rebuilt)
}
pub(super) fn addressed<R: Role>(rendered: &RenderedProjection<R>) -> Vec<ClosureIssue<R>> {
let mut issues: Vec<ClosureIssue<R>> = Vec::new();
let mut taken: Vec<OwnerIdentity> = Vec::new();
for unit in units_to(rendered, Destination::PublicationArtifact) {
let role = unit.role();
let Some(address) = unit.address() else {
issues.push(ClosureIssue::ArtifactAddressAbsent { role });
continue;
};
if taken.contains(&address) {
issues.push(ClosureIssue::ArtifactAddressDoubled { role, address });
} else {
taken.push(address);
}
}
issues
}
fn reconstructed<R: Role>(unit: &RenderedUnit<R>) -> PlannedMember<R> {
PlannedMember {
role: unit.role(),
output: PlannedOutput {
semantic_key: unit.semantic_key(),
origin: unit.origin().clone(),
expected_profile: unit.profile(),
address: unit.address(),
digest_contract: DigestContract {
anchored_to: unit.semantic_key(),
},
},
}
}
pub(super) fn under<R: Role>(
rendered: &RenderedProjection<R>,
role: R,
) -> Option<&RenderedUnit<R>> {
rendered.units().iter().find(|unit| unit.role() == role)
}
pub(super) fn count_under<R: Role>(rendered: &RenderedProjection<R>, role: R) -> usize {
rendered
.units()
.iter()
.filter(|unit| unit.role() == role)
.count()
}
pub(super) fn units_to<R: Role>(
rendered: &RenderedProjection<R>,
destination: Destination,
) -> impl Iterator<Item = &RenderedUnit<R>> {
R::ALL.iter().flat_map(move |role| {
rendered
.units()
.iter()
.filter(move |unit| unit.role() == *role && role.destination() == destination)
})
}
fn digest_under<R: Role>(
unit: &RenderedUnit<R>,
contract: DigestContract,
) -> Identity<identity::OutputBytes> {
let raw = unit.tree().canonical_bytes();
Identity::derived(Transcript::under_projection(
identity::Role::OutputBytes,
&contract.anchored_to,
&raw,
u32::from(unit.role().slot()),
))
}
fn compared<R: Role>(
member: &PlannedMember<R>,
unit: &RenderedUnit<R>,
role: R,
) -> Result<PlannedMember<R>, ClosureIssue<R>> {
let rebuild = reconstructed(unit);
if rebuild.output.semantic_key != member.output.semantic_key {
Err(ClosureIssue::SemanticKeyMismatch { role })
} else if rebuild.output.origin != member.output.origin {
Err(ClosureIssue::OriginOrphan { role })
} else if digest_under(unit, member.output.digest_contract) != unit.digest() {
Err(ClosureIssue::DigestMismatch { role })
} else if rebuild.output.expected_profile != member.output.expected_profile
|| rebuild.output.address != member.output.address
{
Err(ClosureIssue::MaterializationMismatch { role })
} else {
Ok(rebuild)
}
}
fn counted(observed: usize) -> u32 {
u32::try_from(observed).unwrap_or(u32::MAX)
}