Skip to main content

structured_cms/evidence/
verify.rs

1//! Complete ERS sequence execution; historical acceptance is explicitly injected.
2
3mod engine;
4
5use super::{
6    ArchiveTimeStamp, EvidenceRecord, FIELD, Membership, PriorSequence, TimeStamp,
7    input::InputError,
8};
9use crate::{
10    Error, ErrorCode,
11    content_source::ContentError,
12    digest::PassError,
13    provider::SignatureProvider,
14    stream::{RangesError, Source},
15    timestamp::{
16        self, CheckContext, CheckedTimestamp, Evidence, InfoError, ProfileCheckError,
17        ProfileFailure, TokenExtension,
18    },
19};
20use core::convert::Infallible;
21use structured_asn1::{
22    Reader,
23    digest::Resolution,
24    memory::{Execution, OwnedVec, Work},
25};
26use structured_pkix::{
27    Certificate,
28    algorithm::digest::{self, Algorithm},
29    path::WorkingKey,
30    temporal::{Instant, LeapTable},
31};
32
33/// Actual certificate and effective key to use for this timestamp's common check.
34pub struct Selection<'a> {
35    /// Original selected candidate, still subject to SID/ESS/TSA checking.
36    pub certificate: &'a Certificate<'a>,
37    /// That certificate's own producing source and resource ceiling.
38    pub certificate_parent: &'a Reader<'a>,
39    /// Effective parameters from the selected candidate's actual path.
40    pub key: WorkingKey<'a>,
41    /// Caller-authenticated accuracy for this actual TSA and signed policy.
42    pub policy_accuracy: Option<timestamp::PolicyAccuracy<'a>>,
43}
44impl<'a> Selection<'a> {
45    fn historical_context<'s>(&'s self, utc: &'s LeapTable<'a>) -> CheckContext<'a, 's> {
46        CheckContext {
47            certificate: self.certificate,
48            certificate_parent: self.certificate_parent,
49            key: self.key,
50            requested: None,
51            utc,
52            reference: None,
53            window: None,
54            policy_accuracy: self.policy_accuracy,
55        }
56    }
57}
58/// Caller selection; the ERS mechanism executes the check on the actual token.
59pub trait Authorities<'document> {
60    /// Original selection/provider-policy refusal.
61    type Error;
62    /// Selects a candidate for this exact original timestamp ordinal.
63    fn select<'a, W: Work>(
64        &'a self,
65        index: usize,
66        stamp: &'a ArchiveTimeStamp<'document>,
67        work: &W,
68    ) -> Result<Selection<'a>, Self::Error>
69    where
70        'document: 'a;
71}
72
73/// Caller-established historical validation of the supplied actual participant.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum Decision {
76    /// Path/status, signed ESS restrictions and algorithm suitability all hold.
77    Valid,
78    /// At least one required historical fact is known to fail.
79    Invalid,
80    /// Required historical evidence is unavailable or uncertain.
81    Unknown,
82}
83/// Facts for historical validation, never a request to infer trust from genTime.
84pub struct Historical<'a, 'token> {
85    /// Original flat timestamp ordinal.
86    pub index: usize,
87    /// Complete common result for the actual selected original token.
88    pub current: &'a CheckedTimestamp<'token>,
89    /// Candidate/key used by this same common check.
90    pub selection: &'a Selection<'token>,
91    /// Next token's successful mathematical facts, when present.
92    pub following: Option<&'a Evidence>,
93    /// First token of the next chain: the tree algorithm must remain suitable
94    /// through this renewal, even for earlier tokens in the current chain.
95    pub following_chain: Option<&'a Evidence>,
96    /// Explicit current verification reference for the final timestamp.
97    pub verification_time: Instant,
98}
99
100/// Failed preflight retains the actual original source and carried source map.
101#[derive(Debug)]
102pub enum PlanError {
103    /// Source/work/schema refusal before covered content is accessed.
104    Processing(Error),
105    /// Original carried `TSTInfo` refusal and its independently retained source.
106    Information(ContentError<InfoError<AlgorithmError>>),
107    /// Retained envelope/ESS/CMS facts already prevent a conforming token.
108    Nonconforming {
109        /// Actual original timestamp ordinal.
110        index: usize,
111        /// Honest original envelope enclosure.
112        span: structured_asn1::Span,
113    },
114    /// An alternative timestamp format has no selected execution mechanism.
115    UnsupportedTimestamp {
116        /// Actual original archive timestamp ordinal.
117        index: usize,
118    },
119    /// A digest is unknown to the shared standard algorithm resolver.
120    UnsupportedAlgorithm {
121        /// Actual original enclosing position.
122        at: u64,
123    },
124}
125/// Algorithm inspection preserves unavailable capability separately from syntax
126/// and resource errors, including inside a temporary carried `TSTInfo` source.
127#[derive(Debug)]
128pub enum AlgorithmError {
129    /// Unchanged original schema/encoding/resource refusal.
130    Processing(Error),
131    /// The written digest has no standard semantic resolver.
132    Unsupported {
133        /// Original source-relative algorithm position.
134        at: u64,
135    },
136}
137
138/// Complete operation refusal; no successful partial sequence is returned.
139#[derive(Debug)]
140pub enum VerificationError<SE, PE, RE, AE, HE, FE = Infallible> {
141    /// The declared first hash list cannot cover this actual object/group.
142    Membership {
143        /// Original archive timestamp ordinal.
144        index: usize,
145    },
146    /// Original preflight failure before covered-source I/O.
147    Plan(PlanError),
148    /// Common capacity/work/source refusal.
149    Processing(Error),
150    /// Actual object acquisition or selected-provider hashing failed.
151    Acquisition(PassError<SE, PE>),
152    /// Selection refused this exact original token.
153    Selection {
154        /// Original flat timestamp ordinal.
155        index: usize,
156        /// Unchanged selector refusal.
157        cause: AE,
158    },
159    /// Complete common check over cached actual object digests refused.
160    Timestamp {
161        /// Original flat timestamp ordinal.
162        index: usize,
163        /// Unchanged common/input/provider/recognition failure.
164        cause: ProfileCheckError<InputError<SE, PE>, PE, RE, FE>,
165    },
166    /// Complete common check over the preceding timestamp refused.
167    Renewal {
168        /// Original flat timestamp ordinal.
169        index: usize,
170        /// Unchanged renewal check failure.
171        cause: ProfileCheckError<InputError<RangesError, PE>, PE, RE, FE>,
172    },
173    /// Historical validator failed to execute; not a negative document finding.
174    Historical {
175        /// Original flat timestamp ordinal.
176        index: usize,
177        /// Unchanged validator refusal.
178        cause: HE,
179    },
180}
181
182/// Complete record result with unchanged acquisition, provider, selector,
183/// recognizer, historical and profile causes; diagnostics allocate nothing.
184pub type VerificationResult<'a, 'd, SE, PE, RE, AE, HE, FE = Infallible> =
185    Result<Report<'a, 'd>, VerificationError<SE, PE, RE, AE, HE, FE>>;
186
187/// Result for one original token; editing public facts cannot create a receipt.
188pub struct Checked<'a> {
189    index: usize,
190    common: CheckedTimestamp<'a>,
191    selection: Selection<'a>,
192    history: Decision,
193}
194impl<'a> Checked<'a> {
195    /// Original flat timestamp ordinal, independent of verification order.
196    #[must_use]
197    pub fn index(&self) -> usize {
198        self.index
199    }
200    /// Complete common timestamp result, including negative/unsupported facts.
201    #[must_use]
202    pub fn timestamp(&self) -> &CheckedTimestamp<'_> {
203        &self.common
204    }
205    /// Actual original selected candidate/key from that check.
206    #[must_use]
207    pub fn selection(&self) -> &Selection<'_> {
208        &self.selection
209    }
210    /// Explicit caller-established historical answer.
211    #[must_use]
212    pub fn historical(&self) -> Decision {
213        self.history
214    }
215    /// Moves the SAME common result for a format adapter's actor construction.
216    /// No signature is rechecked and no retained evidence is copied. Selection
217    /// references are deliberately discarded by adapters with their own sealed
218    /// path model; callers must preserve this original index/source binding.
219    pub fn into_parts(self) -> (usize, CheckedTimestamp<'a>, Selection<'a>, Decision) {
220        (self.index, self.common, self.selection, self.history)
221    }
222}
223
224/// Complete, possibly negative sequence report, bound to this original record.
225pub struct Report<'a, 'document> {
226    record: &'a EvidenceRecord<'document>,
227    checks: OwnedVec<Checked<'a>>,
228    ordering: OwnedVec<OrderFinding>,
229    complete: bool,
230}
231/// One actual adjacent pair whose required chronological order is not proven.
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct OrderFinding {
234    /// Original earlier archive timestamp ordinal.
235    pub earlier: usize,
236    /// Original next archive timestamp ordinal.
237    pub later: usize,
238    /// Reversal, overlap or missing information, independently of trust facts.
239    pub cause: OrderCause,
240}
241/// Complete temporal comparison outcome; uncertainty is never positive order.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum OrderCause {
244    /// Authenticated generation facts contradict the declared chain order.
245    Reversed,
246    /// Accuracy intervals overlap without a same-TSA ordering assertion.
247    Overlap,
248    /// Supplied UTC facts cannot establish the exact generation order.
249    Utc(structured_pkix::temporal::Uncertainty),
250    /// At least one timestamp has no successful complete mathematical evidence.
251    UnprovenTimestamp,
252    /// A generation endpoint is uncertain (for example accuracy is unstated).
253    Bounds {
254        /// Earlier participant's unavailable bound.
255        earlier: Option<timestamp::BoundUncertainty>,
256        /// Later participant's unavailable bound.
257        later: Option<timestamp::BoundUncertainty>,
258    },
259}
260/// Closed complete record receipt. This incorporates the caller's historical
261/// decisions; it does not choose application trust or claim `CAdES` conformance.
262pub struct Verified<'report, 'record, 'document>(&'report Report<'record, 'document>);
263impl<'a, 'd> Report<'a, 'd> {
264    /// Moves complete results for a higher-level source-bound format adapter.
265    /// Order is the stored reverse execution order; each checked index remains
266    /// original. The boolean is this report's private computed closure result,
267    /// not a constructor for another common receipt.
268    pub fn into_parts(
269        self,
270    ) -> (
271        &'a EvidenceRecord<'d>,
272        OwnedVec<Checked<'a>>,
273        OwnedVec<OrderFinding>,
274        bool,
275    ) {
276        (self.record, self.checks, self.ordering, self.complete)
277    }
278    /// Results in ORIGINAL order, although historical checks run newest first.
279    pub fn checks(&self) -> impl DoubleEndedIterator<Item = &Checked<'a>> + ExactSizeIterator {
280        self.checks.iter().rev()
281    }
282    /// Every failed/uncertain adjacent comparison, with original ordinals.
283    #[must_use]
284    pub fn ordering(&self) -> &[OrderFinding] {
285        &self.ordering
286    }
287    /// Present only when every required check and chronology was established.
288    #[must_use]
289    pub fn verified(&self) -> Option<Verified<'_, 'a, 'd>> {
290        if self.complete {
291            Some(Verified(self))
292        } else {
293            None
294        }
295    }
296}
297impl<'a, 'd> Verified<'_, 'a, 'd> {
298    /// Original record to which all participants and renewal inputs were bound.
299    #[must_use]
300    pub fn record(&self) -> &'a EvidenceRecord<'d> {
301        self.0.record
302    }
303    /// Every actual checked timestamp, in original order.
304    pub fn checks(&self) -> impl DoubleEndedIterator<Item = &Checked<'a>> + ExactSizeIterator {
305        self.0.checks()
306    }
307}
308
309/// Original-bound schema preflight and exact chain-algorithm request mapping.
310/// A format-derived input can prepare its per-algorithm recipe once and reuse
311/// this SAME plan for execution, without reparsing timestamp information.
312pub struct InputPlan<'record, 'document> {
313    record: &'record EvidenceRecord<'document>,
314    chains: OwnedVec<Algorithm>,
315}
316impl<'r, 'd> InputPlan<'r, 'd> {
317    #[expect(
318        clippy::result_large_err,
319        reason = "a carried algorithm failure retains its original source map and cause inline, including when no diagnostic allocation can succeed"
320    )]
321    fn read(record: &'r EvidenceRecord<'d>, operation: &Reader<'_>) -> Result<Self, PlanError> {
322        let origin = record
323            .original
324            .reader_in(operation)
325            .map_err(PlanError::Processing)?;
326        let work = origin.execution_query(operation);
327        let mut declared = work.vector();
328        for identifier in &record.algorithms {
329            let value = known(identifier, &work).map_err(original_algorithm_error)?;
330            insert_algorithm(&mut declared, value, &work, identifier.offset())
331                .map_err(PlanError::Processing)?;
332        }
333        let mut chains = work.vector();
334        let mut chain_index = 0;
335        for (index, stamp) in record.timestamps.iter().enumerate() {
336            work.charge_work(1).map_err(|cause| {
337                PlanError::Processing(Error::resource(
338                    cause,
339                    FIELD,
340                    stamp.original.element.span().start,
341                ))
342            })?;
343            let TimeStamp::Rfc3161(token) = &stamp.token else {
344                return Err(PlanError::UnsupportedTimestamp { index });
345            };
346            if !token.faults().is_empty()
347                || !token.message().faults().is_empty()
348                || !token.signer().faults().is_empty()
349            {
350                return Err(PlanError::Nonconforming {
351                    index,
352                    span: token.message().span(),
353                });
354            }
355            let parent = stamp
356                .timestamp
357                .reader_in(operation)
358                .map_err(PlanError::Processing)?;
359            let timestamp_algorithm =
360                token_algorithm(token, &parent).map_err(PlanError::Information)?;
361            let value = if let Some(identifier) = stamp.algorithm {
362                known(&identifier, &work).map_err(original_algorithm_error)?
363            } else {
364                timestamp_algorithm
365            };
366            require_width(stamp, value.width(), &work).map_err(PlanError::Processing)?;
367            let chain = &record.chains[chain_index];
368            if index == chain.first {
369                chains.try_push(value).map_err(|cause| {
370                    PlanError::Processing(Error::resource(
371                        cause,
372                        FIELD,
373                        chain.original.element.span().start,
374                    ))
375                })?;
376            } else if chains[chain_index] != value {
377                return Err(PlanError::Processing(Error::schema(
378                    ErrorCode::AlgorithmProtection,
379                    FIELD,
380                    stamp.original.element.span().start,
381                )));
382            }
383            if index + 1 == chain.end {
384                chain_index += 1;
385            }
386        }
387        let mut used = work.vector();
388        for algorithm in &chains {
389            insert_algorithm(
390                &mut used,
391                *algorithm,
392                &work,
393                record.original.element.span().start,
394            )
395            .map_err(PlanError::Processing)?;
396        }
397        require_union(
398            &declared,
399            &used,
400            &work,
401            record.original.element.span().start,
402        )
403        .map_err(PlanError::Processing)?;
404        Ok(Self {
405            record,
406            chains: chains.into_owned(),
407        })
408    }
409    /// Original chain order, including repeated algorithms. The digest batch
410    /// retains this ordinal mapping when equal full identities are deduplicated.
411    #[must_use]
412    pub fn algorithms(&self) -> &[Algorithm] {
413        &self.chains
414    }
415    /// SAME original record for this immutable plan.
416    #[must_use]
417    pub fn record(&self) -> &'r EvidenceRecord<'d> {
418        self.record
419    }
420
421    /// Executes asynchronous original objects with the same admitted plan,
422    /// profile-before-input ordering, renewal recipes and historical closure.
423    /// All chain algorithms share one complete pass over each object. Batch
424    /// owners may inspect those actual outputs without acquiring bytes again.
425    #[expect(
426        clippy::too_many_arguments,
427        reason = "the asynchronous driver retains distinct original/provider/authority/UTC inputs and scoped format callbacks"
428    )]
429    #[expect(
430        clippy::result_large_err,
431        reason = "suspension preserves the same source-mapped and opaque failures as resident verification"
432    )]
433    pub async fn verify_async_with_profile<'a, P, PE, A, S, RE, HE, FE>(
434        &self,
435        provider: &'a P,
436        operation: &Reader<'a>,
437        authorities: &'a A,
438        utc: &LeapTable<'a>,
439        reference: Instant,
440        objects: S,
441        membership: Membership,
442        mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
443        mut historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
444        mut profile: impl for<'view, 'scope> FnMut(
445            usize,
446            timestamp::ProfileInput<'view, 'd, 'a, 'scope>,
447        ) -> Result<(), ProfileFailure<FE>>,
448    ) -> VerificationResult<'a, 'd, S::Error, PE, RE, A::Error, HE, FE>
449    where
450        'r: 'a,
451        'd: 'a,
452        P: for<'scope> SignatureProvider<'scope, Error = PE>,
453        A: Authorities<'d>,
454        S: super::AsyncObjects<'a, P>,
455    {
456        let record = self.record;
457        let plan = self;
458        engine::execute!(
459            suspending,
460            record,
461            plan,
462            provider,
463            operation,
464            authorities,
465            utc,
466            reference,
467            objects,
468            membership,
469            recognizes,
470            historical,
471            profile
472        )
473    }
474
475    fn token(&self, index: usize) -> (&'r ArchiveTimeStamp<'d>, &'r timestamp::Token<'d>) {
476        let record = self.record;
477        let stamp = &record.timestamps[index];
478        let TimeStamp::Rfc3161(token) = &stamp.token else {
479            unreachable!("the immutable input plan admitted every timestamp format")
480        };
481        (stamp, token)
482    }
483
484    /// Executes literal original objects with this SAME checked input plan.
485    /// Selection callbacks need not cause a second schema/information parse.
486    #[expect(
487        clippy::too_many_arguments,
488        reason = "the immutable plan keeps source, provider, authority and UTC contexts independent of the three differently scoped callbacks"
489    )]
490    #[expect(
491        clippy::result_large_err,
492        reason = "the literal-source entry preserves mapped timestamp and opaque callback failures without a diagnostic allocation"
493    )]
494    pub fn verify_with_profile<'a, P, PE, A, S, RE, HE, FE>(
495        &self,
496        provider: &'a P,
497        operation: &Reader<'a>,
498        authorities: &'a A,
499        utc: &LeapTable<'a>,
500        reference: Instant,
501        objects: &mut [S],
502        membership: Membership,
503        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
504        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
505        profile: impl for<'view, 'scope> FnMut(
506            usize,
507            timestamp::ProfileInput<'view, 'd, 'a, 'scope>,
508        ) -> Result<(), ProfileFailure<FE>>,
509    ) -> VerificationResult<'a, 'd, S::Error, PE, RE, A::Error, HE, FE>
510    where
511        'r: 'a,
512        'd: 'a,
513        P: for<'scope> SignatureProvider<'scope, Error = PE>,
514        A: Authorities<'d>,
515        S: Source,
516    {
517        self.record.verify_plan_sources(
518            self,
519            provider,
520            operation,
521            authorities,
522            utc,
523            reference,
524            super::objects::Streams(objects),
525            membership,
526            recognizes,
527            historical,
528            profile,
529        )
530    }
531
532    /// Executes per-algorithm format inputs with this already checked plan.
533    /// Every common/token/profile/history check still runs on the actual source.
534    #[expect(
535        clippy::too_many_arguments,
536        reason = "algorithm-dependent source execution has separate producing-reader, provider, authority and callback lifetimes"
537    )]
538    #[expect(
539        clippy::result_large_err,
540        reason = "request-source refusal keeps the actual mapped timestamp and caller causes inline on exhaustion"
541    )]
542    pub fn verify_requests_with_profile<'a, P, PE, A, S, RE, HE, FE>(
543        &self,
544        provider: &'a P,
545        operation: &Reader<'a>,
546        authorities: &'a A,
547        utc: &LeapTable<'a>,
548        reference: Instant,
549        objects: &mut [S],
550        membership: Membership,
551        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
552        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
553        profile: impl for<'view, 'scope> FnMut(
554            usize,
555            timestamp::ProfileInput<'view, 'd, 'a, 'scope>,
556        ) -> Result<(), ProfileFailure<FE>>,
557    ) -> VerificationResult<'a, 'd, S::Error, PE, RE, A::Error, HE, FE>
558    where
559        'r: 'a,
560        'd: 'a,
561        P: for<'scope> SignatureProvider<'scope, Error = PE>,
562        A: Authorities<'d>,
563        S: crate::digest::RequestSource<'a, P>,
564    {
565        self.record.verify_plan_sources(
566            self,
567            provider,
568            operation,
569            authorities,
570            utc,
571            reference,
572            super::objects::Requests(objects),
573            membership,
574            recognizes,
575            historical,
576            profile,
577        )
578    }
579}
580/// Resolves the actual carried imprint with the same source-mapped syntax
581/// preflight used by ERS. Unknown capability, malformed fields and resource
582/// refusal remain distinct; this does not establish timestamp mathematics.
583#[expect(
584    clippy::result_large_err,
585    reason = "the actual carried information/source-map failure must survive before provider or covered-source side effects"
586)]
587pub fn token_algorithm<'d>(
588    token: &timestamp::Token<'d>,
589    parent: &Reader<'d>,
590) -> Result<Algorithm, ContentError<InfoError<AlgorithmError>>> {
591    token.with_info(parent, |info, reader, _| {
592        if info.has_faults() {
593            return Err(AlgorithmError::Processing(Error::schema(
594                ErrorCode::MalformedValue,
595                FIELD,
596                info.span().start,
597            )));
598        }
599        let timestamp::HashAlgorithmReading::Identifier(identifier) = info.imprint().algorithm()
600        else {
601            return Err(AlgorithmError::Processing(Error::schema(
602                ErrorCode::AlgorithmSyntax,
603                FIELD,
604                info.imprint().span().start,
605            )));
606        };
607        known(identifier, &reader.execution())
608    })
609}
610fn known<W: Work>(
611    identifier: &structured_pkix::AlgorithmIdentifier<'_>,
612    work: &W,
613) -> Result<Algorithm, AlgorithmError> {
614    match digest::algorithm(identifier, work)
615        .map_err(|cause| AlgorithmError::Processing(Error::pkix(cause)))?
616    {
617        Resolution::Supported(value) => Ok(value),
618        Resolution::Unsupported => Err(AlgorithmError::Unsupported {
619            at: identifier.offset(),
620        }),
621        Resolution::Malformed => Err(AlgorithmError::Processing(Error::schema(
622            ErrorCode::AlgorithmSyntax,
623            FIELD,
624            identifier.offset(),
625        ))),
626    }
627}
628fn original_algorithm_error(cause: AlgorithmError) -> PlanError {
629    match cause {
630        AlgorithmError::Processing(cause) => PlanError::Processing(cause),
631        AlgorithmError::Unsupported { at } => PlanError::UnsupportedAlgorithm { at },
632    }
633}
634fn insert_algorithm<W: Work>(
635    set: &mut OwnedVec<Algorithm, W>,
636    value: Algorithm,
637    work: &W,
638    at: u64,
639) -> crate::Result<()> {
640    // The set contains DISTINCT variants of the shared finite algorithm enum.
641    // Duplicate advertisements cannot cause declared_count*timestamp_count
642    // comparisons. Every possible member comparison is admitted before access.
643    for member in &*set {
644        work.charge_work(1)
645            .map_err(|cause| Error::resource(cause, FIELD, at))?;
646        if *member == value {
647            return Ok(());
648        }
649    }
650    set.try_push(value)
651        .map_err(|cause| Error::resource(cause, FIELD, at))
652}
653
654fn require_width<W: Work>(
655    stamp: &ArchiveTimeStamp<'_>,
656    width: usize,
657    work: &W,
658) -> crate::Result<()> {
659    if let Some(tree) = &stamp.tree {
660        for level in tree {
661            for hash in &level.hashes {
662                work.charge_work(1)
663                    .map_err(|cause| Error::resource(cause, FIELD, hash.span().start))?;
664                if hash.len() != width {
665                    return Err(Error::schema(
666                        ErrorCode::DigestWidth,
667                        FIELD,
668                        hash.span().start,
669                    ));
670                }
671            }
672        }
673    }
674    Ok(())
675}
676
677fn require_union<W: Work>(
678    declared: &[Algorithm],
679    used: &[Algorithm],
680    work: &W,
681    at: u64,
682) -> crate::Result<()> {
683    // RFC4998 §3.1 requires a union, not an arbitrary advertisement. Both
684    // sets already contain distinct shared variants, bounding this pass.
685    for (left, right) in [(declared, used), (used, declared)] {
686        for requested in left {
687            let mut found = false;
688            for actual in right {
689                work.charge_work(1)
690                    .map_err(|cause| Error::resource(cause, FIELD, at))?;
691                if requested == actual {
692                    found = true;
693                    break;
694                }
695            }
696            if !found {
697                return Err(Error::schema(ErrorCode::AlgorithmProtection, FIELD, at));
698            }
699        }
700    }
701    Ok(())
702}
703
704impl<'document> EvidenceRecord<'document> {
705    /// Validates every timestamp format/tree width/digest advertisement and
706    /// same-chain algorithm before covered-object acquisition or provider work.
707    #[expect(
708        clippy::result_large_err,
709        reason = "preflight returns the carried-source diagnostic even when no diagnostic allocation can succeed"
710    )]
711    pub fn input_plan(
712        &self,
713        operation: &Reader<'_>,
714    ) -> Result<InputPlan<'_, 'document>, PlanError> {
715        InputPlan::read(self, operation)
716    }
717    /// Verifies every timestamp, both renewal relations, the digest union and
718    /// actual chronology. Objects are read ONCE with all chain algorithms in
719    /// `DigestBatch`'s shared fan-out. Reverse execution exposes the next
720    /// already checked participant to the caller's historical validator.
721    ///
722    /// Historical Valid must cover actual path/status, signed ESS restrictions,
723    /// signature/hash suitability at the next token and tree suitability through
724    /// the next chain's first token. The last participant is validated at the
725    /// explicit verification reference. Unprotected hints never satisfy this.
726    #[expect(
727        clippy::too_many_arguments,
728        reason = "standalone verification receives explicit source/group, provider, authority, UTC and historical/recognition boundaries"
729    )]
730    #[expect(
731        clippy::result_large_err,
732        reason = "standalone verification retains original failure categories without allocating error storage on resource refusal"
733    )]
734    pub fn verify<'a, P, PE, A, S, RE, HE>(
735        &'a self,
736        provider: &'a P,
737        operation: &Reader<'a>,
738        authorities: &'a A,
739        utc: &LeapTable<'a>,
740        reference: Instant,
741        objects: &mut [S],
742        membership: Membership,
743        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
744        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
745    ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE>
746    where
747        'document: 'a,
748        P: for<'scope> SignatureProvider<'scope, Error = PE>,
749        A: Authorities<'document>,
750        S: Source,
751    {
752        self.verify_with_profile(
753            provider,
754            operation,
755            authorities,
756            utc,
757            reference,
758            objects,
759            membership,
760            recognizes,
761            historical,
762            |_, _| Ok::<_, ProfileFailure<Infallible>>(()),
763        )
764    }
765
766    /// Same complete sequence check with one schema/profile callback per actual
767    /// timestamp. It sees the SAME ESS and `TSTInfo` readings before signature
768    /// or covered-input callbacks; no second token parse or provider is used.
769    #[expect(
770        clippy::too_many_arguments,
771        reason = "profiled standalone verification keeps original provider/key/UTC inputs and three independently scoped callbacks explicit"
772    )]
773    #[expect(
774        clippy::result_large_err,
775        reason = "the complete carried mapping and profile/caller causes survive on the nonallocating failure path"
776    )]
777    pub fn verify_with_profile<'a, P, PE, A, S, RE, HE, FE>(
778        &'a self,
779        provider: &'a P,
780        operation: &Reader<'a>,
781        authorities: &'a A,
782        utc: &LeapTable<'a>,
783        reference: Instant,
784        objects: &mut [S],
785        membership: Membership,
786        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
787        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
788        profile: impl for<'view, 'scope> FnMut(
789            usize,
790            timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
791        ) -> Result<(), ProfileFailure<FE>>,
792    ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
793    where
794        'document: 'a,
795        P: for<'scope> SignatureProvider<'scope, Error = PE>,
796        A: Authorities<'document>,
797        S: Source,
798    {
799        self.verify_sources(
800            provider,
801            operation,
802            authorities,
803            utc,
804            reference,
805            super::objects::Streams(objects),
806            membership,
807            recognizes,
808            historical,
809            profile,
810        )
811    }
812
813    /// Checks schema-defined algorithm-dependent object preimages (for example
814    /// the TS101733 `PoE` eight-hash recipe) through the SAME sequence kernel.
815    /// Request ordinals refer to original chains; repeated identities share
816    /// their completed output. No arbitrary finalized digest is accepted.
817    #[expect(
818        clippy::too_many_arguments,
819        reason = "schema-derived request sources retain their original authority, execution and callback boundaries without a second dispatcher"
820    )]
821    #[expect(
822        clippy::result_large_err,
823        reason = "format-source errors and mapped token/profile refusals remain distinct without allocating error storage"
824    )]
825    pub fn verify_requests_with_profile<'a, P, PE, A, S, RE, HE, FE>(
826        &'a self,
827        provider: &'a P,
828        operation: &Reader<'a>,
829        authorities: &'a A,
830        utc: &LeapTable<'a>,
831        reference: Instant,
832        objects: &mut [S],
833        membership: Membership,
834        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
835        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
836        profile: impl for<'view, 'scope> FnMut(
837            usize,
838            timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
839        ) -> Result<(), ProfileFailure<FE>>,
840    ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
841    where
842        'document: 'a,
843        P: for<'scope> SignatureProvider<'scope, Error = PE>,
844        A: Authorities<'document>,
845        S: crate::digest::RequestSource<'a, P>,
846    {
847        self.verify_sources(
848            provider,
849            operation,
850            authorities,
851            utc,
852            reference,
853            super::objects::Requests(objects),
854            membership,
855            recognizes,
856            historical,
857            profile,
858        )
859    }
860
861    #[expect(
862        clippy::too_many_arguments,
863        reason = "one dispatch forwards the same literal/request-source verification contexts and separately scoped callbacks"
864    )]
865    #[expect(
866        clippy::result_large_err,
867        reason = "forwarding cannot replace preflight, source-map or opaque caller errors with an allocating wrapper"
868    )]
869    fn verify_sources<'a, P, PE, A, S, RE, HE, FE>(
870        &'a self,
871        provider: &'a P,
872        operation: &Reader<'a>,
873        authorities: &'a A,
874        utc: &LeapTable<'a>,
875        reference: Instant,
876        objects: S,
877        membership: Membership,
878        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
879        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
880        profile: impl for<'view, 'scope> FnMut(
881            usize,
882            timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
883        ) -> Result<(), ProfileFailure<FE>>,
884    ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
885    where
886        'document: 'a,
887        P: for<'scope> SignatureProvider<'scope, Error = PE>,
888        A: Authorities<'document>,
889        S: super::objects::Sources<'a, P>,
890    {
891        let plan = InputPlan::read(self, operation).map_err(VerificationError::Plan)?;
892        self.verify_plan_sources(
893            &plan,
894            provider,
895            operation,
896            authorities,
897            utc,
898            reference,
899            objects,
900            membership,
901            recognizes,
902            historical,
903            profile,
904        )
905    }
906
907    #[expect(
908        clippy::too_many_arguments,
909        reason = "the single execution kernel binds the original plan and all already-admitted source/provider/callback contexts"
910    )]
911    #[expect(
912        clippy::result_large_err,
913        reason = "token, renewal, selector and source-map refusals are returned unchanged when allocation is unavailable"
914    )]
915    fn verify_plan_sources<'a, P, PE, A, S, RE, HE, FE>(
916        &'a self,
917        plan: &InputPlan<'a, 'document>,
918        provider: &'a P,
919        operation: &Reader<'a>,
920        authorities: &'a A,
921        utc: &LeapTable<'a>,
922        reference: Instant,
923        objects: S,
924        membership: Membership,
925        mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
926        mut historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
927        mut profile: impl for<'view, 'scope> FnMut(
928            usize,
929            timestamp::ProfileInput<'view, 'document, 'a, 'scope>,
930        ) -> Result<(), ProfileFailure<FE>>,
931    ) -> VerificationResult<'a, 'document, S::Error, PE, RE, A::Error, HE, FE>
932    where
933        'document: 'a,
934        P: for<'scope> SignatureProvider<'scope, Error = PE>,
935        A: Authorities<'document>,
936        S: super::objects::Sources<'a, P>,
937    {
938        engine::execute!(
939            resident,
940            self,
941            plan,
942            provider,
943            operation,
944            authorities,
945            utc,
946            reference,
947            objects,
948            membership,
949            recognizes,
950            historical,
951            profile
952        )
953    }
954}
955
956enum GroupError {
957    Processing(Error),
958    Membership(usize),
959}
960fn admit_membership<W: Work>(
961    record: &EvidenceRecord<'_>,
962    objects: usize,
963    membership: Membership,
964    work: &W,
965) -> Result<(), GroupError> {
966    let at = record.original.element.span().start;
967    if objects == 0 || (membership == Membership::Object && objects != 1) {
968        return Err(GroupError::Processing(Error::schema(
969            ErrorCode::MissingField,
970            FIELD,
971            at,
972        )));
973    }
974    for chain in &record.chains {
975        work.charge_work(1)
976            .map_err(|cause| GroupError::Processing(Error::resource(cause, FIELD, at)))?;
977        if !super::input::membership_shape(&record.timestamps[chain.first], objects, membership) {
978            return Err(GroupError::Membership(chain.first));
979        }
980    }
981    Ok(())
982}
983type Prefixes<'r, 'd> = OwnedVec<Option<PriorSequence<'r, 'd>>>;
984fn prior_views<'r, 'd, W: Work>(
985    record: &'r EvidenceRecord<'d>,
986    projected: &'r [crate::encoding::Projection<'d>],
987    operation: &Reader<'_>,
988    work: &W,
989) -> crate::Result<Prefixes<'r, 'd>> {
990    let resource = |cause| Error::resource(cause, FIELD, record.original.element.span().start);
991    let mut prefixes = work.vector();
992    prefixes
993        .try_reserve_exact(record.chains.len())
994        .map_err(resource)?;
995    prefixes.try_push(None).map_err(resource)?;
996    for index in 1..record.chains.len() {
997        prefixes
998            .try_push(Some(PriorSequence::borrowed(
999                record, index, projected, operation,
1000            )?))
1001            .map_err(resource)?;
1002    }
1003    Ok(prefixes.into_owned())
1004}
1005fn order_findings<W: Work>(
1006    checks: &[Checked<'_>],
1007    utc: &LeapTable<'_>,
1008    operation: &Reader<'_>,
1009    work: &W,
1010    at: u64,
1011) -> crate::Result<OwnedVec<OrderFinding>> {
1012    let resource = |cause| Error::resource(cause, FIELD, at);
1013    let mut findings = work.vector();
1014    for pair in checks.windows(2) {
1015        work.charge_work(1).map_err(resource)?;
1016        if let Some(cause) = order(&pair[1], &pair[0], utc, operation)? {
1017            findings
1018                .try_push(OrderFinding {
1019                    earlier: pair[1].index,
1020                    later: pair[0].index,
1021                    cause,
1022                })
1023                .map_err(resource)?;
1024        }
1025    }
1026    Ok(findings.into_owned())
1027}
1028
1029fn history_input<'v, 'a>(
1030    record: &EvidenceRecord<'_>,
1031    chain: usize,
1032    index: usize,
1033    current: &'v CheckedTimestamp<'a>,
1034    selection: &'v Selection<'a>,
1035    checks: &'v [Checked<'a>],
1036    reference: Instant,
1037) -> Historical<'v, 'a> {
1038    let following = checks.last().and_then(|next| next.common.evidence());
1039    let following_chain = record
1040        .chains
1041        .get(chain + 1)
1042        .and_then(|next| checks.get(record.timestamps.len() - 1 - next.first))
1043        .and_then(|next| next.common.evidence());
1044    Historical {
1045        index,
1046        current,
1047        selection,
1048        following,
1049        following_chain,
1050        verification_time: reference,
1051    }
1052}
1053
1054fn order(
1055    earlier: &Checked<'_>,
1056    later: &Checked<'_>,
1057    utc: &LeapTable<'_>,
1058    operation: &Reader<'_>,
1059) -> crate::Result<Option<OrderCause>> {
1060    let work = operation.execution();
1061    let (Some(a), Some(b)) = (earlier.common.evidence(), later.common.evidence()) else {
1062        return Ok(Some(OrderCause::UnprovenTimestamp));
1063    };
1064    order_evidence(
1065        a,
1066        earlier.selection.certificate,
1067        b,
1068        later.selection.certificate,
1069        utc,
1070        &work,
1071    )
1072}
1073
1074/// Orders two actual authenticated TSA facts using RFC3161 §2.4.2's SAME-TSA
1075/// ordering rule or complete effective-accuracy intervals. This is shared by
1076/// in-record ERS renewal and `CAdES` cross-proof preservation closure. A readable
1077/// unsigned poeDate is never substituted for either authenticated generation.
1078pub fn order_evidence<W: Work>(
1079    a: &Evidence,
1080    left: &Certificate<'_>,
1081    b: &Evidence,
1082    right: &Certificate<'_>,
1083    utc: &LeapTable<'_>,
1084    work: &W,
1085) -> crate::Result<Option<OrderCause>> {
1086    let resource = |cause| Error::resource(cause, FIELD, a.source().carrier().start);
1087    if a.facts().ordering() && b.facts().ordering() {
1088        let first = left.execution_in(work);
1089        let second = right.execution_in(work);
1090        first.charge_work(1).map_err(resource)?;
1091        second.charge_work(1).map_err(resource)?;
1092        let same = if core::ptr::eq(left, right) {
1093            true
1094        } else if left.as_bytes().len() != right.as_bytes().len() {
1095            false
1096        } else {
1097            first
1098                .charge_work(left.as_bytes().len() as u64)
1099                .map_err(resource)?;
1100            second
1101                .charge_work(right.as_bytes().len() as u64)
1102                .map_err(resource)?;
1103            left.as_bytes() == right.as_bytes()
1104        };
1105        if same {
1106            // RFC3161 §2.4.2: ordering=true for the SAME authenticated TSA
1107            // permits genTime ordering irrespective of stated accuracy. Exact
1108            // retained decimal tails prevent subnanosecond labels collapsing.
1109            // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
1110            return match a.facts().compare_generation(b.facts(), utc, work) {
1111                Ok(core::cmp::Ordering::Greater) => Ok(Some(OrderCause::Reversed)),
1112                Ok(_) => Ok(None),
1113                Err(timestamp::GenerationOrderError::Resource(cause)) => Err(resource(cause)),
1114                Err(timestamp::GenerationOrderError::Uncertain(cause)) => {
1115                    Ok(Some(OrderCause::Utc(cause)))
1116                }
1117            };
1118        }
1119    }
1120    let a = match timestamp::interval_in(
1121        a.facts().gen_time(),
1122        a.facts()
1123            .effective_accuracy()
1124            .map(timestamp::EffectiveAccuracy::accuracy),
1125        utc,
1126        work,
1127    ) {
1128        Ok(value) => Ok(value),
1129        Err(timestamp::IntervalError::Resource(cause)) => return Err(resource(cause)),
1130        Err(timestamp::IntervalError::Uncertain(cause)) => Err(cause),
1131    };
1132    let b = match timestamp::interval_in(
1133        b.facts().gen_time(),
1134        b.facts()
1135            .effective_accuracy()
1136            .map(timestamp::EffectiveAccuracy::accuracy),
1137        utc,
1138        work,
1139    ) {
1140        Ok(value) => Ok(value),
1141        Err(timestamp::IntervalError::Resource(cause)) => return Err(resource(cause)),
1142        Err(timestamp::IntervalError::Uncertain(cause)) => Err(cause),
1143    };
1144    match (a, b) {
1145        (Ok(a), Ok(b)) if a.latest <= b.earliest => Ok(None),
1146        (Ok(a), Ok(b)) if a.earliest > b.latest => Ok(Some(OrderCause::Reversed)),
1147        (Ok(_), Ok(_)) => Ok(Some(OrderCause::Overlap)),
1148        (a, b) => Ok(Some(OrderCause::Bounds {
1149            earlier: a.err(),
1150            later: b.err(),
1151        })),
1152    }
1153}