Skip to main content

lean_ctx/core/archive/
delivered.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Delivered-content lineage register (G03/G04).
3//!
4//! When the Engine releases source-bound text towards the model, it records
5//! here what that exact text was derived from. A later holder of the same
6//! bytes — the proxy, when the text comes back inside a provider request or
7//! as sealed provider material — looks the lineage up by digest
8//! ([`lineage_of`]) and asks [`Lineage::still_admitted`] whether every bound
9//! source holds NOW. The register identifies; it authorizes nothing.
10//!
11//! A [`Lineage`] never mixes source kinds up:
12//!
13//! - **files** — project files the Engine itself read through the admitted
14//!   rooted reader ([`ArchiveAuthority`]). Re-checked by re-reading each file
15//!   under the current policy and role.
16//! - **presented** — caller-supplied sources of the context plan
17//!   ([`PresentedBinding`]). The Engine cannot re-read them and does not learn
18//!   of changes outside; such a binding holds only while the caller keeps
19//!   presenting exactly that content and the plan keeps admitting it under
20//!   the current policy (`archive::presented`).
21//!
22//! Rules of the register:
23//!
24//! - **Key:** BLAKE3 of the delivered text, exact bytes, no normalisation
25//!   ([`digest`]). A host that re-wraps, truncates or re-encodes the text
26//!   changes the digest: no hit, unknown lineage.
27//! - **Value:** lineage handles only. Never content.
28//! - **Recorded:** only a complete lineage whose every source holds at the
29//!   time of the record. Anything else records nothing: unknown stays
30//!   unknown. Owner decision E3: a restricted source is in no lineage.
31//! - **Same text, second lineage:** united per project (`Lineage::join`);
32//!   the text is admitted only while every source of the union holds.
33//!   Lineages of different projects are kept side by side.
34//! - **Persistence and bounds:** see `archive::register`; 64 texts per shard
35//!   (oldest evicted), `MAX_LINEAGES_PER_TEXT` projects per text,
36//!   `MAX_AGE_SECONDS` age. The files name local paths and source ids: they
37//!   never enter a receipt, a log line or client-visible text.
38//! - **Call ids:** a tool delivery may additionally name the host's id of the
39//!   tool call it answered (`record_tool_delivery_for_call`); at most
40//!   `MAX_CALLS_PER_LINEAGE` per text and project, oldest evicted. They are
41//!   host-supplied metadata, answer only `delivered_to_call`, are never part
42//!   of a lineage handle that leaves this module, and are never copied to a
43//!   derived text.
44
45use serde::{Deserialize, Serialize};
46
47use super::authority::ArchiveAuthority;
48use super::presented::{Presentation, PresentedBinding};
49use super::register::{self, Register};
50
51/// Lineages (one per project) one text can have.
52const MAX_LINEAGES_PER_TEXT: usize = 4;
53/// A record older than this is neither returned nor kept.
54const MAX_AGE_SECONDS: i64 = 24 * 60 * 60;
55/// Caller-supplied sources one lineage can name; beyond it the lineage is
56/// unknown, never cut down to the first few.
57const MAX_PRESENTED: usize = 64;
58/// Call ids kept per text and project; the oldest goes first.
59const MAX_CALLS_PER_LINEAGE: usize = 32;
60/// Longest call id that is recorded or looked up.
61const MAX_CALL_ID_BYTES: usize = 128;
62/// Presenter references kept per text and project; the oldest goes first.
63const MAX_PRESENTER_REFS_PER_LINEAGE: usize = 16;
64
65const REGISTER: Register = Register {
66    name: "delivered",
67    shard_entries: 64,
68    max_age_seconds: MAX_AGE_SECONDS,
69};
70
71/// What a delivered text derives from.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct Lineage {
75    /// Project files the Engine itself read.
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    files: Option<ArchiveAuthority>,
78    /// Caller-supplied sources.
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    presented: Vec<PresentedBinding>,
81    /// A source could not be bound, a bound was exceeded, or parts of
82    /// different projects were united: the lineage is unknown.
83    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
84    incomplete: bool,
85    /// Host ids of the tool calls this exact text was delivered for, oldest
86    /// first. Present only on the persisted entry: every handle this module
87    /// returns has it empty, and [`Lineage::join`] never copies it. An entry
88    /// written before the field existed has none.
89    #[serde(default, skip_serializing_if = "Vec::is_empty")]
90    calls: Vec<String>,
91    /// Content-free references of the presenter entries under which this
92    /// exact text was recorded or released (`presented::PresenterCapability::
93    /// reference`: a digest of a stored verifier, never of a capability),
94    /// oldest first, for a lineage that names caller-supplied sources. They
95    /// tie a record to "the same presenter entry"; they authorize nothing.
96    /// Present only on a persisted entry and on the lineage stored with a
97    /// recovery copy: every handle this module returns has it empty, and
98    /// [`Lineage::join`] never copies it.
99    #[serde(default, skip_serializing_if = "Vec::is_empty")]
100    presenters: Vec<String>,
101}
102
103/// Why a lineage does not authorize its text now. Content-free: no path,
104/// object reference, source id or digest.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum LineageRefusal {
107    /// No lineage, or an incomplete one.
108    Unknown,
109    /// A caller-supplied source has no current presentation.
110    SourceNotPresented,
111    /// A file source or a presented source is refused under the current
112    /// rights or policy (changed, gone, restricted, other project, caller or
113    /// role, policy changed since it was admitted).
114    SourceNoLongerAdmitted,
115}
116
117impl std::fmt::Display for LineageRefusal {
118    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        formatter.write_str(match self {
120            Self::Unknown => "lineage.unknown",
121            Self::SourceNotPresented => "lineage.source_not_presented",
122            Self::SourceNoLongerAdmitted => "lineage.source_no_longer_admitted",
123        })
124    }
125}
126
127impl Lineage {
128    /// Lineage of text derived from project files only.
129    #[must_use]
130    pub fn of_files(authority: ArchiveAuthority) -> Self {
131        Self {
132            incomplete: authority.is_incomplete(),
133            files: Some(authority),
134            presented: Vec::new(),
135            calls: Vec::new(),
136            presenters: Vec::new(),
137        }
138    }
139
140    /// Lineage of text derived from caller-supplied sources only. An empty
141    /// set is not a lineage: it is unknown.
142    #[must_use]
143    pub fn of_presented(presented: Vec<PresentedBinding>) -> Self {
144        let mut lineage = Self {
145            files: None,
146            presented: Vec::new(),
147            incomplete: presented.is_empty(),
148            calls: Vec::new(),
149            presenters: Vec::new(),
150        };
151        for binding in presented {
152            lineage.add_presented(binding);
153        }
154        lineage
155    }
156
157    fn add_presented(&mut self, binding: PresentedBinding) {
158        if self
159            .project()
160            .is_some_and(|project| project != binding.project())
161        {
162            self.incomplete = true;
163            return;
164        }
165        if let Some(known) = self
166            .presented
167            .iter_mut()
168            .find(|known| known.same_source(&binding))
169        {
170            // The same source, admitted again later: one binding.
171            *known = binding;
172        } else if self.presented.len() < MAX_PRESENTED {
173            self.presented.push(binding);
174        } else {
175            self.incomplete = true;
176        }
177    }
178
179    fn project(&self) -> Option<&str> {
180        self.files
181            .as_ref()
182            .map(ArchiveAuthority::project)
183            .or_else(|| self.presented.first().map(PresentedBinding::project))
184    }
185
186    /// Union with another lineage of the same text. A source only one side
187    /// names is added, never dropped; the text then needs every source of
188    /// both. Parts of different projects make the lineage unknown. Call ids
189    /// are not sources: the other side's are dropped, this side's stay.
190    pub fn join(&mut self, other: Self) {
191        self.incomplete |= other.incomplete;
192        if let (Some(mine), Some(theirs)) = (self.project(), other.project())
193            && mine != theirs
194        {
195            self.incomplete = true;
196            return;
197        }
198        match (self.files.as_mut(), other.files) {
199            (Some(mine), Some(theirs)) => {
200                mine.absorb(theirs);
201                self.incomplete |= mine.is_incomplete();
202            }
203            (None, Some(theirs)) => self.files = Some(theirs),
204            (_, None) => {}
205        }
206        for binding in other.presented {
207            self.add_presented(binding);
208        }
209    }
210
211    /// Note that this entry's text was delivered for the call with this id.
212    /// A known id moves to the newest place; beyond the bound the oldest goes.
213    fn note_call(&mut self, call_id: &str) {
214        self.calls.retain(|known| known != call_id);
215        self.calls.push(call_id.to_owned());
216        if self.calls.len() > MAX_CALLS_PER_LINEAGE {
217            let excess = self.calls.len() - MAX_CALLS_PER_LINEAGE;
218            self.calls.drain(..excess);
219        }
220    }
221
222    /// Note the presenter of the request in scope on this entry, if the
223    /// lineage names a caller-supplied source. A known reference moves to the
224    /// newest place; beyond the bound the oldest goes.
225    fn note_presenter(&mut self) {
226        if self.presented.is_empty() {
227            return;
228        }
229        let Some(reference) = super::presented::current_presenter_reference() else {
230            return;
231        };
232        self.presenters.retain(|known| *known != reference);
233        self.presenters.push(reference);
234        if self.presenters.len() > MAX_PRESENTER_REFS_PER_LINEAGE {
235            let excess = self.presenters.len() - MAX_PRESENTER_REFS_PER_LINEAGE;
236            self.presenters.drain(..excess);
237        }
238    }
239
240    /// This lineage as it is stored WITH a record made for the request in
241    /// scope (a recovery copy): the same sources, and the reference of the
242    /// presenter that request showed.
243    #[must_use]
244    pub(super) fn recorded_for_presenter(&self) -> Self {
245        let mut recorded = self.clone();
246        recorded.calls.clear();
247        recorded.presenters.clear();
248        recorded.note_presenter();
249        recorded
250    }
251
252    /// The file authority of a lineage that names project files only (no
253    /// caller-supplied source): it then IS the lineage.
254    pub(super) fn files_only(&self) -> Option<&ArchiveAuthority> {
255        self.files
256            .as_ref()
257            .filter(|_| self.presented.is_empty() && !self.incomplete)
258    }
259
260    /// Publish this lineage into the current call's source capture, for a
261    /// declared tool whose reply is derived from text with this lineage and
262    /// from nothing else (`ctx_expand` on a resolved recovery copy). The
263    /// release point then records the reply as it is delivered.
264    ///
265    /// The capture carries project files only: a file-only lineage is
266    /// published; a lineage that names a caller-presented source, and an
267    /// incomplete one, mark the capture incomplete — that reply stays
268    /// unknown rather than being recorded under its file part alone.
269    pub fn publish_files(&self) {
270        match self.files_only() {
271            Some(files) => files.clone().publish(),
272            None => super::authority::mark_unbound(),
273        }
274    }
275
276    /// A stable, content-free identity of a complete lineage: the same
277    /// sources — files with their admitted digests, presented bindings with
278    /// the policy they were admitted under — give the same value, in whatever
279    /// order they were joined. `None` for an incomplete lineage.
280    pub(super) fn identity_digest(&self) -> Option<String> {
281        if !self.is_complete() {
282            return None;
283        }
284        let files = match &self.files {
285            Some(files) => Some(files.identity_digest()?),
286            None => None,
287        };
288        let mut presented: Vec<String> = self
289            .presented
290            .iter()
291            .map(PresentedBinding::identity)
292            .collect();
293        presented.sort_unstable();
294        let mut hash = blake3::Hasher::new();
295        hash.update(b"leanctx-lineage-v1\0");
296        hash.update(&serde_json::to_vec(&(files, presented)).ok()?);
297        Some(hash.finalize().to_hex().to_string())
298    }
299
300    /// The ids of the sources of a lineage that names caller-supplied
301    /// sources ONLY, sorted and distinct. `None` for every other lineage: a
302    /// file source, an incomplete lineage, no source. The ids are
303    /// content-free and authorize nothing — ask [`Lineage::still_admitted`].
304    #[must_use]
305    pub fn presented_source_ids(&self) -> Option<Vec<String>> {
306        if self.files.is_some() || !self.is_complete() {
307            return None;
308        }
309        let mut ids: Vec<String> = self
310            .presented
311            .iter()
312            .map(|binding| binding.source_id().to_owned())
313            .collect();
314        ids.sort_unstable();
315        ids.dedup();
316        Some(ids)
317    }
318
319    /// Whether the lineage names every source of its text.
320    #[must_use]
321    pub fn is_complete(&self) -> bool {
322        !self.incomplete
323            && (self.files.is_some() || !self.presented.is_empty())
324            && self
325                .files
326                .as_ref()
327                .is_none_or(|files| !files.is_incomplete())
328    }
329
330    /// Current authority of every bound source. Call inside the
331    /// `REQUEST_PROJECT` scope of the request the text returns in.
332    ///
333    /// - files: every file is re-read through the admitted rooted reader
334    ///   under the current policy and role and must be unchanged, complete
335    ///   and storable;
336    /// - presented: project, caller and role must be those of the request,
337    ///   and a presentation of exactly that binding must exist that was
338    ///   admitted under the current policy identity within the validity.
339    ///   This says nothing about the caller's or a provider's own copy.
340    pub fn still_admitted(&self) -> Result<(), LineageRefusal> {
341        if !self.is_complete() {
342            return Err(LineageRefusal::Unknown);
343        }
344        if self
345            .files
346            .as_ref()
347            .is_some_and(|files| !files.still_admitted())
348        {
349            return Err(LineageRefusal::SourceNoLongerAdmitted);
350        }
351        self.presented_still_current()
352    }
353
354    /// Current permission of every bound source, for returning a
355    /// provider-sealed item (#2177). Call inside the `REQUEST_PROJECT` scope
356    /// of the request the item returns in.
357    ///
358    /// Identical to [`Lineage::still_admitted`] except for the files: every
359    /// file must still be permitted for its tool under the current policy
360    /// and role, complete and storable (not restricted, E3), but it may have
361    /// CHANGED since it was read (`ArchiveAuthority::still_permitted`). A
362    /// read → think → edit loop therefore keeps its sealed items; a source
363    /// that is gone, withheld or restricted refuses them. Presented bindings
364    /// are checked exactly as in [`Lineage::still_admitted`].
365    pub fn still_permitted(&self) -> Result<(), LineageRefusal> {
366        if !self.is_complete() {
367            return Err(LineageRefusal::Unknown);
368        }
369        if self
370            .files
371            .as_ref()
372            .is_some_and(|files| !files.still_permitted())
373        {
374            return Err(LineageRefusal::SourceNoLongerAdmitted);
375        }
376        self.presented_still_current()
377    }
378
379    /// The presented half of [`Lineage::still_admitted`] and
380    /// [`Lineage::still_permitted`]: the same for both.
381    fn presented_still_current(&self) -> Result<(), LineageRefusal> {
382        let mut not_presented = false;
383        for binding in &self.presented {
384            if !binding.matches_request() {
385                return Err(LineageRefusal::SourceNoLongerAdmitted);
386            }
387            match binding.presentation() {
388                Presentation::Current => {}
389                Presentation::OtherPolicy => return Err(LineageRefusal::SourceNoLongerAdmitted),
390                Presentation::None => not_presented = true,
391            }
392        }
393        if not_presented {
394            return Err(LineageRefusal::SourceNotPresented);
395        }
396        Ok(())
397    }
398}
399
400/// The register key of a delivered text: BLAKE3 over its exact bytes.
401#[must_use]
402pub fn digest(delivered_text: &str) -> String {
403    blake3::hash(delivered_text.as_bytes()).to_hex().to_string()
404}
405
406/// Record that the text with this digest was released with this lineage.
407///
408/// Call inside the request's `REQUEST_PROJECT` scope, at the release point,
409/// with the digest of the exact bytes delivered. Returns whether the lineage
410/// is now on record; `false` leaves the text's lineage unknown (malformed
411/// digest, incomplete lineage, a source that does not hold now — including
412/// every restricted source —, no request project, a bound, or the lock).
413pub fn record_lineage(delivered_text_digest: &str, lineage: &Lineage) -> bool {
414    lineage.still_admitted().is_ok()
415        && store_at(delivered_text_digest, lineage, None, register::now())
416}
417
418/// Record a text that was DERIVED from text with a known lineage (compressed,
419/// or cut down to a stub that points at a recovery id) under that same
420/// lineage, so the derived bytes stay returnable exactly as long as their
421/// origin is. This is the one record a site other than an Engine release
422/// point may make. Preconditions, all of them the caller's:
423///
424/// 1. `lineage` came from [`lineage_of`] for the exact original block, in the
425///    same request, inside the same `REQUEST_PROJECT` scope;
426/// 2. the derivation used ONLY that block (plus fixed text of the deriving
427///    code itself, such as a recovery hint) — no other block, store or
428///    session text;
429/// 3. for a derivation from several blocks: the lineages of ALL of them,
430///    united with [`Lineage::join`]; if one block has no lineage, do not call.
431///
432/// Checked here: the lineage is complete and every source holds at this
433/// moment. Otherwise nothing is recorded and the derived text is unknown.
434pub fn record_derived(derived_text_digest: &str, lineage: &Lineage) -> bool {
435    record_lineage(derived_text_digest, lineage)
436}
437
438/// The lineage recorded for the text with this digest, for the request
439/// project in scope (without a request project: the only lineage, if there is
440/// exactly one). `None`: unknown — nothing on record, expired, incomplete.
441///
442/// The handle is returned whether or not its sources still hold — ask
443/// [`Lineage::still_admitted`] inside the request's project scope.
444#[must_use]
445pub fn lineage_of(delivered_text_digest: &str) -> Option<Lineage> {
446    lineage_at(delivered_text_digest, register::now())
447}
448
449/// Records the lineage of the text blocks actually delivered for one tool
450/// call — the one place that holds the conditions, for the MCP pipeline and
451/// the CLI alike: declared tool, no tool error, complete capture, every
452/// source admitted now, nothing restricted. Anything else records nothing
453/// (unknown stays unknown). Returns the number of blocks recorded.
454///
455/// `declares_every_source` is the tool's `publishes_every_source` for this
456/// call (`tools::registered::ctx_call::declares_every_source`) AND the
457/// caller's own statement that it
458/// mixed no text without source authority (session-derived context, hints
459/// from derived stores) into the delivered blocks; a caller that did passes
460/// `false`. `capture` is what `authority::capture` / `capture_sync` returned
461/// around the handler. `delivered_blocks` are the exact delivered bytes,
462/// after all changes. Call inside the request's `REQUEST_PROJECT` scope.
463pub fn record_tool_delivery(
464    declares_every_source: bool,
465    is_error: bool,
466    capture: Option<ArchiveAuthority>,
467    delivered_blocks: &[&str],
468) -> usize {
469    record_tool_delivery_for_call(
470        declares_every_source,
471        is_error,
472        capture,
473        delivered_blocks,
474        None,
475    )
476}
477
478/// A call id as this register records and looks it up: 1 to
479/// `MAX_CALL_ID_BYTES` bytes of `[A-Za-z0-9_-]`. Anything else is no id.
480#[must_use]
481pub fn valid_call_id(call_id: &str) -> bool {
482    (1..=MAX_CALL_ID_BYTES).contains(&call_id.len())
483        && call_id
484            .bytes()
485            .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'-')
486}
487
488/// [`record_tool_delivery`] for a call whose host named its own id for the
489/// call (the MCP pipeline). What is recorded, and when, is exactly the same;
490/// a valid `call_id` is additionally noted on each recorded block's entry of
491/// the request project, so [`delivered_to_call`] can later tell that THIS
492/// call was answered with THESE bytes by a process writing this store. An
493/// absent or malformed id notes nothing. The id is metadata of the host: it
494/// is persisted only in the register files and appears in no tool output,
495/// receipt, log line or refusal.
496pub fn record_tool_delivery_for_call(
497    declares_every_source: bool,
498    is_error: bool,
499    capture: Option<ArchiveAuthority>,
500    delivered_blocks: &[&str],
501    call_id: Option<&str>,
502) -> usize {
503    if !declares_every_source || is_error {
504        return 0;
505    }
506    let Some(authority) = capture.filter(|authority| !authority.is_incomplete()) else {
507        return 0;
508    };
509    let lineage = Lineage::of_files(authority);
510    // One admission pass for the call, not one per block.
511    if lineage.still_admitted().is_err() {
512        return 0;
513    }
514    let call_id = call_id.filter(|call_id| valid_call_id(call_id));
515    let now = register::now();
516    delivered_blocks
517        .iter()
518        .filter(|block| store_at(&digest(block), &lineage, call_id, now))
519        .count()
520}
521
522/// Whether the text with this digest is on record, for the request project in
523/// scope, as delivered for the tool call with this id. `true` only for a
524/// complete, unexpired entry of that project that lists exactly this id;
525/// without a request project, for a malformed id, and for every entry written
526/// without an id (including all written before ids were recorded): `false`.
527///
528/// This identifies a delivery; like the rest of the register it authorizes
529/// nothing — the lineage still has to hold ([`Lineage::still_admitted`]).
530#[must_use]
531pub fn delivered_to_call(delivered_text_digest: &str, call_id: &str) -> bool {
532    if !valid_call_id(call_id) {
533        return false;
534    }
535    let Some(project) = request_project() else {
536        return false;
537    };
538    REGISTER
539        .read::<Vec<Lineage>>(delivered_text_digest, register::now())
540        .is_some_and(|lineages| {
541            lineages.iter().any(|lineage| {
542                lineage.is_complete()
543                    && lineage.project() == Some(project.as_str())
544                    && lineage.calls.iter().any(|known| known == call_id)
545            })
546        })
547}
548
549/// For a caller that has verified `authority` in this very step (archive
550/// resolution re-admits every source before it returns content).
551pub(super) fn record_verified(delivered_text_digest: &str, authority: &ArchiveAuthority) -> bool {
552    store_at(
553        delivered_text_digest,
554        &Lineage::of_files(authority.clone()),
555        None,
556        register::now(),
557    )
558}
559
560fn request_project() -> Option<String> {
561    crate::core::policy::runtime::REQUEST_PROJECT
562        .try_with(|slot| slot.borrow().clone())
563        .ok()
564        .flatten()
565        .and_then(|path| crate::core::pathutil::canonicalize_secure(&path).ok())
566        .and_then(|path| path.to_str().map(str::to_owned))
567}
568
569fn lineage_at(digest: &str, now: i64) -> Option<Lineage> {
570    let mut lineages: Vec<Lineage> = REGISTER
571        .read::<Vec<Lineage>>(digest, now)?
572        .into_iter()
573        .filter(Lineage::is_complete)
574        .map(|mut lineage| {
575            // A handle names sources; which calls were answered, and under
576            // which presenter entries, stays here.
577            lineage.calls.clear();
578            lineage.presenters.clear();
579            lineage
580        })
581        .collect();
582    let scoped = crate::core::policy::runtime::REQUEST_PROJECT
583        .try_with(|slot| slot.borrow().is_some())
584        .unwrap_or(false);
585    if scoped {
586        let project = request_project()?;
587        return lineages
588            .into_iter()
589            .find(|lineage| lineage.project() == Some(project.as_str()));
590    }
591    (lineages.len() == 1).then(|| lineages.remove(0))
592}
593
594/// `call_id`: already validated by the caller ([`valid_call_id`]), or `None`.
595/// The ids of the stored entry are kept; those of `lineage` are never used.
596fn store_at(digest: &str, lineage: &Lineage, call_id: Option<&str>, now: i64) -> bool {
597    if !lineage.is_complete() {
598        return false;
599    }
600    REGISTER.update::<Vec<Lineage>>(digest, now, |known| {
601        let mut lineages = known.unwrap_or_default();
602        let position = lineages
603            .iter()
604            .position(|known| known.project() == lineage.project());
605        let entry = if let Some(position) = position {
606            // Identical text from a second source set: the union, never the
607            // weaker of the two. A union beyond a bound is kept as unknown —
608            // the text is then not attributed to one of its sets.
609            lineages[position].join(lineage.clone());
610            &mut lineages[position]
611        } else if lineages.len() < MAX_LINEAGES_PER_TEXT {
612            let mut fresh = lineage.clone();
613            fresh.calls.clear();
614            fresh.presenters.clear();
615            lineages.push(fresh);
616            lineages.last_mut()?
617        } else {
618            return None;
619        };
620        if let Some(call_id) = call_id {
621            entry.note_call(call_id);
622        }
623        entry.note_presenter();
624        Some(lineages)
625    })
626}
627
628#[cfg(test)]
629mod tests {
630    use std::path::{Path, PathBuf};
631
632    use super::*;
633    use crate::core::archive::presented::{self, record_presentation};
634    use crate::core::policy::runtime::{REQUEST_PROJECT, TestPolicyOverride};
635
636    const GOVERNED: &str = "[context_gateway]\nmode = \"governed\"\nclassification = \"warn\"\n";
637
638    fn isolated(config: &str) -> crate::core::data_dir::IsolatedDataDir {
639        let data = crate::core::data_dir::isolated_data_dir();
640        write_config(config);
641        data
642    }
643
644    fn write_config(config: &str) {
645        let dir = crate::core::paths::config_dir_read_only().expect("isolated config dir");
646        std::fs::create_dir_all(&dir).expect("config dir");
647        std::fs::write(dir.join("config.toml"), config).expect("config");
648    }
649
650    fn project() -> (tempfile::TempDir, PathBuf) {
651        let directory = tempfile::tempdir().expect("project");
652        let root = directory.path().canonicalize().expect("canonical root");
653        (directory, root)
654    }
655
656    fn scoped<T>(root: &Path, operation: impl FnOnce() -> T) -> T {
657        REQUEST_PROJECT.sync_scope(std::cell::RefCell::new(Some(root.to_path_buf())), operation)
658    }
659
660    /// The authority the admitted reader would publish for this file.
661    fn authority_of(root: &Path, name: &str, content: &str) -> ArchiveAuthority {
662        let path = root.join(name);
663        std::fs::write(&path, content).expect("source");
664        let admitted = crate::core::rooted_read::read_file_for_tool_rooted(
665            path.to_str().expect("utf-8 path"),
666            root.to_str().expect("utf-8 root"),
667            "ctx_read",
668        )
669        .expect("admitted read");
670        ArchiveAuthority::file(
671            root,
672            path.to_str().expect("utf-8 path"),
673            "ctx_read",
674            &admitted,
675        )
676        .expect("protection is active: the read is bound")
677    }
678
679    /// Record a file-only lineage.
680    fn record(delivered_text_digest: &str, authority: &ArchiveAuthority) -> bool {
681        record_lineage(delivered_text_digest, &Lineage::of_files(authority.clone()))
682    }
683
684    /// The file part of a file-only lineage; `None` for any other lineage.
685    fn lineage_for(delivered_text_digest: &str) -> Option<ArchiveAuthority> {
686        lineage_of(delivered_text_digest)
687            .filter(|lineage| lineage.presented.is_empty())
688            .and_then(|lineage| lineage.files)
689    }
690
691    fn presented_now(root: &Path, object_ref: &str) -> PresentedBinding {
692        PresentedBinding::admitted_now(root, object_ref, "source-one", "sha256:aa")
693            .expect("policy identity available")
694    }
695
696    /// A complete file lineage is recorded and found by the digest of the
697    /// exact bytes; the handle outlives its source and then refuses.
698    #[test]
699    fn a_complete_lineage_is_found_and_stops_admitting_when_its_source_is_gone() {
700        let _data = isolated(GOVERNED);
701        let _community = TestPolicyOverride::set(None);
702        let (_directory, root) = project();
703        scoped(&root, || {
704            let authority = authority_of(&root, "kept.rs", "pub fn kept() {}\n");
705            let delivered = digest("pub fn kept() {}\n");
706            assert!(lineage_of(&delivered).is_none(), "nothing on record yet");
707            assert!(record(&delivered, &authority));
708
709            let found = lineage_of(&delivered).expect("recorded lineage");
710            assert_eq!(found, Lineage::of_files(authority.clone()));
711            assert_eq!(found.still_admitted(), Ok(()));
712            assert_eq!(lineage_for(&delivered), Some(authority));
713            // Exact bytes: any re-wrapping is a different text.
714            assert!(lineage_of(&digest("pub fn kept() {}")).is_none());
715            assert!(lineage_of("not-a-digest").is_none());
716            assert_eq!(found.still_permitted(), Ok(()));
717
718            // An edit after the read (#2177): the bytes no longer match, so
719            // reuse is refused, but the source is still permitted — a sealed
720            // item derived from it may return.
721            std::fs::write(root.join("kept.rs"), "pub fn kept() { edited() }\n")
722                .expect("edit the source");
723            assert_eq!(
724                found.still_admitted(),
725                Err(LineageRefusal::SourceNoLongerAdmitted)
726            );
727            assert_eq!(found.still_permitted(), Ok(()));
728
729            // An edit that makes the source restricted (E3): no longer
730            // permitted, so nothing derived from it returns.
731            std::fs::write(root.join("kept.rs"), "*** TOP SECRET ***\nlaunch window\n")
732                .expect("restrict the source");
733            assert_eq!(
734                found.still_permitted(),
735                Err(LineageRefusal::SourceNoLongerAdmitted)
736            );
737
738            std::fs::remove_file(root.join("kept.rs")).expect("delete the source");
739            let found = lineage_of(&delivered).expect("the handle is still on record");
740            assert_eq!(
741                found.still_admitted(),
742                Err(LineageRefusal::SourceNoLongerAdmitted)
743            );
744            assert_eq!(
745                found.still_permitted(),
746                Err(LineageRefusal::SourceNoLongerAdmitted)
747            );
748        });
749    }
750
751    /// Unknown stays unknown: an incomplete capture, a restricted source
752    /// (E3), an empty lineage and a record outside any request project leave
753    /// no entry. The shared tool-delivery entry applies the same conditions.
754    #[tokio::test]
755    async fn incomplete_and_restricted_lineages_are_not_recorded() {
756        let _data = isolated(GOVERNED);
757        let _community = TestPolicyOverride::set(None);
758        let (_directory, root) = project();
759
760        let ((), incomplete) = super::super::authority::capture(async {
761            assert!(
762                ArchiveAuthority::file(&root, "/outside/of/the/project.rs", "ctx_read", "x")
763                    .is_none()
764            );
765        })
766        .await;
767        let incomplete = incomplete.expect("an unbound read is reported");
768        assert!(incomplete.is_incomplete());
769
770        scoped(&root, || {
771            let unknown = digest("derived from an unbound source");
772            assert!(!record(&unknown, &incomplete));
773            assert!(!record_verified(&unknown, &incomplete));
774            assert!(!record_lineage(
775                &unknown,
776                &Lineage::of_presented(Vec::new())
777            ));
778            assert_eq!(
779                Lineage::of_files(incomplete.clone()).still_admitted(),
780                Err(LineageRefusal::Unknown)
781            );
782            assert!(lineage_of(&unknown).is_none());
783
784            // Delivered under `classification = "warn"`, but restricted: not
785            // storable, so it has no reusable lineage.
786            let restricted = authority_of(&root, "plan.md", "*** TOP SECRET ***\nlaunch window\n");
787            let delivered = digest("launch window");
788            assert!(!record(&delivered, &restricted));
789            assert_eq!(
790                record_tool_delivery(true, false, Some(restricted), &["launch window"]),
791                0
792            );
793            assert!(lineage_of(&delivered).is_none());
794
795            // The shared entry: each condition by itself records nothing.
796            let clean = authority_of(&root, "tool.rs", "pub fn tool() {}\n");
797            let block = "pub fn tool() {}\n";
798            assert_eq!(
799                record_tool_delivery(false, false, Some(clean.clone()), &[block]),
800                0
801            );
802            assert_eq!(
803                record_tool_delivery(true, true, Some(clean.clone()), &[block]),
804                0
805            );
806            assert_eq!(record_tool_delivery(true, false, None, &[block]), 0);
807            assert_eq!(
808                record_tool_delivery(true, false, Some(incomplete.clone()), &[block]),
809                0
810            );
811            assert!(lineage_of(&digest(block)).is_none());
812            assert_eq!(
813                record_tool_delivery(true, false, Some(clean), &[block, "second block"]),
814                2
815            );
816            assert!(lineage_of(&digest(block)).is_some());
817            assert!(lineage_of(&digest("second block")).is_some());
818        });
819
820        let clean = scoped(&root, || {
821            authority_of(&root, "clean.rs", "pub fn clean() {}\n")
822        });
823        let delivered = digest("pub fn clean() {}\n");
824        assert!(
825            !record(&delivered, &clean),
826            "no request project: nothing verifies the lineage"
827        );
828        assert!(lineage_of(&delivered).is_none());
829    }
830
831    /// Identical text from a second source set is admitted only while both
832    /// sets hold; a re-read of the same source refreshes its digest; another
833    /// project's lineage is kept apart.
834    #[test]
835    fn identical_text_from_two_source_sets_needs_both() {
836        let _data = isolated(GOVERNED);
837        let _community = TestPolicyOverride::set(None);
838        let (_directory, root) = project();
839        let (_other_directory, other_root) = project();
840        let delivered = digest("pub fn shared() {}\n");
841        scoped(&root, || {
842            let first = authority_of(&root, "a.rs", "pub fn shared() {}\n");
843            let second = authority_of(&root, "b.rs", "pub fn shared() {}\n");
844            assert!(record(&delivered, &first));
845            assert!(record(&delivered, &second));
846            let both = lineage_of(&delivered).expect("united lineage");
847            assert_ne!(both, Lineage::of_files(first.clone()));
848            assert_ne!(both, Lineage::of_files(second));
849            assert_eq!(both.still_admitted(), Ok(()));
850
851            // The same source in a newer state delivers the same text again.
852            let refreshed = authority_of(&root, "a.rs", "pub fn shared() {}\n// note\n");
853            assert!(record(&delivered, &refreshed));
854            let both = lineage_of(&delivered).expect("united lineage");
855            assert_eq!(both.still_admitted(), Ok(()), "superseded digest replaced");
856
857            std::fs::remove_file(root.join("b.rs")).expect("delete one source");
858            let both = lineage_of(&delivered).expect("still on record");
859            assert_eq!(
860                both.still_admitted(),
861                Err(LineageRefusal::SourceNoLongerAdmitted),
862                "one remaining set authorizes nothing"
863            );
864            assert!(refreshed.still_admitted());
865        });
866        scoped(&other_root, || {
867            assert!(
868                lineage_of(&delivered).is_none(),
869                "not this project's lineage"
870            );
871            let foreign = authority_of(&other_root, "c.rs", "pub fn shared() {}\n");
872            assert!(record(&delivered, &foreign));
873            assert_eq!(lineage_for(&delivered), Some(foreign));
874        });
875        assert!(
876            lineage_of(&delivered).is_none(),
877            "two projects and none in scope: ambiguous"
878        );
879    }
880
881    /// Caller-supplied sources: a binding holds only while it is presented
882    /// and admitted under the current policy; kinds are never mixed up; a
883    /// block from a file and a presented source needs both.
884    #[test]
885    fn a_presented_lineage_needs_a_current_presentation_and_a_mixed_one_needs_both() {
886        let _data = isolated(GOVERNED);
887        let _community = TestPolicyOverride::set(None);
888        let (_directory, root) = project();
889        let (_other_directory, other_root) = project();
890        scoped(&root, || {
891            let binding = presented_now(&root, "ref-quartz");
892            let lineage = Lineage::of_presented(vec![binding.clone()]);
893            let delivered = digest("prepared context");
894            assert_eq!(
895                lineage.still_admitted(),
896                Err(LineageRefusal::SourceNotPresented),
897                "bound, but no presentation on record"
898            );
899            assert!(!record_lineage(&delivered, &lineage), "nothing recorded");
900
901            assert!(record_presentation(&binding));
902            assert_eq!(lineage.still_admitted(), Ok(()));
903            assert!(record_lineage(&delivered, &lineage));
904            assert_eq!(lineage_of(&delivered), Some(lineage.clone()));
905            assert!(
906                lineage_for(&delivered).is_none(),
907                "a presented lineage is never passed off as a file lineage"
908            );
909
910            // Same object reference, other content: not the same binding.
911            let other_content =
912                PresentedBinding::admitted_now(&root, "ref-quartz", "source-one", "sha256:bb")
913                    .expect("binding");
914            assert_eq!(
915                Lineage::of_presented(vec![other_content]).still_admitted(),
916                Err(LineageRefusal::SourceNotPresented)
917            );
918
919            // The policy changes: the presentation on record was admitted
920            // under another policy identity.
921            write_config("[context_gateway]\nmode = \"sovereign\"\n");
922            assert_eq!(
923                lineage.still_admitted(),
924                Err(LineageRefusal::SourceNoLongerAdmitted)
925            );
926            // Presented and admitted again under the new policy: the text
927            // delivered under the OLD policy stays refused until it is
928            // delivered and recorded again under the current one.
929            let renewed = presented_now(&root, "ref-quartz");
930            assert!(record_presentation(&renewed));
931            assert_eq!(
932                lineage.still_admitted(),
933                Err(LineageRefusal::SourceNoLongerAdmitted)
934            );
935            let lineage = Lineage::of_presented(vec![renewed]);
936            assert!(record_lineage(&delivered, &lineage));
937            assert_eq!(
938                lineage_of(&delivered).expect("on record").still_admitted(),
939                Ok(())
940            );
941
942            // Mixed block: the file part and the presented part must hold.
943            let file = authority_of(&root, "kept.rs", "pub fn kept() {}\n");
944            let mut mixed = Lineage::of_files(file);
945            mixed.join(lineage.clone());
946            assert!(mixed.is_complete());
947            assert_eq!(mixed.still_admitted(), Ok(()));
948            let mixed_text = digest("file part and prepared part");
949            assert!(record_lineage(&mixed_text, &mixed));
950            std::fs::remove_file(root.join("kept.rs")).expect("delete the file source");
951            assert_eq!(
952                lineage_of(&mixed_text).expect("on record").still_admitted(),
953                Err(LineageRefusal::SourceNoLongerAdmitted)
954            );
955
956            // Parts of two projects are no lineage.
957            let mut crossed = lineage.clone();
958            crossed.join(Lineage::of_presented(vec![presented_now(
959                &other_root,
960                "ref-quartz",
961            )]));
962            assert!(!crossed.is_complete());
963            assert_eq!(crossed.still_admitted(), Err(LineageRefusal::Unknown));
964        });
965        // Another project's request: the binding is not its own.
966        scoped(&other_root, || {
967            let foreign = Lineage::of_presented(vec![presented_now(&root, "ref-quartz")]);
968            assert_eq!(
969                foreign.still_admitted(),
970                Err(LineageRefusal::SourceNoLongerAdmitted)
971            );
972        });
973        // No refusal names anything.
974        for refusal in [
975            LineageRefusal::Unknown,
976            LineageRefusal::SourceNotPresented,
977            LineageRefusal::SourceNoLongerAdmitted,
978        ] {
979            let text = refusal.to_string();
980            assert!(
981                text.starts_with("lineage.") && !text.contains("quartz"),
982                "{text}"
983            );
984        }
985        assert_eq!(presented::VALIDITY_SECONDS, 600);
986    }
987
988    /// What another process sees: only the persisted, owner-only shard — no
989    /// in-memory state — read back through the public API. Age, entry and
990    /// size bounds hold.
991    #[test]
992    fn the_persisted_register_is_bounded_private_and_readable_by_another_process() {
993        let _data = isolated(GOVERNED);
994        let _community = TestPolicyOverride::set(None);
995        let (_directory, root) = project();
996        scoped(&root, || {
997            let authority = authority_of(&root, "kept.rs", "pub fn kept() {}\n");
998            let lineage = Lineage::of_files(authority);
999            let delivered = digest("pub fn kept() {}\n");
1000            assert!(record_lineage(&delivered, &lineage));
1001
1002            // The register has no in-memory state: this lookup decodes the
1003            // file exactly as a second process would.
1004            let shard = REGISTER.shard_path(&delivered);
1005            let raw = std::fs::read_to_string(&shard).expect("persisted shard");
1006            assert!(!raw.contains("pub fn kept"), "no content in the register");
1007            #[cfg(unix)]
1008            {
1009                use std::os::unix::fs::PermissionsExt;
1010                let mode = std::fs::metadata(&shard)
1011                    .expect("shard")
1012                    .permissions()
1013                    .mode();
1014                assert_eq!(mode & 0o777, 0o600, "owner-only");
1015            }
1016            assert_eq!(lineage_of(&delivered), Some(lineage.clone()));
1017
1018            // Age: an expired record is not returned.
1019            let start = register::now();
1020            assert!(lineage_at(&delivered, start + MAX_AGE_SECONDS + 1).is_none());
1021
1022            // Entries: one shard never holds more than its bound; the oldest
1023            // record goes first. Digests share the shard prefix "ab".
1024            let in_shard = |index: usize| format!("ab{index:062x}");
1025            for index in 0..REGISTER.shard_entries + 3 {
1026                let at = start + i64::try_from(index).expect("small index");
1027                assert!(store_at(&in_shard(index), &lineage, None, at));
1028            }
1029            let later = start + 1_000;
1030            let newest = in_shard(REGISTER.shard_entries + 2);
1031            assert!(lineage_at(&in_shard(0), later).is_none(), "oldest evicted");
1032            assert!(lineage_at(&in_shard(3), later).is_some());
1033            assert!(lineage_at(&newest, later).is_some());
1034
1035            // Size and format: an oversized or foreign shard is not read.
1036            let shard = REGISTER.shard_path(&newest);
1037            let too_large = usize::try_from(register::MAX_SHARD_BYTES).expect("fits usize") + 1;
1038            std::fs::write(&shard, " ".repeat(too_large)).expect("write");
1039            assert!(lineage_at(&newest, later).is_none());
1040            std::fs::write(&shard, "{\"version\":2,\"entries\":{}}").expect("write");
1041            assert!(lineage_at(&newest, later).is_none());
1042        });
1043    }
1044
1045    /// A delivery recorded with the host's call id answers for exactly that
1046    /// id, in exactly that project; the id never rides on a lineage handle,
1047    /// is not lost when the same text is recorded again without one, and is
1048    /// not copied to a derived text.
1049    #[test]
1050    fn a_delivery_is_on_record_for_the_call_it_answered_and_for_no_other() {
1051        let _data = isolated(GOVERNED);
1052        let _community = TestPolicyOverride::set(None);
1053        let (_directory, root) = project();
1054        let (_other_directory, other_root) = project();
1055        let block = "pub fn kept() {}\n";
1056        let delivered = digest(block);
1057        scoped(&root, || {
1058            let authority = authority_of(&root, "kept.rs", block);
1059            assert!(!delivered_to_call(&delivered, "toolu_X"), "nothing yet");
1060            assert_eq!(
1061                record_tool_delivery_for_call(
1062                    true,
1063                    false,
1064                    Some(authority.clone()),
1065                    &[block],
1066                    Some("toolu_X"),
1067                ),
1068                1
1069            );
1070            assert!(delivered_to_call(&delivered, "toolu_X"));
1071            assert!(!delivered_to_call(&delivered, "toolu_Y"));
1072            assert!(!delivered_to_call(&digest("other text"), "toolu_X"));
1073            assert!(!delivered_to_call("not-a-digest", "toolu_X"));
1074
1075            // The handle names sources only.
1076            let handle = lineage_of(&delivered).expect("on record");
1077            assert!(handle.calls.is_empty());
1078            assert_eq!(handle, Lineage::of_files(authority.clone()));
1079
1080            // The same bytes again without an id (CLI, another caller), and
1081            // through the plain lineage record: X stays, nothing is added.
1082            assert_eq!(
1083                record_tool_delivery(true, false, Some(authority.clone()), &[block]),
1084                1
1085            );
1086            assert!(record(&delivered, &authority));
1087            assert!(delivered_to_call(&delivered, "toolu_X"));
1088            assert!(!delivered_to_call(&delivered, ""));
1089
1090            // A malformed id is not recorded; the delivery itself is.
1091            let second = "pub fn second() {}\n";
1092            let second_authority = authority_of(&root, "second.rs", second);
1093            let too_long = "x".repeat(MAX_CALL_ID_BYTES + 1);
1094            for malformed in ["", "has space", "slash/id", too_long.as_str()] {
1095                assert!(!valid_call_id(malformed), "{malformed:?}");
1096                assert_eq!(
1097                    record_tool_delivery_for_call(
1098                        true,
1099                        false,
1100                        Some(second_authority.clone()),
1101                        &[second],
1102                        Some(malformed),
1103                    ),
1104                    1
1105                );
1106                assert!(!delivered_to_call(&digest(second), malformed));
1107            }
1108            assert!(valid_call_id(&"x".repeat(MAX_CALL_ID_BYTES)));
1109            assert!(valid_call_id("call_Ab-9"));
1110
1111            // A text derived from the block is not a delivery of that call.
1112            let derived = digest("[stub of the block]");
1113            assert!(record_derived(&derived, &handle));
1114            assert!(lineage_of(&derived).is_some());
1115            assert!(!delivered_to_call(&derived, "toolu_X"));
1116
1117            // A refused delivery notes no id either.
1118            let refused = "pub fn refused() {}\n";
1119            assert_eq!(
1120                record_tool_delivery_for_call(
1121                    true,
1122                    true,
1123                    Some(authority.clone()),
1124                    &[refused],
1125                    Some("toolu_X"),
1126                ),
1127                0
1128            );
1129            assert!(!delivered_to_call(&digest(refused), "toolu_X"));
1130        });
1131        // Another project: the same bytes there were not delivered for X.
1132        scoped(&other_root, || {
1133            assert!(!delivered_to_call(&delivered, "toolu_X"));
1134            let foreign = authority_of(&other_root, "kept.rs", block);
1135            assert_eq!(
1136                record_tool_delivery_for_call(true, false, Some(foreign), &[block], Some("Z")),
1137                1
1138            );
1139            assert!(delivered_to_call(&delivered, "Z"));
1140            assert!(!delivered_to_call(&delivered, "toolu_X"));
1141        });
1142        scoped(&root, || {
1143            assert!(delivered_to_call(&delivered, "toolu_X"));
1144            assert!(!delivered_to_call(&delivered, "Z"));
1145        });
1146        assert!(
1147            !delivered_to_call(&delivered, "toolu_X"),
1148            "no request project: no answer"
1149        );
1150    }
1151
1152    /// An entry written before call ids existed loads unchanged and answers
1153    /// for no call; the bound evicts the oldest id and a repeated id counts
1154    /// as the newest.
1155    #[test]
1156    fn an_entry_without_call_ids_answers_for_none_and_the_bound_evicts_the_oldest() {
1157        let _data = isolated(GOVERNED);
1158        let _community = TestPolicyOverride::set(None);
1159        let (_directory, root) = project();
1160        scoped(&root, || {
1161            let block = "pub fn kept() {}\n";
1162            let authority = authority_of(&root, "kept.rs", block);
1163            let delivered = digest(block);
1164            assert!(record(&delivered, &authority));
1165
1166            // The persisted form of an entry without ids has no such field:
1167            // it is byte-for-byte the form written before the field existed.
1168            let shard = REGISTER.shard_path(&delivered);
1169            let raw = std::fs::read_to_string(&shard).expect("persisted shard");
1170            assert!(!raw.contains("\"calls\""), "{raw}");
1171            assert_eq!(
1172                lineage_of(&delivered),
1173                Some(Lineage::of_files(authority.clone()))
1174            );
1175            assert!(!delivered_to_call(&delivered, "toolu_X"));
1176
1177            let record_for = |call_id: &str| {
1178                record_tool_delivery_for_call(
1179                    true,
1180                    false,
1181                    Some(authority.clone()),
1182                    &[block],
1183                    Some(call_id),
1184                )
1185            };
1186            for index in 0..MAX_CALLS_PER_LINEAGE {
1187                assert_eq!(record_for(&format!("call_{index}")), 1);
1188            }
1189            assert!(delivered_to_call(&delivered, "call_0"));
1190            // Repeating the oldest id makes it the newest …
1191            assert_eq!(record_for("call_0"), 1);
1192            // … so the next new id evicts `call_1`.
1193            assert_eq!(record_for("call_new"), 1);
1194            assert!(delivered_to_call(&delivered, "call_0"));
1195            assert!(!delivered_to_call(&delivered, "call_1"), "oldest evicted");
1196            assert!(delivered_to_call(&delivered, "call_2"));
1197            assert!(delivered_to_call(&delivered, "call_new"));
1198            let raw = std::fs::read_to_string(&shard).expect("persisted shard");
1199            assert!(raw.contains("\"calls\""));
1200            assert!(!raw.contains("pub fn kept"), "no content in the register");
1201        });
1202    }
1203}