use super::{
ArchiveError, AssertionScope, Graph, InputAssociation, MemberId, RangeSource, Work, resource,
};
use crate::{
Asic, ISSUER,
content::Content,
source::{DocumentId, Node as SourceNode},
timestamp,
};
use structured_asn1::{Reader, memory::Execution};
use structured_cms::{
provider::SignatureProvider,
timestamp::{CheckContext, CheckedTimestamp, Token, TokenExtension},
};
use structured_ltv::{
material::Coverage,
source::{Location, Query},
timestamp::{
CheckedActor, Purpose, RequiredToken, TimestampRequirements, TimestampScope, Verified,
},
};
pub struct ActorCheck<'c, 'd, 'v, 's, S: RangeSource> {
token: MemberId<S::Document, S::Generation>,
input: MemberId<S::Document, S::Generation>,
facts: CheckedTimestamp<'d>,
actor: Option<CheckedActor<'c, 'd, SourceNode, DocumentId<'v, 's, S>, (), Asic>>,
}
impl<'c, 'd, 'v, 's, S: RangeSource> ActorCheck<'c, 'd, 'v, 's, S> {
#[must_use]
pub fn token(&self) -> MemberId<S::Document, S::Generation> {
self.token
}
#[must_use]
pub fn input(&self) -> MemberId<S::Document, S::Generation> {
self.input
}
#[must_use]
pub fn facts(&self) -> &CheckedTimestamp<'d> {
&self.facts
}
#[must_use]
pub fn actor(
&self,
) -> Option<&CheckedActor<'c, 'd, SourceNode, DocumentId<'v, 's, S>, (), Asic>> {
self.actor.as_ref()
}
}
pub type ActorResult<'c, 'd, 'v, 's, S, PE, RE> = Result<
ActorCheck<'c, 'd, 'v, 's, S>,
timestamp::Error<
<S as RangeSource>::Error,
PE,
RE,
<S as RangeSource>::Document,
<S as RangeSource>::Generation,
>,
>;
pub type RequirementsResult<'v, 's, S> = Result<
Option<TimestampRequirements<SourceNode, DocumentId<'v, 's, S>, (), Asic>>,
ArchiveError<<S as RangeSource>::Error>,
>;
pub type CoverageResult<'q, 'v, 's, S> = Result<
Option<Coverage<SourceNode, DocumentId<'v, 's, S>, (), Execution<'q>, Asic>>,
ArchiveError<<S as RangeSource>::Error>,
>;
impl<'v, 's, S: RangeSource> Content<'v, 's, S> {
#[expect(
clippy::too_many_arguments,
reason = "actual source, provider, selected TSA context, complete input and its explicit role are independent contracts"
)]
#[expect(
clippy::result_large_err,
reason = "preserve admitted original diagnostic maps when an allocation cannot be made"
)]
pub async fn check_assertion_actor<'c, 'd, P, PE, RE>(
&'d self,
token: &'d Token<'d>,
parent: &Reader<'d>,
provider: &'d P,
context: &CheckContext<'d, 'c>,
input: &impl crate::payload::Payload<'v, 's, S>,
kind: timestamp::InputKind,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> ActorResult<'c, 'd, 'v, 's, S, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
let work = parent.execution();
let checked = self
.check_timestamp(
token,
parent,
provider,
timestamp::Context::Historical(context),
input,
recognizes,
)
.await?;
let mut facts = checked.into_common();
let query = self.member().archive().ltv_query(&work);
let purpose = match kind {
timestamp::InputKind::Data => Purpose::Content,
timestamp::InputKind::Archive => Purpose::Archive,
};
let actor = facts.take_evidence().map(|evidence| {
let verified = Verified::new(
ISSUER,
Location {
snapshot: query.snapshot(),
node: SourceNode::Member(self.member().id().ordinal()),
},
SourceNode::Root,
SourceNode::Member(input.member().id().ordinal()),
purpose,
evidence,
);
CheckedActor::new(ISSUER, verified, context.certificate, context.key)
});
self.member()
.archive()
.admit(&work, self.enclosure())
.map_err(timestamp::Error::Archive)?;
Ok(ActorCheck {
token: self.member().id(),
input: input.member().id(),
facts,
actor,
})
}
}
impl<'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource> Graph<'v, 's, N, D, G, S> {
#[expect(
clippy::too_many_arguments,
reason = "the unchanged common token/provider/TSA/UTC context and selected source node remain explicit"
)]
#[expect(
clippy::result_large_err,
reason = "original source maps and processing causes must survive resource refusal without an error allocation"
)]
pub async fn check_actor<'c, 'd, P, PE, RE>(
&self,
index: usize,
carried: &'d Content<'v, 's, S>,
token: &'d Token<'d>,
parent: &Reader<'d>,
provider: &'d P,
context: &CheckContext<'d, 'c>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> ActorResult<'c, 'd, 'v, 's, S, PE, RE>
where
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
let work = parent.execution();
let foreign = || {
timestamp::Error::Archive(ArchiveError::Foreign {
range: self.archive.directory_span(),
})
};
let node = self.nodes.get(index).ok_or_else(foreign)?;
if !core::ptr::eq(carried.member().archive(), self.archive)
|| node.timestamp != Some(carried.member().id())
{
return Err(foreign());
}
let input = self
.archive
.original(node.member, &work)
.map_err(timestamp::Error::Archive)?;
carried
.check_assertion_actor(
token,
parent,
provider,
context,
&input,
timestamp::InputKind::Archive,
recognizes,
)
.await
}
pub fn requirements<W: Work>(
&self,
index: usize,
layout: &crate::layout::Inspection<'_, 'v, 's, S>,
work: &W,
) -> RequirementsResult<'v, 's, S> {
let original = self.timestamp_inputs(index, layout, work)?;
if !original.complete() {
return Ok(None);
}
let query = self.archive.ltv_query(work);
let execution = query.execution();
self.archive
.admit(execution, self.archive.directory_span())?;
let signature = SourceNode::Root;
let mut entries = execution.vector();
for prior in original.entries() {
execution
.charge_work(1)
.map_err(|cause| resource(self.archive.directory_span(), cause))?;
if prior.scope != AssertionScope::Declared {
continue;
}
let InputAssociation::Known { member, purpose } = prior.association else {
return Ok(None);
};
entries
.try_push(RequiredToken {
signature,
property: SourceNode::Member(member.ordinal()),
token: SourceNode::Member(prior.token.ordinal()),
purpose,
})
.map_err(|cause| resource(self.archive.directory_span(), cause))?;
}
let current = &self.nodes[index];
let Some(token) = current.timestamp else {
return Ok(None);
};
Ok(Some(TimestampRequirements::new(
ISSUER,
signature,
TimestampScope::BeforeArchive(Location {
snapshot: query.snapshot(),
node: SourceNode::Member(token.ordinal()),
}),
entries.into_owned(),
)))
}
pub fn coverage<'q, 'c, 'd, W: Work>(
&self,
index: usize,
checked: &ActorCheck<'c, 'd, 'v, 's, S>,
work: &'q W,
) -> CoverageResult<'q, 'v, 's, S>
where
'v: 'q,
{
let query = self.archive.ltv_query(work);
let execution = query.execution();
self.archive
.admit(execution, self.archive.directory_span())?;
let Some(node) = self.nodes.get(index) else {
return Ok(None);
};
if checked.input != node.member || Some(checked.token) != node.timestamp {
return Err(ArchiveError::Foreign {
range: self.archive.directory_span(),
});
}
if !self.geometry_complete() {
return Ok(None);
}
let Some(actor) = checked.actor.as_ref() else {
return Ok(None);
};
let Some(facts) = node
.facts
.and_then(|index| self.facts[index].object_digests())
else {
return Ok(None);
};
if facts.is_empty() {
return Ok(None);
}
for fact in facts {
execution
.charge_work(1)
.map_err(|cause| resource(self.archive.directory_span(), cause))?;
if *fact
!= crate::references::DigestReading::Checked(
structured_xades::certificate::Agreement::Matches,
)
{
return Ok(None);
}
}
if actor.verified().token().snapshot != query.snapshot() {
return Err(ArchiveError::Foreign {
range: self.archive.directory_span(),
});
}
let mut items = execution.map();
items
.try_insert(SourceNode::Member(node.member.ordinal()), true)
.map_err(|cause| resource(self.archive.directory_span(), cause))?;
for member in node.objects().iter().flatten() {
items
.try_insert(SourceNode::Member(member.ordinal()), true)
.map_err(|cause| resource(self.archive.directory_span(), cause))?;
}
Ok(Some(Coverage::for_token(
ISSUER,
query.snapshot(),
actor.verified().signature(),
SourceNode::Member(node.member.ordinal()),
SourceNode::Member(checked.token.ordinal()),
Purpose::Archive,
items,
)))
}
}