1use serde::{Deserialize, Serialize};
46
47use super::authority::ArchiveAuthority;
48use super::presented::{Presentation, PresentedBinding};
49use super::register::{self, Register};
50
51const MAX_LINEAGES_PER_TEXT: usize = 4;
53const MAX_AGE_SECONDS: i64 = 24 * 60 * 60;
55const MAX_PRESENTED: usize = 64;
58const MAX_CALLS_PER_LINEAGE: usize = 32;
60const MAX_CALL_ID_BYTES: usize = 128;
62const 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct Lineage {
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 files: Option<ArchiveAuthority>,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
80 presented: Vec<PresentedBinding>,
81 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
84 incomplete: bool,
85 #[serde(default, skip_serializing_if = "Vec::is_empty")]
90 calls: Vec<String>,
91 #[serde(default, skip_serializing_if = "Vec::is_empty")]
100 presenters: Vec<String>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum LineageRefusal {
107 Unknown,
109 SourceNotPresented,
111 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 #[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 #[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 *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 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 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 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 #[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 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 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 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 #[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 #[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 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 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 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#[must_use]
402pub fn digest(delivered_text: &str) -> String {
403 blake3::hash(delivered_text.as_bytes()).to_hex().to_string()
404}
405
406pub 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
418pub fn record_derived(derived_text_digest: &str, lineage: &Lineage) -> bool {
435 record_lineage(derived_text_digest, lineage)
436}
437
438#[must_use]
445pub fn lineage_of(delivered_text_digest: &str) -> Option<Lineage> {
446 lineage_at(delivered_text_digest, register::now())
447}
448
449pub 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#[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
488pub 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 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#[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
549pub(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 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
594fn 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 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 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 fn record(delivered_text_digest: &str, authority: &ArchiveAuthority) -> bool {
681 record_lineage(delivered_text_digest, &Lineage::of_files(authority.clone()))
682 }
683
684 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 #[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 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 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 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 #[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 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 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 #[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 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 #[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 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 write_config("[context_gateway]\nmode = \"sovereign\"\n");
922 assert_eq!(
923 lineage.still_admitted(),
924 Err(LineageRefusal::SourceNoLongerAdmitted)
925 );
926 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 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 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 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 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 #[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 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 let start = register::now();
1020 assert!(lineage_at(&delivered, start + MAX_AGE_SECONDS + 1).is_none());
1021
1022 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 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 #[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 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 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 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 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 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 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 #[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 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 assert_eq!(record_for("call_0"), 1);
1192 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}