Skip to main content

structured_cms/evidence/
verify.rs

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