Skip to main content

structured_ltv/material/
index.rs

1//! Exact original artifact availability, distinct from validation and trust.
2
3use super::{Coverage, CoverageError};
4use crate::pki::CheckedPath;
5use crate::source::{Location, Query, ScopeError, Snapshot};
6use crate::timestamp::{Purpose, Verified};
7use core::hash::Hash;
8use core::marker::PhantomData;
9use structured_asn1::memory::{HashWork, MapExecution, MemoryError, OwnedMap, OwnedVec, Work};
10use structured_pkix::{Certificate, crl::CertificateList, ocsp::BasicResponse};
11mod storage;
12#[cfg(test)]
13mod tests;
14use storage::Storage;
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub(super) enum Kind {
18    Certificate,
19    Crl,
20    Basic,
21}
22
23/// Exact identity of one original artifact: its kind and original DER. Two
24/// separately parsed copies of the same value have the same identity.
25#[derive(Clone, Copy, PartialEq, Eq, Hash)]
26pub struct ArtifactKey<'source> {
27    pub(super) kind: Kind,
28    pub(super) bytes: &'source [u8],
29}
30
31impl HashWork for ArtifactKey<'_> {
32    fn hash_work(&self) -> Result<u64, MemoryError> {
33        u64::try_from(self.bytes.len())
34            .map_err(|_| MemoryError::WorkExceeded)?
35            .checked_add(2)
36            .ok_or(MemoryError::WorkExceeded)
37    }
38}
39
40/// Exact identity of a revocation choice carried in a timestamp token. These
41/// ordinals do not address the format's own material pool.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct CarriedOrigin {
44    /// Ordinal in the frozen timestamp token-source set.
45    pub token: usize,
46    /// Original choice ordinal inside that token's `SignedData` revocation set.
47    pub choice: usize,
48}
49
50/// One actual location of an original certificate or status artifact. No path,
51/// authorization, freshness, reason coverage or attained level is asserted.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum ArtifactSource {
54    /// Ordinal in the format's frozen material sources/values pool.
55    Material(usize),
56    /// Certificate already parsed in an original timestamp `SignedData`.
57    TokenCertificate {
58        /// Ordinal in the frozen timestamp source pool.
59        token: usize,
60        /// Ordinal in that message's parsed X.509 certificate catalogue.
61        certificate: usize,
62    },
63    /// Original revocation choice in the separate timestamp source pool.
64    TokenRevocation(CarriedOrigin),
65}
66
67/// One original occurrence, with an explicit source-pool discriminator and the
68/// format's own placement of the value.
69#[derive(Debug, Clone, Copy)]
70pub struct Occurrence<N, D, G, P> {
71    source: ArtifactSource,
72    location: Location<N, D, G>,
73    placement: P,
74    embedded_certificate: Option<usize>,
75}
76
77/// One paid catalogue step: an original occurrence or its source/query refusal.
78type OccurrenceItem<N, D, G, P> = Result<Occurrence<N, D, G, P>, IndexError<N>>;
79
80impl<N: Copy, D: Copy, G: Copy, P: Copy> Occurrence<N, D, G, P> {
81    /// A direct occurrence of a certificate, CRL or whole OCSP value.
82    #[must_use]
83    pub fn new(source: ArtifactSource, location: Location<N, D, G>, placement: P) -> Self {
84        Self {
85            source,
86            location,
87            placement,
88            embedded_certificate: None,
89        }
90    }
91    /// Actual original source pool and its ordinal, never a shared ambiguous
92    /// integer for format material and timestamp token sources.
93    #[must_use]
94    pub fn source(&self) -> ArtifactSource {
95        self.source
96    }
97    /// Original enclosure of the whole material value.
98    #[must_use]
99    pub fn location(&self) -> Location<N, D, G> {
100        self.location
101    }
102    /// Actual container role, not a permission to use it for every actor.
103    #[must_use]
104    pub fn placement(&self) -> P {
105        self.placement
106    }
107    /// Certificate ordinal inside an OCSP basic response, if the artifact is
108    /// embedded there. None means a direct certificate/status value.
109    #[must_use]
110    pub fn embedded_certificate(&self) -> Option<usize> {
111        self.embedded_certificate
112    }
113}
114
115struct Entry<N, D, G, P> {
116    value: Occurrence<N, D, G, P>,
117    next: Option<usize>,
118}
119
120#[derive(Hash, PartialEq, Eq)]
121struct Bucket {
122    kind: Kind,
123    hash: u64,
124}
125impl HashWork for Bucket {
126    fn hash_work(&self) -> Result<u64, MemoryError> {
127        Ok(2)
128    }
129}
130struct Class<'source> {
131    key: ArtifactKey<'source>,
132    producer: structured_pkix::algorithm::Producer<'source>,
133    head: usize,
134    tail: usize,
135    next: Option<usize>,
136}
137
138/// Accounted scoped index of every original occurrence. Keys borrow original
139/// DER, not copied/normalized certificate names or unqualified fingerprints.
140/// The same artifact in several containers keeps ALL locations, so later
141/// archive/actor checks cannot mistake a late or disallowed copy for coverage.
142///
143/// `P` is the format's placement of an occurrence and `O` an entry of the
144/// format's schema-open catalogue; the index stores and returns both unread.
145/// Only the holder of issuer `I` builds it from the format's own pools, and it
146/// locates material only under coverage and evidence of the same issuer.
147pub struct Index<'source, N, D, G, P, O, W: Work, I> {
148    snapshot: Snapshot<D, G>,
149    signature: N,
150    keys: Option<OwnedMap<Bucket, usize, W>>,
151    classes: Storage<Class<'source>, W>,
152    entries: Storage<Entry<N, D, G, P>, W>,
153    open: &'source [O],
154    issuer: PhantomData<I>,
155    refused: Option<IndexError<N>>,
156}
157
158/// Original source refusal is independent of index capacity/work refusal.
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum IndexError<N> {
161    /// Source generation or current-operation failure.
162    Scope(ScopeError<N>),
163    /// A supplied view/index belongs to another original source generation.
164    Foreign {
165        /// Original selected signature.
166        signature: N,
167    },
168    /// Index hashing/probes/storage or occurrence traversal was not admitted.
169    Resource {
170        /// Original selected signature.
171        signature: N,
172        /// Original resource cause.
173        cause: MemoryError,
174    },
175}
176
177/// Refusal to transfer an existing format index to its enclosing source.
178#[derive(Debug)]
179pub enum ProjectionError<N, E> {
180    /// Original/current source or storage refusal.
181    Index(IndexError<N>),
182    /// The format's unchanged source-projection cause.
183    Projection(E),
184}
185
186/// A transferred exact-artifact index and its independent projection cause.
187pub type Projection<'s, N, D, G, P, O, W, I, E> =
188    Result<Index<'s, N, D, G, P, O, W, I>, ProjectionError<N, E>>;
189
190/// One original parsed artifact to locate. This is a source reference, not a
191/// receipt of authentication, applicability, freshness or actor membership.
192#[derive(Debug, Clone, Copy)]
193pub enum ArtifactRef<'view, 'source> {
194    /// Exact original certificate, including a separately supplied anchor.
195    Certificate(&'view Certificate<'source>),
196    /// Exact whole complete or delta CRL. A bound pair requires both artifacts.
197    Crl(&'view CertificateList<'source>),
198    /// Exact basic response from its format-defined retained container. CMS
199    /// RFC5940 uses a whole `OCSPResponse`; legacy `CAdES` Annex A.1.4 explicitly
200    /// carries `BasicOCSPResponse`. The format issuer admits the REAL placement;
201    /// an arbitrary caller body cannot mint an indexed occurrence or evidence.
202    Ocsp(&'view BasicResponse<'source>),
203}
204
205impl<'source> ArtifactRef<'_, 'source> {
206    fn producer(self) -> structured_pkix::algorithm::Producer<'source> {
207        match self {
208            Self::Certificate(value) => value.signature_algorithm().execution_origin(),
209            Self::Crl(value) => value.signature_algorithm().execution_origin(),
210            Self::Ocsp(value) => value.signature_algorithm().execution_origin(),
211        }
212    }
213    /// Original artifact and current-operation funding for a new hash,
214    /// comparison or retained query result. Equal accounts do not waive the
215    /// producing local ceiling, and no wire input is reparsed.
216    #[must_use]
217    pub fn execution_in<'operation, W: Work>(
218        &self,
219        work: &'operation W,
220    ) -> structured_asn1::memory::Execution<'operation>
221    where
222        'source: 'operation,
223    {
224        match self {
225            Self::Certificate(value) => value.execution_in(work),
226            Self::Crl(value) => value.execution_in(work),
227            Self::Ocsp(value) => value.execution_in(work),
228        }
229    }
230
231    /// Exact identity of the referenced artifact, borrowing its original DER.
232    #[must_use]
233    pub fn identity(self) -> ArtifactKey<'source> {
234        match self {
235            Self::Certificate(value) => ArtifactKey {
236                kind: Kind::Certificate,
237                bytes: value.as_bytes(),
238            },
239            Self::Crl(value) => ArtifactKey {
240                kind: Kind::Crl,
241                bytes: value.as_bytes(),
242            },
243            Self::Ocsp(value) => ArtifactKey {
244                kind: Kind::Basic,
245                bytes: value.as_bytes(),
246            },
247        }
248    }
249}
250
251/// Exact original artifact availability under one checked archive input.
252#[derive(Debug)]
253pub enum Archived<N, D, G, P> {
254    /// No exact artifact was incorporated in either original source pool.
255    Missing,
256    /// Exact copies exist, but none is covered by this particular archive input.
257    /// A later property or the archive token's own material cannot repair it.
258    Uncovered,
259    /// At least one exact incorporated copy is mathematically covered.
260    Present(Occurrence<N, D, G, P>),
261}
262
263/// Exact position of a certificate in a supplied, actually checked path.
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum PathCertificate {
266    /// Explicitly supplied trust-anchor certificate, if one exists. This is
267    /// material availability only, never a requirement to revoke an anchor.
268    Anchor,
269    /// Issuer-to-subject ordinal in the checked non-anchor member catalogue.
270    Member(usize),
271}
272
273/// One checked-path certificate's mathematical availability before an archive.
274#[derive(Debug)]
275pub struct ArchivedCertificate<N, D, G, P> {
276    /// Original role; no caller-supplied certificate count is used.
277    pub role: PathCertificate,
278    /// Exact copies are searched using the same archive coverage receipt.
279    pub availability: Archived<N, D, G, P>,
280}
281
282/// Complete certificate census for one actual checked path and archive input.
283/// It deliberately retains its path: ordinals cannot be relabelled as another
284/// actor after this check. Status artifacts and actor association are separate.
285pub struct PathCertificates<'path, 'document, N, D, G, P> {
286    path: &'path CheckedPath<'document>,
287    archive: Location<N, D, G>,
288    entries: OwnedVec<ArchivedCertificate<N, D, G, P>>,
289    all_covered: bool,
290}
291
292impl<'path, 'document, N: Copy, D: Copy, G: Copy, P>
293    PathCertificates<'path, 'document, N, D, G, P>
294{
295    /// Same actual common-validator path, not an independent artifact list.
296    #[must_use]
297    pub fn path(&self) -> &'path CheckedPath<'document> {
298        self.path
299    }
300    /// Original archive token whose actual input coverage was used.
301    #[must_use]
302    pub fn archive(&self) -> Location<N, D, G> {
303        self.archive
304    }
305    /// Every existing anchor/member certificate, including missing late copies.
306    #[must_use]
307    pub fn entries(&self) -> &[ArchivedCertificate<N, D, G, P>] {
308        &self.entries
309    }
310    /// Whether every certificate in this path has an exact covered copy.
311    /// This alone proves neither status completeness nor any baseline level.
312    #[must_use]
313    pub fn all_certificates_covered(&self) -> bool {
314        self.all_covered
315    }
316}
317
318/// Original source/resource failure, separately from missing archive material.
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub enum ArchiveError<N> {
321    /// Exact artifact hashing, lookup or source traversal refused.
322    Index(IndexError<N>),
323    /// The mathematical input coverage/source binding could not be established.
324    Coverage(CoverageError<N>),
325    /// The supplied successful token covers a different kind of input.
326    NotArchive {
327        /// Actual original timestamp property.
328        property: N,
329    },
330}
331
332impl<'source, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, I>
333    Index<'source, N, D, G, P, O, W, I>
334{
335    /// An empty index for this signature in the query's source generation.
336    ///
337    /// `open` is the format's schema-open material catalogue, retained only so
338    /// that format checks find it beside the index. Requiring the issuer value
339    /// keeps a caller of the format from building an index of its own.
340    pub fn new<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
341        _issuer: I,
342        signature: N,
343        open: &'source [O],
344        query: &Q,
345    ) -> Result<Self, IndexError<N>> {
346        query.admit(signature, 1).map_err(IndexError::Scope)?;
347        let execution = query.execution();
348        Ok(Self {
349            snapshot: query.snapshot(),
350            signature,
351            keys: Some(execution.map()),
352            classes: Storage::new(execution.vector()),
353            entries: Storage::new(execution.vector()),
354            open,
355            issuer: PhantomData,
356            refused: None,
357        })
358    }
359
360    /// Records one occurrence of an exact original artifact. For an OCSP value
361    /// every certificate embedded in its basic response is recorded too, with
362    /// the same location and placement. Hashing, probes and storage are
363    /// admitted first; a refusal leaves the index for the caller to drop. The
364    /// builder pays its own traversal of the format's pools with [`Self::admit`].
365    /// Requiring the issuer value keeps a caller of the format, which may own
366    /// the index, from adding material the document does not carry.
367    pub fn insert<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
368        &mut self,
369        _issuer: I,
370        artifact: ArtifactRef<'_, 'source>,
371        occurrence: Occurrence<N, D, G, P>,
372        query: &Q,
373    ) -> Result<(), IndexError<N>> {
374        // Coverage lookups compare only the occurrence's node, so its source
375        // generation must already be this index's.
376        if occurrence.location.snapshot != self.snapshot || query.snapshot() != self.snapshot {
377            return Err(IndexError::Foreign {
378                signature: self.signature,
379            });
380        }
381        let result = self.insert_artifact(artifact, occurrence, query);
382        if let Err(cause) = result {
383            self.refused = Some(cause);
384        }
385        result
386    }
387
388    fn insert_artifact<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
389        &mut self,
390        artifact: ArtifactRef<'_, 'source>,
391        occurrence: Occurrence<N, D, G, P>,
392        query: &Q,
393    ) -> Result<(), IndexError<N>> {
394        self.admit(query)?;
395        self.insert_key(artifact.identity(), artifact.producer(), occurrence, query)?;
396        if let ArtifactRef::Ocsp(response) = artifact {
397            for certificate in 0..response.certificates().len() {
398                self.admit(query)?;
399                self.insert_key(
400                    ArtifactKey {
401                        kind: Kind::Certificate,
402                        bytes: response.certificates()[certificate].as_bytes(),
403                    },
404                    response.certificates()[certificate]
405                        .signature_algorithm()
406                        .execution_origin(),
407                    Occurrence {
408                        embedded_certificate: Some(certificate),
409                        ..occurrence
410                    },
411                    query,
412                )?;
413            }
414        }
415        Ok(())
416    }
417
418    /// The format's schema-open catalogue, as supplied.
419    #[must_use]
420    pub fn open(&self) -> &'source [O] {
421        self.open
422    }
423
424    /// Projects a complete inner-format pool into its actual enclosing source.
425    /// Exact artifact keys and their allocation are transferred unchanged;
426    /// models are neither reparsed nor rehashed. Only occurrence metadata gets
427    /// a new funded buffer. The enclosing format must possess its private
428    /// issuer and verify the original source association in `project`.
429    /// Failure drops both partial destination and original index, never returns
430    /// a successful prefix, and does not roll back a visitor's external effects.
431    ///
432    /// # Panics
433    /// Panics if the internal invariant that a non-refused index owns its
434    /// restored artifact map is violated.
435    pub fn into_projected<N2, D2, G2, P2, I2, Q, E>(
436        self,
437        _issuer: I2,
438        signature: N2,
439        query: &Q,
440        mut project: impl FnMut(
441            Occurrence<N, D, G, P>,
442        ) -> Result<(ArtifactSource, Location<N2, D2, G2>, P2), E>,
443    ) -> Projection<'source, N2, D2, G2, P2, O, Q::Work, I2, E>
444    where
445        N2: Copy + Eq,
446        D2: Copy + Eq,
447        G2: Copy + Eq,
448        P2: Copy,
449        Q: Query<Id = N2, Document = D2, Generation = G2>,
450    {
451        query
452            .admit(signature, 1)
453            .map_err(|cause| ProjectionError::Index(IndexError::Scope(cause)))?;
454        let execution = query.execution();
455        let Index {
456            keys,
457            classes,
458            entries,
459            open,
460            refused,
461            ..
462        } = self;
463        if refused.is_some() {
464            return Err(ProjectionError::Index(IndexError::Foreign { signature }));
465        }
466        let keys = keys
467            .expect("restored artifact map")
468            .into_owned()
469            .with_execution(execution)
470            .map_err(|_| ProjectionError::Index(IndexError::Foreign { signature }))?;
471        let classes = classes
472            .into_inner()
473            .into_owned()
474            .with_execution(execution)
475            .map_err(|_| ProjectionError::Index(IndexError::Foreign { signature }))?;
476        let mut projected = execution.vector();
477        projected
478            .try_reserve_exact(entries.len())
479            .map_err(|cause| ProjectionError::Index(IndexError::Resource { signature, cause }))?;
480        query
481            .admit(
482                signature,
483                u64::try_from(entries.len()).map_err(|_| {
484                    ProjectionError::Index(IndexError::Resource {
485                        signature,
486                        cause: MemoryError::WorkExceeded,
487                    })
488                })?,
489            )
490            .map_err(|cause| ProjectionError::Index(IndexError::Scope(cause)))?;
491        for entry in entries.into_inner() {
492            let (source, location, placement) =
493                project(entry.value).map_err(ProjectionError::Projection)?;
494            if location.snapshot != query.snapshot() {
495                return Err(ProjectionError::Index(IndexError::Foreign { signature }));
496            }
497            projected
498                .try_push(Entry {
499                    value: Occurrence {
500                        source,
501                        location,
502                        placement,
503                        embedded_certificate: entry.value.embedded_certificate,
504                    },
505                    next: entry.next,
506                })
507                .map_err(|cause| {
508                    ProjectionError::Index(IndexError::Resource { signature, cause })
509                })?;
510        }
511        Ok(Index {
512            snapshot: query.snapshot(),
513            signature,
514            keys: Some(keys),
515            classes: Storage::new(classes),
516            entries: Storage::new(projected),
517            open,
518            issuer: PhantomData,
519            refused: None,
520        })
521    }
522
523    /// Every original indexed occurrence in source-catalogue insertion order,
524    /// including duplicate copies. No hashing, lookup, allocation or clone is
525    /// needed. Current query admission precedes each actual entry access;
526    /// the first refusal is returned once and permanently fuses the traversal.
527    /// Format recipes use this to derive coverage from their already parsed
528    /// material index rather than parsing or enumerating material again.
529    pub fn all_occurrences<'view, 'query, Q>(
530        &'view self,
531        query: &'query Q,
532    ) -> Result<
533        impl Iterator<Item = OccurrenceItem<N, D, G, P>>
534        + use<'view, 'query, 'source, N, D, G, P, O, W, Q, I>,
535        IndexError<N>,
536    >
537    where
538        Q: Query<Id = N, Document = D, Generation = G>,
539    {
540        self.admit(query)?;
541        let mut next = if self.entries.is_empty() {
542            None
543        } else {
544            Some(0)
545        };
546        Ok(core::iter::from_fn(move || {
547            let ordinal = next.take()?;
548            if let Err(cause) = self.admit(query) {
549                return Some(Err(cause));
550            }
551            let value = self.entries[ordinal].value;
552            if ordinal + 1 < self.entries.len() {
553                next = Some(ordinal + 1);
554            }
555            Some(Ok(value))
556        }))
557    }
558
559    /// Original selected signature of this index.
560    #[must_use]
561    pub fn signature(&self) -> N {
562        self.signature
563    }
564
565    /// Source generation of every occurrence.
566    #[must_use]
567    pub fn snapshot(&self) -> Snapshot<D, G> {
568        self.snapshot
569    }
570
571    /// Finds all copies of this exact original certificate, including copies
572    /// inside OCSP. A match is availability only, never authentication/trust.
573    pub fn certificate<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
574        &'view self,
575        value: &Certificate<'_>,
576        query: &'query Q,
577    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
578        self.find(ArtifactRef::Certificate(value), query)
579    }
580    /// Finds all whole CRL copies. An authenticated delta still needs its
581    /// authenticated/bound complete source; finding either alone proves no pair.
582    pub fn crl<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
583        &'view self,
584        value: &CertificateList<'_>,
585        query: &'query Q,
586    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
587        self.find(ArtifactRef::Crl(value), query)
588    }
589    /// Finds the exact basic response in format-issued retained occurrences.
590    /// Good-looking status or a successful wire code does not mint evidence.
591    pub fn ocsp<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
592        &'view self,
593        value: &BasicResponse<'_>,
594        query: &'query Q,
595    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
596        self.find(ArtifactRef::Ocsp(value), query)
597    }
598
599    /// Finds all copies of this exact artifact, in source catalogue order.
600    pub fn artifact<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
601        &'view self,
602        artifact: ArtifactRef<'_, '_>,
603        query: &'query Q,
604    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
605        self.find(artifact, query)
606    }
607
608    /// Requires that this index was built for `signature`.
609    pub fn require_signature<Q: Query<Id = N, Document = D, Generation = G>>(
610        &self,
611        signature: N,
612        query: &Q,
613    ) -> Result<(), IndexError<N>> {
614        self.admit(query)?;
615        let same =
616            query
617                .equal(self.signature, signature)
618                .map_err(|cause| IndexError::Resource {
619                    signature: self.signature,
620                    cause,
621                })?;
622        if !same {
623            return Err(IndexError::Foreign { signature });
624        }
625        Ok(())
626    }
627
628    /// Locates a whole original artifact in this exact checked archive input.
629    ///
630    /// EN319132-1 §6.3(aa)/§5.5.2.2 requires the necessary material to have
631    /// been incorporated before generating each archive timestamp. This uses
632    /// the same selection the format used for actual timestamp input, so merely
633    /// finding a late or self-carried copy cannot count as covered. All copies
634    /// remain available; an early uncovered duplicate does not hide a covered
635    /// one. This proves mathematical availability only. The caller must derive
636    /// required artifacts from actual actor/path/status checks and must
637    /// authenticate the TSA separately before claiming a baseline level.
638    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
639    pub fn archived<Q: Query<Id = N, Document = D, Generation = G>, C: Work>(
640        &self,
641        artifact: ArtifactRef<'_, '_>,
642        coverage: &Coverage<N, D, G, C, I>,
643        evidence: &Verified<N, D, G, I>,
644        query: &Q,
645    ) -> Result<Archived<N, D, G, P>, ArchiveError<N>>
646    where
647        N: HashWork,
648    {
649        if evidence.purpose() != Purpose::Archive {
650            return Err(ArchiveError::NotArchive {
651                property: evidence.property(),
652            });
653        }
654        // Validate even empty/missing catalogues before deriving an absence.
655        if evidence.token().snapshot != self.snapshot {
656            return Err(ArchiveError::Index(IndexError::Foreign {
657                signature: self.signature,
658            }));
659        }
660        self.require_signature(evidence.signature(), query)
661            .map_err(ArchiveError::Index)?;
662        coverage
663            .require_evidence(evidence, evidence.property(), query)
664            .map_err(ArchiveError::Coverage)?;
665        let copies = self.find(artifact, query).map_err(ArchiveError::Index)?;
666        let mut found = false;
667        for copy in copies {
668            let copy = copy.map_err(ArchiveError::Index)?;
669            found = true;
670            // The immutable coverage/evidence pair was bound once above;
671            // each occurrence still checks the current source and lookup.
672            if coverage
673                .selects(copy.location.node, query)
674                .map_err(ArchiveError::Coverage)?
675            {
676                return Ok(Archived::Present(copy));
677            }
678        }
679        Ok(if found {
680            Archived::Uncovered
681        } else {
682            Archived::Missing
683        })
684    }
685
686    /// Derives certificate requirements from the actual common-validator path,
687    /// including an existing anchor certificate, then checks each exact artifact
688    /// against this archive's mathematical input. Missing/late copies remain
689    /// facts; resource/source failure returns no successful partial census.
690    ///
691    /// EN319132-1 V1.3.1 §§5.4.2/5.5.2.2/6.3(aa) require the material of the
692    /// actual actors before each archive. This certificate component does not
693    /// assert that status, credential, countersigner or previous-TSA processing
694    /// completed. The caller must supply their separately bound paths.
695    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
696    pub fn archived_path<'path, 'document, Q, C: Work>(
697        &self,
698        path: &'path CheckedPath<'document>,
699        coverage: &Coverage<N, D, G, C, I>,
700        evidence: &Verified<N, D, G, I>,
701        query: &Q,
702    ) -> Result<PathCertificates<'path, 'document, N, D, G, P>, ArchiveError<N>>
703    where
704        N: HashWork,
705        Q: Query<Id = N, Document = D, Generation = G>,
706    {
707        query
708            .admit(self.signature, 1)
709            .map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
710        let mut entries = query.execution().vector();
711        let mut all_covered = true;
712        let mut record = |certificate, role| {
713            let availability = self.archived(
714                ArtifactRef::Certificate(certificate),
715                coverage,
716                evidence,
717                query,
718            )?;
719            if !matches!(availability, Archived::Present(_)) {
720                all_covered = false;
721            }
722            entries
723                .try_push(ArchivedCertificate { role, availability })
724                .map_err(|cause| {
725                    ArchiveError::Index(IndexError::Resource {
726                        signature: self.signature,
727                        cause,
728                    })
729                })
730        };
731        if let Some(certificate) = path.anchor().certificate() {
732            record(certificate, PathCertificate::Anchor)?;
733        }
734        for (ordinal, member) in path.members().iter().enumerate() {
735            self.admit(query).map_err(ArchiveError::Index)?;
736            record(member.certificate(), PathCertificate::Member(ordinal))?;
737        }
738        Ok(PathCertificates {
739            path,
740            archive: evidence.token(),
741            entries: entries.into_owned(),
742            all_covered,
743        })
744    }
745
746    /// Admits one traversal step over this index's source generation.
747    pub fn admit<Q: Query<Id = N, Document = D, Generation = G>>(
748        &self,
749        query: &Q,
750    ) -> Result<(), IndexError<N>> {
751        if let Some(cause) = self.refused {
752            return Err(cause);
753        }
754        if self.snapshot != query.snapshot() {
755            return Err(IndexError::Foreign {
756                signature: self.signature,
757            });
758        }
759        query.admit(self.signature, 2).map_err(IndexError::Scope)
760    }
761
762    fn insert_key<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
763        &mut self,
764        key: ArtifactKey<'source>,
765        producer: structured_pkix::algorithm::Producer<'source>,
766        value: Occurrence<N, D, G, P>,
767        query: &Q,
768    ) -> Result<(), IndexError<N>> {
769        self.admit(query)?;
770        let signature = self.signature;
771        let resource = |cause| IndexError::Resource { signature, cause };
772        let storage = |cause| match cause {
773            storage::Error::Resource(cause) => resource(cause),
774            storage::Error::Foreign => IndexError::Foreign { signature },
775        };
776        let work = producer.execution_in(query.execution());
777        work.charge_work(1).map_err(resource)?;
778        let bucket = Bucket {
779            kind: key.kind,
780            hash: self
781                .keys
782                .as_ref()
783                .expect("restored artifact map")
784                .fingerprint(&key, &work)
785                .map_err(resource)?,
786        };
787        let previous = self.class(&bucket, key, producer, query)?;
788        // Linking a duplicate changes two retained metadata slots. Admit
789        // both writes before appending the new occurrence or mutating links.
790        if previous.is_some() {
791            work.charge_work(2).map_err(resource)?;
792        }
793        let next = self.entries.len();
794        self.entries
795            .scoped(work, |entries| {
796                entries.try_push(Entry { value, next: None })
797            })
798            .map_err(storage)?;
799        if let Some(ordinal) = previous {
800            let tail = self.classes[ordinal].tail;
801            self.entries[tail].next = Some(next);
802            self.classes[ordinal].tail = next;
803        } else {
804            let table = self.keys.as_ref().expect("restored artifact map");
805            let head = table.get(&bucket, &work).map_err(resource)?.copied();
806            let ordinal = self.classes.len();
807            self.classes
808                .scoped(work, |classes| {
809                    classes.try_push(Class {
810                        key,
811                        producer,
812                        head: next,
813                        tail: next,
814                        next: head,
815                    })
816                })
817                .map_err(storage)?;
818            let original = self.keys.take().expect("restored artifact map");
819            let (mut table, previous) = match original.rebind(work) {
820                MapExecution::Changed { map, previous } => (map, previous),
821                MapExecution::Foreign { map, .. } => {
822                    self.keys = Some(map);
823                    return Err(IndexError::Foreign { signature });
824                }
825            };
826            let result = table.try_insert(bucket, ordinal);
827            self.keys = Some(match table.rebind(previous) {
828                MapExecution::Changed { map, .. } => map,
829                MapExecution::Foreign { .. } => {
830                    unreachable!("restore uses the original table account")
831                }
832            });
833            result.map_err(resource)?;
834        }
835        Ok(())
836    }
837
838    fn class<Q: Query<Id = N, Document = D, Generation = G>>(
839        &self,
840        bucket: &Bucket,
841        key: ArtifactKey<'_>,
842        producer: structured_pkix::algorithm::Producer<'_>,
843        query: &Q,
844    ) -> Result<Option<usize>, IndexError<N>> {
845        let resource = |cause| IndexError::Resource {
846            signature: self.signature,
847            cause,
848        };
849        let work = producer.execution_in(query.execution());
850        let mut next = self
851            .keys
852            .as_ref()
853            .expect("restored artifact map")
854            .get(bucket, &work)
855            .map_err(resource)?
856            .copied();
857        while let Some(ordinal) = next {
858            self.admit(query)?;
859            let class = &self.classes[ordinal];
860            let original = class.producer.execution_in(query.execution());
861            original.charge_work(1).map_err(resource)?;
862            if key.kind == class.key.kind && key.bytes.len() == class.key.bytes.len() {
863                if core::ptr::eq(key.bytes, class.key.bytes) {
864                    return Ok(Some(ordinal));
865                }
866                let length = u64::try_from(key.bytes.len())
867                    .map_err(|_| resource(MemoryError::WorkExceeded))?;
868                work.charge_work(length).map_err(resource)?;
869                original.charge_work(length).map_err(resource)?;
870                if key.bytes == class.key.bytes {
871                    return Ok(Some(ordinal));
872                }
873            }
874            next = class.next;
875        }
876        Ok(None)
877    }
878
879    fn find<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
880        &'view self,
881        artifact: ArtifactRef<'_, '_>,
882        query: &'query Q,
883    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
884        self.admit(query)?;
885        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
886        let work = artifact.execution_in(query.execution());
887        // Even an empty index observes this external artifact. Its producing
888        // local ceiling is not a credit supplied by a wider current query.
889        work.charge_work(1).map_err(|cause| IndexError::Resource {
890            signature: self.signature,
891            cause,
892        })?;
893        let key = artifact.identity();
894        let bucket = Bucket {
895            kind: key.kind,
896            hash: self
897                .keys
898                .as_ref()
899                .expect("restored artifact map")
900                .fingerprint(&key, &work)
901                .map_err(|cause| IndexError::Resource {
902                    signature: self.signature,
903                    cause,
904                })?,
905        };
906        let next = self
907            .class(&bucket, key, artifact.producer(), query)?
908            .map(|ordinal| self.classes[ordinal].head);
909        Ok(Occurrences {
910            index: self,
911            query,
912            next,
913        })
914    }
915}
916
917/// Every copy of one artifact, or the refusal to look it up.
918pub type Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> =
919    Result<Occurrences<'view, 'query, 'source, N, D, G, P, O, W, Q, I>, IndexError<N>>;
920
921/// Source-catalogue-order traversal of every copy; current Work pays metadata
922/// access even when index storage belongs to an earlier execution. The first
923/// source/resource refusal is returned once and fuses the iterator.
924pub struct Occurrences<'view, 'query, 'source, N, D, G, P, O, W: Work, Q, I> {
925    index: &'view Index<'source, N, D, G, P, O, W, I>,
926    query: &'query Q,
927    next: Option<usize>,
928}
929
930impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> Iterator
931    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
932where
933    Q: Query<Id = N, Document = D, Generation = G>,
934{
935    type Item = Result<Occurrence<N, D, G, P>, IndexError<N>>;
936    fn next(&mut self) -> Option<Self::Item> {
937        let next = self.next.take()?;
938        if let Err(error) = self.index.admit(self.query) {
939            return Some(Err(error));
940        }
941        let entry = &self.index.entries[next];
942        self.next = entry.next;
943        Some(Ok(entry.value))
944    }
945}
946
947impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> core::iter::FusedIterator
948    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
949where
950    Q: Query<Id = N, Document = D, Generation = G>,
951{
952}