Skip to main content

structured_cades/timestamp/
historical.rs

1//! Historical archive inputs, distinct from `ATSv3` and modern baseline status.
2//!
3//! RFC3126 §4.4.1, TS101733 V1.5.1 §6.4.1, and RFC5126 §6.4.1 select
4//! DIFFERENT octets. The OID shared by the first two does not select an
5//! edition. TS101733 V2.2.1 Annex K note 3 requires handling both unsigned
6//! attribute framing interpretations for archiveTimestampV2.
7//! <https://www.rfc-editor.org/rfc/rfc3126#section-4.4.1>
8//! <https://www.rfc-editor.org/rfc/rfc5126#section-6.4.1>
9//! <https://www.etsi.org/deliver/etsi_ts/101700_101799/101733/02.02.01_60/ts_101733v020201p.pdf>
10
11use super::{ArchiveChecked, Checked};
12use crate::{
13    Cades, Error, ISSUER,
14    prepare::{Preparation, encoding::SignerView},
15    preservation::Kind,
16    source::{DocumentId, Node},
17};
18use core::cell::Cell;
19use structured_asn1::{
20    Oid, Reader,
21    length::DerLength,
22    memory::{Memory, OwnedMap, OwnedVec, Work},
23};
24use structured_cms::{
25    Octets,
26    stream::Source,
27    timestamp::{
28        self as tsp, ComputedInput, ImprintAgreement, ImprintContext, ImprintProof, InputFailure,
29    },
30};
31use structured_ltv::{
32    material::Coverage,
33    source::{Scope, Source as _},
34    timestamp::{Purpose, Verified},
35};
36use structured_pkix::algorithm::digest::AsnDigest;
37
38const FIELD: &str = "cades.historical";
39
40mod selection;
41mod snapshot;
42
43/// Caller-established historical edition. Content never chooses a permissive
44/// edition; both `ArchiveV1` editions deliberately have the same attribute OID.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Edition {
47    /// Logical content, signed-attribute contents, signature contents and the
48    /// listed unsigned attribute contents, in RFC3126 §4.4.1 order.
49    Rfc3126,
50    /// Whole `EncapsulatedContentInfo`, material fields, original signer fields
51    /// and unsigned attributes. No detached-byte addition is specified.
52    Ts101733V151,
53    /// RFC5126 input, including actual detached bytes when eContent is absent,
54    /// with BOTH Annex K unsigned-attribute framing alternatives.
55    Rfc5126,
56}
57impl Edition {
58    fn kind(self) -> Kind {
59        match self {
60            Self::Rfc3126 | Self::Ts101733V151 => Kind::ArchiveV1,
61            Self::Rfc5126 => Kind::ArchiveV2,
62        }
63    }
64}
65
66/// Honest description of the unsigned field used by this input. A canonical
67/// projection cannot recover a lost historical BER length/EOC spelling.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Framing {
70    /// Exact previous field bytes, including any indefinite wrapper/EOC.
71    OriginalSnapshot,
72    /// Newly generated minimal definite framing around original members.
73    CanonicalDefiniteProjection,
74}
75
76/// Refusal before provider/source side effects; no implicit missing-input or
77/// successful missing-preservation fallback is provided.
78#[derive(Debug)]
79pub enum SelectionError {
80    /// Failure in the current source/operation.
81    Processing(Error),
82    /// Failure while consulting the explicitly supplied earlier source.
83    PriorProcessing(Error),
84    /// Exact common coverage/source refusal while admitting a later witness.
85    Coverage(structured_ltv::material::CoverageError<Node>),
86    /// Attribute OID and explicitly selected historical edition disagree.
87    WrongEdition,
88    /// Wrong source/owner/placement, repeated selection, or unreadable value.
89    WrongOccurrence,
90    /// RFC5126 requires actual detached input, but no source was supplied.
91    MissingDetached,
92    /// Original signed fields/material/previous unsigned attributes changed.
93    PriorNotPreserved,
94    /// The proposed previous input contains its own target token.
95    SelfIncluded,
96    /// Exclusion lacks a validated later proof covering this earlier token.
97    UnprovenLater {
98        /// Original excluded archive attribute, without invented coordinates.
99        property: Node,
100    },
101    /// A separate preservation mechanism has no usable qualified processor.
102    Unavailable {
103        /// Original unsupported preservation property.
104        property: Node,
105    },
106    /// Exact range/framing lengths cannot be represented.
107    LengthOverflow,
108}
109impl From<Error> for SelectionError {
110    fn from(cause: Error) -> Self {
111        Self::Processing(cause)
112    }
113}
114
115/// A validated later proof which actually covers the earlier token. A claimed
116/// genTime or source order cannot manufacture this witness. The constructor
117/// borrows the sealed common actor and its exact format-produced coverage.
118pub struct Later<'view, 'model, 'document> {
119    evidence: &'view Verified<Node, DocumentId<'model, 'document>, (), Cades>,
120    coverage: &'view Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
121}
122impl<'v, 'm, 'd> Later<'v, 'm, 'd> {
123    /// Borrows an actual successful `ATSv3` receipt and its input coverage.
124    pub fn modern(checked: &'v ArchiveChecked<'m, 'd, '_>) -> Option<Self> {
125        Some(Self {
126            evidence: checked.timestamp().actor()?.verified(),
127            coverage: checked.coverage(),
128        })
129    }
130    /// Borrows an actual successful historical archive receipt and coverage.
131    pub fn historical(checked: &'v HistoricalChecked<'m, 'd, '_>) -> Option<Self> {
132        Some(Self {
133            evidence: checked.timestamp.actor()?.verified(),
134            coverage: &checked.coverage,
135        })
136    }
137    /// A fully checked ERS/PoE renewal with its exact format-derived coverage.
138    /// Whole-record completion is required by `RecordArchive`'s constructor.
139    #[must_use]
140    pub fn record(checked: &'v crate::evidence::RecordArchive<'_, '_, 'm, 'd, '_>) -> Option<Self> {
141        Some(Self {
142            evidence: checked.timestamp().actor()?.verified(),
143            coverage: checked.coverage(),
144        })
145    }
146    pub(crate) fn permits(
147        &self,
148        prepared: &Preparation<'m, 'd>,
149        later: &crate::prepare::Timestamp<'d>,
150        earlier: &crate::prepare::Timestamp<'d>,
151        operation: &Reader<'d>,
152    ) -> Result<bool, SelectionError> {
153        operation
154            .charge(1)
155            .map_err(|cause| Error::encoding(cause, FIELD))?;
156        let Some(value) = later.value() else {
157            return Ok(false);
158        };
159        if self.evidence.token().node != Node::of(value.span()) {
160            return Ok(false);
161        }
162        let source = prepared.document();
163        let work = operation.execution();
164        let query = Scope::new(source, source.snapshot(), &work);
165        let node = earlier
166            .value()
167            .map_or(earlier.property(), |value| Node::of(value.span()));
168        let selected = self
169            .coverage
170            .covers(self.evidence, node, &query)
171            .map_err(SelectionError::Coverage)?;
172        if selected {
173            return Ok(true);
174        }
175        self.coverage
176            .covers(self.evidence, earlier.property(), &query)
177            .map_err(SelectionError::Coverage)
178    }
179}
180
181/// Exact earlier source context, retained by borrowing the actual preparation
182/// and producing reader. Capturing performs no reparse, re-encoding or payload
183/// copy. Binding this to a later message is checked before starting a digest.
184pub struct OriginalSnapshot<'view, 'model, 'document> {
185    prepared: &'view Preparation<'model, 'document>,
186    owner: Node,
187    reader: &'view Reader<'document>,
188    signer: SignerView<'view, 'document>,
189}
190impl<'v, 'm, 'd> OriginalSnapshot<'v, 'm, 'd> {
191    /// Selects an actual root/counter in its own original parse session.
192    pub fn capture(
193        prepared: &'v Preparation<'m, 'd>,
194        owner: Node,
195        reader: &'v Reader<'d>,
196    ) -> Result<Self, SelectionError> {
197        let signer = prepared.signer_encoding(owner, reader)?;
198        Ok(Self {
199            prepared,
200            owner,
201            reader,
202            signer,
203        })
204    }
205}
206
207#[derive(Clone, Copy)]
208struct Header {
209    bytes: [u8; 10],
210    length: u8,
211}
212impl Header {
213    fn new(tag: u8, length: u64) -> Self {
214        let length = DerLength::new(length);
215        let mut bytes = [0; 10];
216        bytes[0] = tag;
217        bytes[1..=length.as_bytes().len()].copy_from_slice(length.as_bytes());
218        Self {
219            bytes,
220            length: 1 + u8::try_from(length.as_bytes().len())
221                .expect("DER length uses at most nine octets"),
222        }
223    }
224    fn bytes(&self) -> &[u8] {
225        &self.bytes[..usize::from(self.length)]
226    }
227}
228enum Part<'a> {
229    Current(&'a [u8]),
230    Prior(&'a [u8]),
231    Framing(Header),
232}
233impl Part<'_> {
234    fn length(&self) -> usize {
235        match self {
236            Self::Current(bytes) | Self::Prior(bytes) => bytes.len(),
237            Self::Framing(header) => header.bytes().len(),
238        }
239    }
240}
241enum Payload<'s, 'd, S> {
242    Absent,
243    Carried(&'s Octets<'d>),
244    Detached(&'s mut S),
245}
246
247struct Carried<'s, 'd, E> {
248    source: &'s Octets<'d>,
249    error: core::marker::PhantomData<E>,
250}
251impl<E> Source for Carried<'_, '_, E> {
252    type Error = StreamError<E>;
253    fn stream<W: Work>(
254        &mut self,
255        receiver: &mut impl structured_cms::stream::Receiver,
256        work: &W,
257    ) -> Result<(), Self::Error> {
258        self.source
259            .stream(receiver, work)
260            .map_err(StreamError::Carried)
261    }
262}
263struct Lift<'a, S>(&'a mut S);
264impl<S: Source> Source for Lift<'_, S> {
265    type Error = StreamError<S::Error>;
266    fn stream<W: Work>(
267        &mut self,
268        receiver: &mut impl structured_cms::stream::Receiver,
269        work: &W,
270    ) -> Result<(), Self::Error> {
271        self.0.stream(receiver, work).map_err(StreamError::Source)
272    }
273}
274
275#[derive(PartialEq, Eq, Hash)]
276struct ArchiveValue<'a> {
277    generation: u8,
278    bytes: &'a [u8],
279}
280impl structured_asn1::memory::HashWork for ArchiveValue<'_> {
281    fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
282        (self.bytes.len() as u64)
283            .checked_add(2)
284            .ok_or(structured_asn1::memory::MemoryError::WorkExceeded)
285    }
286}
287
288/// Prepared range recipe. Only descriptors/framing are owned; document bytes
289/// remain original borrowed ranges. Detached acquisition happens once, after
290/// all variant states have been admitted, with no complete-document buffer.
291pub struct HistoricalInput<'input, 'model, 'document, S> {
292    prepared: &'input Preparation<'model, 'document>,
293    operation: &'input Reader<'document>,
294    prior: Option<&'input Reader<'input>>,
295    ordinal: usize,
296    edition: Edition,
297    framing: Framing,
298    parts: OwnedVec<Part<'input>>,
299    payload_at: usize,
300    unsigned_at: usize,
301    unsigned_header: Option<Header>,
302    original_unsigned: Option<&'input [u8]>,
303    payload: Payload<'input, 'document, S>,
304    covered: Option<OwnedMap<Node, bool>>,
305    matched: Cell<Option<Variant>>,
306}
307
308/// Actual matching interpretation, never guessed from the token's date/OID.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum Variant {
311    /// RFC3126's prescribed sequence of logical field/attribute contents.
312    ListedContents,
313    /// Complete original/projected unsigned field, including its framing.
314    UnsignedField,
315    /// Unsigned-field contents with its outer wrapper/EOC excluded.
316    UnsignedContents,
317}
318
319/// Acquisition/format refusal during a computed imprint. Processing retains
320/// its honest original cause; it is not a semantic digest disagreement.
321#[derive(Debug)]
322pub enum StreamError<E> {
323    /// Opaque failure acquiring the actual detached bytes.
324    Source(E),
325    /// Original carried segment traversal or retained receiver refusal.
326    Carried(structured_cms::octets::SourceError),
327    /// Original range/metadata processing failure.
328    Processing(Error),
329}
330
331impl<'s, 'm, 'd, S> HistoricalInput<'s, 'm, 'd, S> {
332    /// Uses a genuine earlier unsigned field, including its original BER
333    /// wrapper and EOC. Signed fields, global material fields and the complete
334    /// previous attribute multiset are bound to this current owner FIRST.
335    /// Neither a guessed date nor a synthetic previous encoding substitutes
336    /// for the supplied source. All older archive values must still be present.
337    pub fn from_snapshot<'old_model, 'old_document: 's>(
338        prepared: &'s Preparation<'m, 'd>,
339        ordinal: usize,
340        edition: Edition,
341        snapshot: &'s OriginalSnapshot<'_, 'old_model, 'old_document>,
342        later: &[Later<'_, 'm, 'd>],
343        operation: &'s Reader<'d>,
344        detached: Option<&'s mut S>,
345    ) -> Result<Self, SelectionError> {
346        prepared
347            .document()
348            .message()
349            .require_session(operation, FIELD)
350            .map_err(Error::Cms)?;
351        snapshot
352            .prepared
353            .document()
354            .message()
355            .require_session(snapshot.reader, FIELD)
356            .map_err(|cause| SelectionError::PriorProcessing(Error::Cms(cause)))?;
357        let target = prepared
358            .timestamps()
359            .get(ordinal)
360            .ok_or(SelectionError::WrongOccurrence)?;
361        let view = prepared.signer_encoding(target.owner(), operation)?;
362        snapshot.boundaries(prepared, view, operation)?;
363        snapshot.parent(prepared, target.owner(), operation)?;
364        let mut attributes = snapshot.attributes(view, operation)?;
365        let previous = snapshot.previous(prepared, target.owner(), ordinal, operation)?;
366        Self::project(
367            prepared, ordinal, edition, &previous, later, operation, detached,
368        )?
369        .restore(snapshot, view, &mut attributes)
370    }
371    /// Explicit final-message projection. `previous` contains exact prepared
372    /// archive occurrence ordinals, not claimed times. Every excluded later
373    /// archive must supply a validated coverage witness over this target.
374    /// This spells NEW definite headers and never claims to recover lost BER.
375    pub fn project(
376        prepared: &'s Preparation<'m, 'd>,
377        ordinal: usize,
378        edition: Edition,
379        previous: &[usize],
380        later: &[Later<'_, 'm, 'd>],
381        operation: &'s Reader<'d>,
382        detached: Option<&'s mut S>,
383    ) -> Result<Self, SelectionError> {
384        prepared
385            .document()
386            .message()
387            .require_session(operation, FIELD)
388            .map_err(Error::Cms)?;
389        operation
390            .charge(1)
391            .map_err(|cause| Error::encoding(cause, FIELD))?;
392        let target = prepared
393            .timestamps()
394            .get(ordinal)
395            .ok_or(SelectionError::WrongOccurrence)?;
396        if target.preservation_kind() != Some(edition.kind()) {
397            return Err(SelectionError::WrongEdition);
398        }
399        if target.value().is_none() {
400            return Err(SelectionError::WrongOccurrence);
401        }
402        let view = prepared.signer_encoding(target.owner(), operation)?;
403        let work = operation.execution();
404        let resource = |cause| {
405            SelectionError::Processing(Error::resource(
406                cause,
407                FIELD,
408                target.property().span().start,
409            ))
410        };
411        let mut placement = false;
412        for index in 0..view.attributes() {
413            work.charge_work(1).map_err(resource)?;
414            if Node::of(
415                view.attribute(index)
416                    .ok_or(SelectionError::WrongOccurrence)?
417                    .span,
418            ) == target.property()
419            {
420                placement = true;
421                break;
422            }
423        }
424        if !placement {
425            return Err(SelectionError::WrongOccurrence);
426        }
427        let chosen = selection::choose(prepared, ordinal, edition, previous, later, operation)?;
428        let content = prepared.document().message().content().content();
429        let payload = match (edition, content, detached) {
430            (Edition::Rfc3126, Some(source), _) => Payload::Carried(source),
431            (Edition::Rfc5126, None, Some(source)) => Payload::Detached(source),
432            (Edition::Rfc5126, None, None) => return Err(SelectionError::MissingDetached),
433            _ => Payload::Absent,
434        };
435        let mut parts = work.vector();
436        let mut covered = work.map();
437        let payload_at = selection::Output::new(&mut parts, &mut covered, &work, target.property())
438            .prefix(prepared, view, target.owner(), edition, operation)?;
439        let unsigned_at = parts.len();
440        {
441            let mut output =
442                selection::Output::new(&mut parts, &mut covered, &work, target.property());
443            output.unsigned(prepared, view, edition, &chosen)?;
444            output.context(prepared, view, target.owner(), edition)?;
445        }
446        let mut unsigned_length = 0u64;
447        for part in &parts[unsigned_at..] {
448            work.charge_work(1).map_err(resource)?;
449            unsigned_length = unsigned_length
450                .checked_add(part.length() as u64)
451                .ok_or(SelectionError::LengthOverflow)?;
452        }
453        Ok(Self {
454            prepared,
455            operation,
456            prior: None,
457            ordinal,
458            edition,
459            framing: Framing::CanonicalDefiniteProjection,
460            parts: parts.into_owned(),
461            payload_at,
462            unsigned_at,
463            unsigned_header: (unsigned_length != 0).then(|| Header::new(0xa1, unsigned_length)),
464            original_unsigned: None,
465            payload,
466            covered: Some(covered.into_owned()),
467            matched: Cell::new(None),
468        })
469    }
470    /// Whether this input retained original or generated unsigned framing.
471    pub fn framing(&self) -> Framing {
472        self.framing
473    }
474    /// Last fully completed matching variant, or no matching computation.
475    pub fn matched_variant(&self) -> Option<Variant> {
476        self.matched.get()
477    }
478}
479
480fn archive_kind(kind: Oid<'_>) -> Option<Kind> {
481    match Kind::from_oid(kind) {
482        Some(value) if value.is_archive() => Some(value),
483        _ => None,
484    }
485}
486fn category(kind: Oid<'_>) -> Option<usize> {
487    for (index, last) in [14, 21, 22, 23, 24, 25, 26, 27].into_iter().enumerate() {
488        if kind == Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, last]) {
489            return Some(index);
490        }
491    }
492    None
493}
494
495fn covered_container<W: Work>(
496    view: SignerView<'_, '_>,
497    node: Node,
498    selected: &OwnedMap<Node, bool, W>,
499    work: &impl Work,
500) -> Result<bool, structured_asn1::memory::MemoryError> {
501    // Parsed top-level attributes are disjoint and in original physical order.
502    // Binary search avoids a counter-by-attribute cross-product. Selection is
503    // consulted ONLY after whole-range containment, never by a prefix overlap.
504    let span = node.span();
505    let mut from = 0;
506    let mut to = view.attributes();
507    while from < to {
508        work.charge_work(1)?;
509        let middle = from + (to - from) / 2;
510        let attribute = view.attribute(middle).expect("bounded original catalogue");
511        if attribute.span.start <= span.start {
512            from = middle + 1;
513        } else {
514            to = middle;
515        }
516    }
517    if from == 0 {
518        return Ok(false);
519    }
520    work.charge_work(1)?;
521    let attribute = view
522        .attribute(from - 1)
523        .expect("preceding original attribute");
524    if span.end() > attribute.span.end() {
525        return Ok(false);
526    }
527    Ok(selected.get(&Node::of(attribute.span), work)? == Some(&true))
528}
529
530impl<P, PE, S: Source> ComputedInput<P, PE> for HistoricalInput<'_, '_, '_, S> {
531    type Error = StreamError<S::Error>;
532    fn compute<'context, 'reader, 'document>(
533        &mut self,
534        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
535    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
536    where
537        P: AsnDigest<'document, Error = PE>,
538    {
539        self.matched.set(None);
540        let mut first = context.start()?;
541        let mut second = if self.edition == Edition::Rfc5126 {
542            Some(context.start()?)
543        } else {
544            None
545        };
546        for part in &self.parts[..self.payload_at] {
547            emit(part, &mut first, self.operation, self.prior)?;
548            if let Some(second) = &mut second {
549                emit(part, second, self.operation, self.prior)?;
550            }
551        }
552        match &mut self.payload {
553            Payload::Absent => {}
554            Payload::Carried(source) => {
555                let mut source = Carried::<S::Error> {
556                    source,
557                    error: core::marker::PhantomData,
558                };
559                if let Some(second) = &mut second {
560                    context.stream_from(&mut source, self.operation, &mut [&mut first, second])?;
561                } else {
562                    context.stream_from(&mut source, self.operation, &mut [&mut first])?;
563                }
564            }
565            Payload::Detached(source) => {
566                if let Some(second) = &mut second {
567                    context.stream_from(
568                        &mut Lift(*source),
569                        self.operation,
570                        &mut [&mut first, second],
571                    )?;
572                } else {
573                    context.stream_from(&mut Lift(*source), self.operation, &mut [&mut first])?;
574                }
575            }
576        }
577        for part in &self.parts[self.payload_at..self.unsigned_at] {
578            emit(part, &mut first, self.operation, self.prior)?;
579            if let Some(second) = &mut second {
580                emit(part, second, self.operation, self.prior)?;
581            }
582        }
583        if self.edition != Edition::Rfc3126 {
584            if let Some(bytes) = self.original_unsigned {
585                first.write_from(
586                    bytes,
587                    self.prior
588                        .expect("original unsigned belongs to captured source"),
589                )?;
590            } else if let Some(header) = &self.unsigned_header {
591                first.write(header.bytes())?;
592            }
593        }
594        for part in &self.parts[self.unsigned_at..] {
595            if self.original_unsigned.is_some() && self.edition != Edition::Rfc3126 {
596                if let Some(second) = &mut second {
597                    emit(part, second, self.operation, self.prior)?;
598                }
599            } else {
600                emit(part, &mut first, self.operation, self.prior)?;
601                if let Some(second) = &mut second {
602                    emit(part, second, self.operation, self.prior)?;
603                }
604            }
605        }
606        let first = first.finish()?;
607        let second = match second {
608            Some(state) => Some(state.finish()?),
609            None => None,
610        };
611        let a = context.compare(&first)?;
612        let b = match &second {
613            Some(digest) => Some(context.compare(digest)?),
614            None => None,
615        };
616        if a.agreement() == ImprintAgreement::Matches {
617            self.matched.set(Some(if self.edition == Edition::Rfc3126 {
618                Variant::ListedContents
619            } else {
620                Variant::UnsignedField
621            }));
622            Ok(a)
623        } else if let Some(b) = b {
624            if b.agreement() == ImprintAgreement::Matches {
625                self.matched.set(Some(Variant::UnsignedContents));
626            }
627            Ok(b)
628        } else {
629            Ok(a)
630        }
631    }
632}
633fn emit<'c, 'r, 'd, P: AsnDigest<'d>, E>(
634    part: &Part<'_>,
635    state: &mut tsp::ImprintState<'c, 'r, 'd, P, E>,
636    operation: &Reader<'_>,
637    prior: Option<&Reader<'_>>,
638) -> Result<(), structured_cms::stream::Refused> {
639    match part {
640        Part::Current(bytes) => state.write_from(bytes, operation),
641        Part::Prior(bytes) => {
642            state.write_from(bytes, prior.expect("prior descriptor has its source"))
643        }
644        Part::Framing(header) => state.write(header.bytes()),
645    }
646}
647
648/// Complete common TSA result plus exact original-item coverage. This remains
649/// separate from modern baseline-format conformance and application trust.
650pub struct HistoricalChecked<'model, 'document, 'path> {
651    timestamp: Checked<'model, 'document, 'path>,
652    coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
653    framing: Framing,
654    variant: Option<Variant>,
655}
656impl<'m, 'd, 'p> HistoricalChecked<'m, 'd, 'p> {
657    /// Completed common signature/ESS/authority/time facts, including negatives.
658    pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
659        &self.timestamp
660    }
661    /// Exact mathematical selection; use its own successful actor to bind it.
662    pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
663        &self.coverage
664    }
665    /// Original versus explicitly projected unsigned-field framing.
666    pub fn framing(&self) -> Framing {
667        self.framing
668    }
669    /// Completed matching variant; absent on semantic disagreement.
670    pub fn matched_variant(&self) -> Option<Variant> {
671        self.variant
672    }
673    /// Derives every actually covered earlier TSA/counter from this archive's
674    /// own successful actor. Its own TSA is excluded by exact token identity,
675    /// not by an attribute identifier which might contain other older values.
676    pub fn requirements(
677        &self,
678        prepared: &Preparation<'m, 'd>,
679        operation: &Reader<'d>,
680    ) -> Result<
681        structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
682        super::ArchiveScopeError,
683    > {
684        super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
685    }
686}
687
688impl<'m, 'd> super::Context<'_, 'm, 'd> {
689    /// Performs the SAME complete common CMS/ESS/TSA/effective-key/path/time
690    /// pipeline as an ordinary token, using this sealed historical recipe.
691    /// Historical mathematical validity does not waive modern baseline rules.
692    #[expect(
693        clippy::result_large_err,
694        reason = "original profile causes stay unboxed on refusal"
695    )]
696    pub fn check_historical<'a, 'p, P, S: Source, PE, RE>(
697        &'a self,
698        provider: &'a P,
699        path: &'p structured_ltv::pki::CheckedPath<'d>,
700        certificate_parent: &Reader<'d>,
701        time: &super::CheckTime<'_, 'd>,
702        input: &mut HistoricalInput<'_, 'm, 'd, S>,
703        recognizes: impl FnMut(
704            &tsp::TokenExtension<'_>,
705            &structured_asn1::memory::Execution<'_>,
706        ) -> Result<bool, RE>,
707    ) -> Result<HistoricalChecked<'m, 'd, 'p>, super::CheckError<StreamError<S::Error>, PE, RE>>
708    where
709        P: for<'scope> structured_cms::provider::SignatureProvider<'scope, Error = PE>,
710    {
711        if self.ordinal != input.ordinal
712            || !core::ptr::eq(self.prepared, input.prepared)
713            || input.covered.is_none()
714        {
715            return Err(super::CheckError::Selection(Error::Foreign {
716                at: self.entry.property().span().start,
717            }));
718        }
719        let value = self
720            .entry
721            .value()
722            .ok_or(super::CheckError::Selection(Error::Foreign {
723                at: self.entry.property().span().start,
724            }))?;
725        let timestamp = self.check_computed_profile(
726            provider,
727            path,
728            certificate_parent,
729            time,
730            input,
731            (recognizes, |_| {
732                Ok::<(), tsp::ProfileFailure<core::convert::Infallible>>(())
733            }),
734        )?;
735        let coverage = Coverage::for_token(
736            ISSUER,
737            self.prepared.document().snapshot(),
738            self.entry.owner(),
739            self.entry.property(),
740            Node::of(value.span()),
741            Purpose::Archive,
742            input
743                .covered
744                .take()
745                .ok_or(super::CheckError::Selection(Error::Foreign {
746                    at: self.entry.property().span().start,
747                }))?,
748        );
749        Ok(HistoricalChecked {
750            timestamp,
751            coverage,
752            framing: input.framing,
753            variant: input.matched.get(),
754        })
755    }
756}