1use super::{
4 ArchiveError, AssertionScope, Graph, InputAssociation, MemberId, RangeSource, Work, resource,
5};
6use crate::{
7 Asic, ISSUER,
8 content::Content,
9 source::{DocumentId, Node as SourceNode},
10 timestamp,
11};
12use structured_asn1::{Reader, memory::Execution};
13use structured_cms::{
14 provider::SignatureProvider,
15 timestamp::{CheckContext, CheckedTimestamp, Token, TokenExtension},
16};
17use structured_ltv::{
18 material::Coverage,
19 source::{Location, Query},
20 timestamp::{
21 CheckedActor, Purpose, RequiredToken, TimestampRequirements, TimestampScope, Verified,
22 },
23};
24
25pub struct ActorCheck<'c, 'd, 'v, 's, S: RangeSource> {
28 token: MemberId<S::Document, S::Generation>,
29 input: MemberId<S::Document, S::Generation>,
30 facts: CheckedTimestamp<'d>,
31 actor: Option<CheckedActor<'c, 'd, SourceNode, DocumentId<'v, 's, S>, (), Asic>>,
32}
33impl<'c, 'd, 'v, 's, S: RangeSource> ActorCheck<'c, 'd, 'v, 's, S> {
34 #[must_use]
36 pub fn token(&self) -> MemberId<S::Document, S::Generation> {
37 self.token
38 }
39 #[must_use]
41 pub fn input(&self) -> MemberId<S::Document, S::Generation> {
42 self.input
43 }
44 #[must_use]
46 pub fn facts(&self) -> &CheckedTimestamp<'d> {
47 &self.facts
48 }
49 #[must_use]
52 pub fn actor(
53 &self,
54 ) -> Option<&CheckedActor<'c, 'd, SourceNode, DocumentId<'v, 's, S>, (), Asic>> {
55 self.actor.as_ref()
56 }
57}
58
59pub type ActorResult<'c, 'd, 'v, 's, S, PE, RE> = Result<
61 ActorCheck<'c, 'd, 'v, 's, S>,
62 timestamp::Error<
63 <S as RangeSource>::Error,
64 PE,
65 RE,
66 <S as RangeSource>::Document,
67 <S as RangeSource>::Generation,
68 >,
69>;
70pub type RequirementsResult<'v, 's, S> = Result<
72 Option<TimestampRequirements<SourceNode, DocumentId<'v, 's, S>, (), Asic>>,
73 ArchiveError<<S as RangeSource>::Error>,
74>;
75pub type CoverageResult<'q, 'v, 's, S> = Result<
77 Option<Coverage<SourceNode, DocumentId<'v, 's, S>, (), Execution<'q>, Asic>>,
78 ArchiveError<<S as RangeSource>::Error>,
79>;
80
81impl<'v, 's, S: RangeSource> Content<'v, 's, S> {
82 #[expect(
86 clippy::too_many_arguments,
87 reason = "actual source, provider, selected TSA context, complete input and its explicit role are independent contracts"
88 )]
89 #[expect(
90 clippy::result_large_err,
91 reason = "preserve admitted original diagnostic maps when an allocation cannot be made"
92 )]
93 pub async fn check_assertion_actor<'c, 'd, P, PE, RE>(
94 &'d self,
95 token: &'d Token<'d>,
96 parent: &Reader<'d>,
97 provider: &'d P,
98 context: &CheckContext<'d, 'c>,
99 input: &impl crate::payload::Payload<'v, 's, S>,
100 kind: timestamp::InputKind,
101 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
102 ) -> ActorResult<'c, 'd, 'v, 's, S, PE, RE>
103 where
104 P: for<'scope> SignatureProvider<'scope, Error = PE>,
105 {
106 let work = parent.execution();
107 let checked = self
108 .check_timestamp(
109 token,
110 parent,
111 provider,
112 timestamp::Context::Historical(context),
113 input,
114 recognizes,
115 )
116 .await?;
117 let mut facts = checked.into_common();
118 let query = self.member().archive().ltv_query(&work);
119 let purpose = match kind {
120 timestamp::InputKind::Data => Purpose::Content,
121 timestamp::InputKind::Archive => Purpose::Archive,
122 };
123 let actor = facts.take_evidence().map(|evidence| {
124 let verified = Verified::new(
125 ISSUER,
126 Location {
127 snapshot: query.snapshot(),
128 node: SourceNode::Member(self.member().id().ordinal()),
129 },
130 SourceNode::Root,
131 SourceNode::Member(input.member().id().ordinal()),
132 purpose,
133 evidence,
134 );
135 CheckedActor::new(ISSUER, verified, context.certificate, context.key)
136 });
137 self.member()
138 .archive()
139 .admit(&work, self.enclosure())
140 .map_err(timestamp::Error::Archive)?;
141 Ok(ActorCheck {
142 token: self.member().id(),
143 input: input.member().id(),
144 facts,
145 actor,
146 })
147 }
148}
149
150impl<'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource> Graph<'v, 's, N, D, G, S> {
151 #[expect(
155 clippy::too_many_arguments,
156 reason = "the unchanged common token/provider/TSA/UTC context and selected source node remain explicit"
157 )]
158 #[expect(
159 clippy::result_large_err,
160 reason = "original source maps and processing causes must survive resource refusal without an error allocation"
161 )]
162 pub async fn check_actor<'c, 'd, P, PE, RE>(
163 &self,
164 index: usize,
165 carried: &'d Content<'v, 's, S>,
166 token: &'d Token<'d>,
167 parent: &Reader<'d>,
168 provider: &'d P,
169 context: &CheckContext<'d, 'c>,
170 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
171 ) -> ActorResult<'c, 'd, 'v, 's, S, PE, RE>
172 where
173 P: for<'scope> SignatureProvider<'scope, Error = PE>,
174 {
175 let work = parent.execution();
176 let foreign = || {
177 timestamp::Error::Archive(ArchiveError::Foreign {
178 range: self.archive.directory_span(),
179 })
180 };
181 let node = self.nodes.get(index).ok_or_else(foreign)?;
182 if !core::ptr::eq(carried.member().archive(), self.archive)
183 || node.timestamp != Some(carried.member().id())
184 {
185 return Err(foreign());
186 }
187 let input = self
188 .archive
189 .original(node.member, &work)
190 .map_err(timestamp::Error::Archive)?;
191 carried
192 .check_assertion_actor(
193 token,
194 parent,
195 provider,
196 context,
197 &input,
198 timestamp::InputKind::Archive,
199 recognizes,
200 )
201 .await
202 }
203
204 pub fn requirements<W: Work>(
209 &self,
210 index: usize,
211 layout: &crate::layout::Inspection<'_, 'v, 's, S>,
212 work: &W,
213 ) -> RequirementsResult<'v, 's, S> {
214 let original = self.timestamp_inputs(index, layout, work)?;
215 if !original.complete() {
216 return Ok(None);
217 }
218 let query = self.archive.ltv_query(work);
219 let execution = query.execution();
220 self.archive
221 .admit(execution, self.archive.directory_span())?;
222 let signature = SourceNode::Root;
223 let mut entries = execution.vector();
224 for prior in original.entries() {
225 execution
226 .charge_work(1)
227 .map_err(|cause| resource(self.archive.directory_span(), cause))?;
228 if prior.scope != AssertionScope::Declared {
229 continue;
230 }
231 let InputAssociation::Known { member, purpose } = prior.association else {
232 return Ok(None);
233 };
234 entries
235 .try_push(RequiredToken {
236 signature,
237 property: SourceNode::Member(member.ordinal()),
238 token: SourceNode::Member(prior.token.ordinal()),
239 purpose,
240 })
241 .map_err(|cause| resource(self.archive.directory_span(), cause))?;
242 }
243 let current = &self.nodes[index];
244 let Some(token) = current.timestamp else {
245 return Ok(None);
246 };
247 Ok(Some(TimestampRequirements::new(
248 ISSUER,
249 signature,
250 TimestampScope::BeforeArchive(Location {
251 snapshot: query.snapshot(),
252 node: SourceNode::Member(token.ordinal()),
253 }),
254 entries.into_owned(),
255 )))
256 }
257
258 pub fn coverage<'q, 'c, 'd, W: Work>(
262 &self,
263 index: usize,
264 checked: &ActorCheck<'c, 'd, 'v, 's, S>,
265 work: &'q W,
266 ) -> CoverageResult<'q, 'v, 's, S>
267 where
268 'v: 'q,
269 {
270 let query = self.archive.ltv_query(work);
271 let execution = query.execution();
272 self.archive
273 .admit(execution, self.archive.directory_span())?;
274 let Some(node) = self.nodes.get(index) else {
275 return Ok(None);
276 };
277 if checked.input != node.member || Some(checked.token) != node.timestamp {
278 return Err(ArchiveError::Foreign {
279 range: self.archive.directory_span(),
280 });
281 }
282 if !self.geometry_complete() {
283 return Ok(None);
284 }
285 let Some(actor) = checked.actor.as_ref() else {
286 return Ok(None);
287 };
288 let Some(facts) = node
289 .facts
290 .and_then(|index| self.facts[index].object_digests())
291 else {
292 return Ok(None);
293 };
294 if facts.is_empty() {
295 return Ok(None);
296 }
297 for fact in facts {
298 execution
299 .charge_work(1)
300 .map_err(|cause| resource(self.archive.directory_span(), cause))?;
301 if *fact
302 != crate::references::DigestReading::Checked(
303 structured_xades::certificate::Agreement::Matches,
304 )
305 {
306 return Ok(None);
307 }
308 }
309 if actor.verified().token().snapshot != query.snapshot() {
310 return Err(ArchiveError::Foreign {
311 range: self.archive.directory_span(),
312 });
313 }
314 let mut items = execution.map();
315 items
316 .try_insert(SourceNode::Member(node.member.ordinal()), true)
317 .map_err(|cause| resource(self.archive.directory_span(), cause))?;
318 for member in node.objects().iter().flatten() {
319 items
320 .try_insert(SourceNode::Member(member.ordinal()), true)
321 .map_err(|cause| resource(self.archive.directory_span(), cause))?;
322 }
323 Ok(Some(Coverage::for_token(
324 ISSUER,
325 query.snapshot(),
326 actor.verified().signature(),
327 SourceNode::Member(node.member.ordinal()),
328 SourceNode::Member(checked.token.ordinal()),
329 Purpose::Archive,
330 items,
331 )))
332 }
333}