Skip to main content

structured_asic/archive/
preservation.rs

1//! Exact original A.7 actors, preceding obligations and protected material input.
2
3use 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
25/// Actual common check, with its independently retained evidence moved exactly
26/// once into the selected TSA actor. It performs no second signature or copy.
27pub 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    /// Actual original time-assertion member.
35    #[must_use]
36    pub fn token(&self) -> MemberId<S::Document, S::Generation> {
37        self.token
38    }
39    /// Actual original complete manifest input, not its name or a rebuilt XML.
40    #[must_use]
41    pub fn input(&self) -> MemberId<S::Document, S::Generation> {
42        self.input
43    }
44    /// All original check facts; mathematical evidence has moved into `actor()`.
45    #[must_use]
46    pub fn facts(&self) -> &CheckedTimestamp<'d> {
47        &self.facts
48    }
49    /// Same actual certificate/key used by complete checking. None preserves
50    /// a negative original check and cannot create a TSA path or material proof.
51    #[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
59/// Complete original actor result, preserving large retained failure context.
60pub 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>;
70/// Complete source-derived preceding TSA requirements, or explicit unavailability.
71pub type RequirementsResult<'v, 's, S> = Result<
72    Option<TimestampRequirements<SourceNode, DocumentId<'v, 's, S>, (), Asic>>,
73    ArchiveError<<S as RangeSource>::Error>,
74>;
75/// Actual protected original-member selection, independently of trust/material.
76pub 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    /// Checks one actual historical assertion over an original complete input.
83    /// The role selects data versus archive-input mathematics, not container
84    /// conformance. Only the source-derived graph can mint archive coverage.
85    #[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    /// Checks the actual original token of this graph node against its complete
152    /// original manifest bytes, then records the exact selected TSA actor for
153    /// the existing LTV path/status/material machinery. No generic attach exists.
154    #[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    /// Complete preceding TSA obligations for exactly this original archive
205    /// input. A broken/uninspected graph yields no complete census. The first
206    /// timestamp has no preceding TSA, while its own new-TSA material remains
207    /// a separate incorporation/placement obligation.
208    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    /// Mints protected whole-member coverage only after this ACTUAL TST covers
259    /// its original manifest and EVERY declared object digest matched. Neither
260    /// geometry alone nor an arbitrary later successful token supplies it.
261    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}