1use crate::layer::LayerId;
20use crate::manifest::{LayerManifest, ManifestError};
21use crate::pin::Pin;
22use crate::rollback::{HighWaterMarks, RollbackError, RollbackVerdict};
23use crate::source::{LayerRef, LayerSource, SourceError};
24use crate::store::{Store, StoreError, manifest_digest};
25
26pub trait ManifestVerifier {
33 fn verify(&self, fetched_bytes: &[u8]) -> Result<Vec<u8>, VerifyError>;
34}
35
36#[derive(Debug, thiserror::Error)]
37#[error("manifest signature verification failed: {0}")]
38pub struct VerifyError(pub String);
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct InstallOutcome {
43 pub digest: String,
44 pub layer: LayerId,
45 pub counter: u64,
46 pub staleness_days: Option<i64>,
49 pub index_high_water: Option<u64>,
56 pub attestations_carried: usize,
60 pub attestation_note: Option<String>,
66}
67
68#[derive(Debug, thiserror::Error)]
69pub enum InstallError {
70 #[error(transparent)]
71 Index(#[from] crate::lineindex::IndexError),
72 #[error(transparent)]
73 Source(#[from] SourceError),
74 #[error(transparent)]
75 Verify(#[from] VerifyError),
76 #[error(transparent)]
77 Manifest(#[from] ManifestError),
78 #[error(
79 "source returned manifest {got} where the pin demands {pinned} — refusing (the digest is the artifact)"
80 )]
81 DigestMismatch { pinned: String, got: String },
82 #[error("manifest is for layer {got}, the pin names {pinned} — refusing")]
83 LayerMismatch { pinned: String, got: String },
84 #[error("manifest is on channel '{got}', the pin selects '{pinned}' — refusing")]
85 ChannelMismatch { pinned: String, got: String },
86 #[error(
87 "rollback refused: layer presents counter {presented} but the {line} line's high-water mark is {high_water} — a stale, validly-signed layer cannot be passed off as current"
88 )]
89 Rollback {
90 line: String,
91 presented: u64,
92 high_water: u64,
93 },
94 #[error(
95 "layer on line {line} presents counter {presented}, below the floor of {floor} this \
96 realm signs for the line — refusing.\n\n\
97 This machine has never installed from {line}, so it has no history to compare against. \
98 That is the one moment anti-rollback protects nobody, and the moment it is worth \
99 attacking: a fresh checkout, a new CI runner, a new laptop are all first contacts. The \
100 realm states a floor in its signed line-status so a consumer with no history still has \
101 one.\n\n\
102 Either the pin names a layer the realm has withdrawn from first-contact use, or \
103 something served an old signed layer to a new machine. Both are worth knowing before \
104 installing."
105 )]
106 BelowFloor {
107 line: String,
108 presented: u64,
109 floor: u64,
110 },
111 #[error("blob {digest} fetched for tool '{tool}' does not match its signed digest — refusing")]
112 BlobDigestMismatch { tool: String, digest: String },
113 #[error("manifest entry {digest} is missing the eu.pulseengine.tool annotation")]
114 UnnamedEntry { digest: String },
115 #[error(
116 "layer {layer} carries no entry for platform {platform} — refusing to install a \
117 wrong-architecture toolchain; use --platform only if you know why"
118 )]
119 NoPlatformEntry { layer: String, platform: String },
120 #[error(transparent)]
121 Store(#[from] StoreError),
122 #[error(transparent)]
123 State(#[from] RollbackError),
124}
125
126pub struct InstallPolicy<'a> {
129 pub now: &'a str,
131 pub staleness_threshold_days: u32,
132 pub platform: &'a str,
135 pub index: Option<crate::lineindex::IndexPolicy<'a>>,
139}
140
141pub fn install(
142 pin: &Pin,
143 source: &dyn LayerSource,
144 verifier: &dyn ManifestVerifier,
145 store: &Store,
146 marks: &mut HighWaterMarks,
147 policy: &InstallPolicy<'_>,
148) -> Result<InstallOutcome, InstallError> {
149 let layer_ref = match &pin.digest {
151 Some(digest) => LayerRef::Digest(digest.clone()),
152 None => LayerRef::Name(pin.layer.clone()),
153 };
154 let fetched = source.fetch_manifest(&layer_ref)?;
155
156 let bytes = verifier.verify(&fetched)?;
160
161 let manifest = LayerManifest::parse(&bytes)?;
163 let digest = manifest_digest(&bytes);
164 if let Some(pinned) = &pin.digest
165 && &digest != pinned
166 {
167 return Err(InstallError::DigestMismatch {
168 pinned: pinned.clone(),
169 got: digest,
170 });
171 }
172 if manifest.layer != pin.layer {
173 return Err(InstallError::LayerMismatch {
174 pinned: pin.layer.to_string(),
175 got: manifest.layer.to_string(),
176 });
177 }
178 let pinned_channel = match pin.channel {
179 crate::pin::Channel::Qualified => "qualified",
180 crate::pin::Channel::Rolling => "rolling",
181 };
182 if manifest.channel != pinned_channel {
183 return Err(InstallError::ChannelMismatch {
184 pinned: pinned_channel.to_string(),
185 got: manifest.channel.clone(),
186 });
187 }
188
189 let line = manifest.layer.line();
195 let line_str = line.to_string();
196 let index_cache = crate::lineindex::IndexCache::at_root(store.root());
197 let mut index_high_water: Option<u64> = None;
198 let mut accepted_index: Option<(crate::lineindex::LineIndex, Vec<u8>)> = None;
202 if let Some(index_policy) = &policy.index {
203 let envelope = source.fetch_line_index(&line_str)?;
204 let served = source.served_layers(&line_str)?;
205 let cached = index_cache.load(&line_str)?;
210 let verified = crate::lineindex::check(
211 &line_str,
212 envelope.as_deref(),
213 served.as_deref(),
214 cached.as_ref(),
215 index_policy,
216 )?;
217 index_high_water = verified.as_ref().and_then(|i| i.high_water());
218 if let (Some(doc), Some(bytes)) = (verified, envelope) {
219 accepted_index = Some((doc, bytes));
220 }
221 }
222
223 let mut first_contact_floor: Option<u64> = None;
239 if marks.mark(line).is_none()
240 && let Some(index_policy) = &policy.index
241 && let Some(bytes) = source.fetch_line_status(&layer_ref)?
242 && let Ok(doc) =
243 crate::linestatus::LineStatus::verify_and_parse(&bytes, index_policy.root_public_key)
244 && doc.line == line_str
245 {
246 first_contact_floor = doc.min_counter;
247 }
248
249 match marks.check_with_floor(&manifest, first_contact_floor) {
251 RollbackVerdict::Rollback {
252 line,
253 presented,
254 high_water,
255 } => {
256 return Err(InstallError::Rollback {
257 line,
258 presented,
259 high_water,
260 });
261 }
262 RollbackVerdict::BelowFloor {
263 line,
264 presented,
265 floor,
266 } => {
267 return Err(InstallError::BelowFloor {
268 line,
269 presented,
270 floor,
271 });
272 }
273 RollbackVerdict::Accept => {}
274 }
275
276 struct Fetched {
283 name: String,
284 version: Option<String>,
285 dispatchable: bool,
286 bytes: Vec<u8>,
287 }
288 let mut tools: Vec<Fetched> = Vec::new();
289 let mut matched = 0usize;
290 for entry in &manifest.entries {
291 if !crate::platform::entry_matches(
292 entry
293 .annotations
294 .get(crate::platform::ANN_PLATFORM)
295 .map(String::as_str),
296 policy.platform,
297 ) {
298 continue;
299 }
300 if entry.kind() == Ok(crate::kind::PayloadKind::Layer) {
305 continue;
306 }
307 matched += 1;
308 let tool = entry
309 .annotations
310 .get("eu.pulseengine.tool")
311 .ok_or_else(|| InstallError::UnnamedEntry {
312 digest: entry.digest.clone(),
313 })?
314 .clone();
315 let blob = source.fetch_blob(&entry.digest)?;
316 if manifest_digest(&blob) != entry.digest {
317 return Err(InstallError::BlobDigestMismatch {
318 tool,
319 digest: entry.digest.clone(),
320 });
321 }
322 tools.push(Fetched {
323 name: tool,
324 version: crate::store::entry_version(entry).map(str::to_string),
325 dispatchable: crate::store::entry_is_dispatchable(entry),
326 bytes: blob,
327 });
328 }
329
330 if matched == 0 && !manifest.entries.is_empty() {
333 return Err(InstallError::NoPlatformEntry {
334 layer: manifest.layer.to_string(),
335 platform: policy.platform.to_string(),
336 });
337 }
338
339 let payloads: Vec<crate::store::Payload<'_>> = tools
341 .iter()
342 .map(|t| crate::store::Payload {
343 name: t.name.as_str(),
344 version: t.version.as_deref(),
345 dispatchable: t.dispatchable,
346 bytes: t.bytes.as_slice(),
347 })
348 .collect();
349 let stored_digest = store.lay_down_payloads(&bytes, &payloads)?;
350 debug_assert_eq!(stored_digest, digest);
351
352 if fetched != bytes
356 && let Some(entry) = store.get(&digest)?
357 {
358 let path = entry.root.join(crate::reverify::ENVELOPE_FILE);
359 std::fs::write(&path, &fetched).map_err(|source| StoreError::Io {
360 path: path.display().to_string(),
361 source,
362 })?;
363 }
364
365 let mut attestations_carried = 0usize;
372 let mut attestation_note = None;
373 if let Some(entry) = store.get(&digest)? {
374 match crate::attestcarry::carry_from_source(source, &layer_ref, &entry.root) {
375 Ok(n) => attestations_carried = n,
376 Err(e) => attestation_note = Some(e.to_string()),
382 }
383 }
384
385 marks.advance(&manifest)?;
388 if let Some((doc, bytes)) = &accepted_index {
389 index_cache.update(&line_str, bytes, doc)?;
390 }
391
392 let staleness_days = crate::rollback::staleness_warning(
393 &manifest.issued_at,
394 policy.now,
395 policy.staleness_threshold_days,
396 );
397 Ok(InstallOutcome {
398 digest,
399 layer: manifest.layer.clone(),
400 counter: manifest.counter,
401 staleness_days,
402 index_high_water,
403 attestations_carried,
404 attestation_note,
405 })
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::manifest::fixtures::manifest_with_tools;
412 use crate::pin::Pin;
413 use crate::rollback::HighWaterMarks;
414 use crate::source::{DirSource, MemorySource};
415
416 struct AcceptAll;
417 impl ManifestVerifier for AcceptAll {
418 fn verify(&self, fetched: &[u8]) -> Result<Vec<u8>, VerifyError> {
419 Ok(fetched.to_vec())
420 }
421 }
422
423 struct RejectAll;
424 impl ManifestVerifier for RejectAll {
425 fn verify(&self, _: &[u8]) -> Result<Vec<u8>, VerifyError> {
426 Err(VerifyError("untrusted signature (test)".into()))
427 }
428 }
429
430 fn pin(layer: &str) -> Pin {
431 Pin::parse(
432 &format!(
433 "manifest-version = 1\n[toolchain]\nchannel = \"qualified\"\nlayer = \"{layer}\"\n"
434 ),
435 "varve.toml",
436 )
437 .unwrap()
438 }
439
440 fn policy() -> InstallPolicy<'static> {
441 InstallPolicy {
442 index: None,
443 now: "2026-08-07T00:00:00Z",
444 staleness_threshold_days: 90,
445 platform: "test-platform",
446 }
447 }
448
449 fn july() -> (Vec<u8>, Vec<(String, Vec<u8>)>) {
451 let synth = b"july-synth".to_vec();
452 let rivet = b"july-rivet".to_vec();
453 let blobs = vec![
454 (manifest_digest(&synth), synth),
455 (manifest_digest(&rivet), rivet),
456 ];
457 let bytes = manifest_with_tools(
458 "2026.07.0",
459 "qualified",
460 1,
461 "2026-07-31T09:14:00Z",
462 &[("synth", &blobs[0].0), ("rivet", &blobs[1].0)],
463 );
464 (bytes, blobs)
465 }
466
467 fn memory_source(manifest: &[u8], blobs: &[(String, Vec<u8>)]) -> MemorySource {
468 let mut source = MemorySource::new().with_manifest(manifest);
469 for (digest, bytes) in blobs {
470 source = source.with_blob(digest, bytes);
471 }
472 source
473 }
474
475 fn setup() -> (tempfile::TempDir, Store, HighWaterMarks) {
476 let tmp = tempfile::tempdir().unwrap();
477 let root = tmp.path().join("varve-root");
478 let store = Store::at(&root);
479 let marks = HighWaterMarks::load(&root).unwrap();
480 (tmp, store, marks)
481 }
482
483 #[test]
485 fn installs_a_verified_layer_end_to_end() {
486 let (_tmp, store, mut marks) = setup();
487 let (bytes, blobs) = july();
488 let source = memory_source(&bytes, &blobs);
489 let outcome = install(
490 &pin("2026.07.0"),
491 &source,
492 &AcceptAll,
493 &store,
494 &mut marks,
495 &policy(),
496 )
497 .unwrap();
498 assert_eq!(outcome.layer.to_string(), "2026.07.0");
499 assert_eq!(outcome.digest, manifest_digest(&bytes));
500 let entry = store.get(&outcome.digest).unwrap().unwrap();
502 assert!(store.tool_path(&entry, "synth").is_some());
503 assert!(store.tool_path(&entry, "rivet").is_some());
504 }
505
506 #[test]
508 fn installing_two_versions_of_one_crate_lands_both_sets_of_bytes() {
509 use crate::manifest::fixtures::manifest_with_payloads;
515 let (_tmp, store, mut marks) = setup();
516 let (a, b) = (
517 b"serde-1.0.200-crate".to_vec(),
518 b"serde-1.0.210-crate".to_vec(),
519 );
520 let (da, db) = (manifest_digest(&a), manifest_digest(&b));
521 let bytes = manifest_with_payloads(
522 "2026.07.0",
523 "qualified",
524 1,
525 "2026-07-31T09:14:00Z",
526 &[
527 ("serde", "1.0.200", "crate", &da),
528 ("serde", "1.0.210", "crate", &db),
529 ],
530 );
531 let source = memory_source(&bytes, &[(da, a.clone()), (db, b.clone())]);
532 let outcome = install(
533 &pin("2026.07.0"),
534 &source,
535 &AcceptAll,
536 &store,
537 &mut marks,
538 &policy(),
539 )
540 .expect("two versions of one crate is the ordinary shape of a dependency graph");
541
542 let entry = store.get(&outcome.digest).unwrap().unwrap();
543 assert_eq!(
544 std::fs::read(entry.root.join("payloads/serde/1.0.200")).unwrap(),
545 a
546 );
547 assert_eq!(
548 std::fs::read(entry.root.join("payloads/serde/1.0.210")).unwrap(),
549 b,
550 "the second version must not have overwritten the first"
551 );
552 assert!(!entry.root.join("bin/serde").exists());
554 }
555
556 #[test]
558 fn a_signed_manifest_whose_entries_share_one_identity_is_refused_not_overwritten() {
559 use crate::manifest::fixtures::manifest_with_payloads;
565 let (_tmp, store, mut marks) = setup();
566 let (a, b) = (b"first-bytes".to_vec(), b"second-bytes".to_vec());
567 let (da, db) = (manifest_digest(&a), manifest_digest(&b));
568 let bytes = manifest_with_payloads(
569 "2026.07.0",
570 "qualified",
571 1,
572 "2026-07-31T09:14:00Z",
573 &[
574 ("serde", "1.0.200", "crate", &da),
575 ("serde", "1.0.200", "crate", &db),
576 ],
577 );
578 let source = memory_source(&bytes, &[(da, a), (db, b)]);
579 let err = install(
580 &pin("2026.07.0"),
581 &source,
582 &AcceptAll,
583 &store,
584 &mut marks,
585 &policy(),
586 )
587 .expect_err("one identity, two payloads: the store must refuse");
588 assert!(
589 matches!(err, InstallError::Store(StoreError::Collision { .. })),
590 "got: {err}"
591 );
592 assert!(store.list().unwrap().is_empty(), "nothing may be laid down");
593 assert_eq!(
594 marks.mark(&"2026.07".parse().unwrap()),
595 None,
596 "a refused install must not burn the mark"
597 );
598 }
599
600 #[test]
602 fn kill_criterion_two_sources_one_verdict() {
603 let (bytes, blobs) = july();
606 let tmp = tempfile::tempdir().unwrap();
607 let dir = DirSource::at(tmp.path().join("archive"));
608 dir.put(
609 &bytes,
610 &blobs
611 .iter()
612 .map(|(d, b)| (d.as_str(), b.as_slice()))
613 .collect::<Vec<_>>(),
614 )
615 .unwrap();
616 let mem = memory_source(&bytes, &blobs);
617
618 let run = |source: &dyn LayerSource, verifier: &dyn ManifestVerifier| {
619 let (_t, store, mut marks) = setup();
620 install(
621 &pin("2026.07.0"),
622 source,
623 verifier,
624 &store,
625 &mut marks,
626 &policy(),
627 )
628 .map_err(|e| e.to_string())
629 };
630
631 let accept_mem = run(&mem, &AcceptAll).unwrap();
632 let accept_dir = run(&dir, &AcceptAll).unwrap();
633 assert_eq!(accept_mem, accept_dir, "same bytes, same acceptance");
634
635 let reject_mem = run(&mem, &RejectAll).unwrap_err();
636 let reject_dir = run(&dir, &RejectAll).unwrap_err();
637 assert_eq!(reject_mem, reject_dir, "same bytes, same rejection");
638 }
639
640 #[test]
642 fn an_unverified_manifest_fetches_no_blobs_and_installs_nothing() {
643 struct CountingSource {
644 inner: MemorySource,
645 blob_fetches: std::cell::Cell<usize>,
646 }
647 impl LayerSource for CountingSource {
648 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
649 self.inner.fetch_manifest(layer)
650 }
651 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
652 self.blob_fetches.set(self.blob_fetches.get() + 1);
653 self.inner.fetch_blob(digest)
654 }
655 }
656 let (_tmp, store, mut marks) = setup();
657 let (bytes, blobs) = july();
658 let source = CountingSource {
659 inner: memory_source(&bytes, &blobs),
660 blob_fetches: std::cell::Cell::new(0),
661 };
662 let err = install(
663 &pin("2026.07.0"),
664 &source,
665 &RejectAll,
666 &store,
667 &mut marks,
668 &policy(),
669 )
670 .unwrap_err();
671 assert!(matches!(err, InstallError::Verify(_)), "got: {err}");
672 assert_eq!(
673 source.blob_fetches.get(),
674 0,
675 "no blob leaves the source before the signature verdict"
676 );
677 assert!(store.list().unwrap().is_empty(), "nothing laid down");
678 }
679
680 #[test]
682 fn a_source_that_alters_a_blob_is_caught_by_the_signed_digest() {
683 let (_tmp, store, mut marks) = setup();
684 let (bytes, blobs) = july();
685 let mut source = MemorySource::new().with_manifest(&bytes);
687 source = source.with_blob(&blobs[0].0, b"EVIL");
688 source = source.with_blob(&blobs[1].0, &blobs[1].1);
689 let err = install(
690 &pin("2026.07.0"),
691 &source,
692 &AcceptAll,
693 &store,
694 &mut marks,
695 &policy(),
696 )
697 .unwrap_err();
698 assert!(
699 matches!(err, InstallError::BlobDigestMismatch { .. }),
700 "got: {err}"
701 );
702 assert!(
703 store.list().unwrap().is_empty(),
704 "tampered layer must not land"
705 );
706 }
707
708 #[test]
710 fn a_source_that_answers_a_digest_request_with_other_bytes_is_caught() {
711 struct LyingSource(Vec<u8>);
712 impl LayerSource for LyingSource {
713 fn fetch_manifest(&self, _: &LayerRef) -> Result<Vec<u8>, SourceError> {
714 Ok(self.0.clone())
715 }
716 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
717 Err(SourceError::NotFound(digest.into()))
718 }
719 }
720 let (_tmp, store, mut marks) = setup();
721 let (bytes, _) = july();
723 let other = manifest_with_tools("2026.07.0", "qualified", 1, "2026-07-31T09:14:00Z", &[]);
724 let pinned = manifest_digest(&other);
725 let hex = pinned.strip_prefix("sha256:").unwrap();
726 let p = Pin::parse(
727 &format!(
728 "manifest-version = 1\n[toolchain]\nchannel = \"qualified\"\nlayer = \"2026.07.0\"\ndigest = \"sha256:{hex}\"\n"
729 ),
730 "varve.toml",
731 )
732 .unwrap();
733 let err = install(
734 &p,
735 &LyingSource(bytes),
736 &AcceptAll,
737 &store,
738 &mut marks,
739 &policy(),
740 )
741 .unwrap_err();
742 assert!(
743 matches!(err, InstallError::DigestMismatch { .. }),
744 "got: {err}"
745 );
746 }
747
748 #[test]
750 fn a_rolled_back_layer_is_refused_at_install() {
751 let (_tmp, store, mut marks) = setup();
752 let newer = manifest_with_tools("2026.07.2", "qualified", 5, "2026-08-01T00:00:00Z", &[]);
754 marks
755 .advance(&LayerManifest::parse(&newer).unwrap())
756 .unwrap();
757 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
759 let err = install(
760 &pin("2026.07.0"),
761 &source,
762 &AcceptAll,
763 &store,
764 &mut marks,
765 &policy(),
766 )
767 .unwrap_err();
768 assert!(
769 matches!(
770 err,
771 InstallError::Rollback {
772 presented: 1,
773 high_water: 5,
774 ..
775 }
776 ),
777 "got: {err}"
778 );
779 assert!(store.list().unwrap().is_empty());
780 }
781
782 #[test]
784 fn a_failed_install_does_not_advance_the_high_water_mark() {
785 let (_tmp, store, mut marks) = setup();
786 let (bytes, blobs) = july();
787 let source = MemorySource::new()
789 .with_manifest(&bytes)
790 .with_blob(&blobs[0].0, &blobs[0].1);
791 let err = install(
792 &pin("2026.07.0"),
793 &source,
794 &AcceptAll,
795 &store,
796 &mut marks,
797 &policy(),
798 )
799 .unwrap_err();
800 assert!(
801 matches!(err, InstallError::Source(SourceError::NotFound(_))),
802 "got: {err}"
803 );
804 let m = LayerManifest::parse(&bytes).unwrap();
805 assert_eq!(
806 marks.mark(m.layer.line()),
807 None,
808 "failed install must not burn the mark"
809 );
810 }
811
812 #[test]
814 fn staleness_is_surfaced_on_an_accepted_layer() {
815 let (_tmp, store, mut marks) = setup();
816 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
818 let policy = InstallPolicy {
819 index: None,
820 now: "2026-12-01T00:00:00Z",
821 staleness_threshold_days: 90,
822 platform: "test-platform",
823 };
824 let outcome = install(
825 &pin("2026.07.0"),
826 &source,
827 &AcceptAll,
828 &store,
829 &mut marks,
830 &policy,
831 )
832 .unwrap();
833 assert_eq!(outcome.staleness_days, Some(123));
834 }
835
836 #[test]
838 fn install_selects_only_entries_for_the_target_platform() {
839 let (_tmp, store, mut marks) = setup();
840 let here = b"here-tool".to_vec();
841 let there = b"there-tool".to_vec();
842 let (d_here, d_there) = (manifest_digest(&here), manifest_digest(&there));
843 let bytes = crate::manifest::fixtures::manifest_with_platform_tools(
844 "2026.07.0",
845 "qualified",
846 1,
847 "2026-07-31T09:14:00Z",
848 &[
849 ("synth", &d_here, Some("test-platform")),
850 ("synth", &d_there, Some("other-platform")),
851 ],
852 );
853 let source = MemorySource::new()
856 .with_manifest(&bytes)
857 .with_blob(&d_here, &here);
858 let policy = InstallPolicy {
859 index: None,
860 now: "2026-08-07T00:00:00Z",
861 staleness_threshold_days: 90,
862 platform: "test-platform",
863 };
864 let outcome = install(
865 &pin("2026.07.0"),
866 &source,
867 &AcceptAll,
868 &store,
869 &mut marks,
870 &policy,
871 )
872 .unwrap();
873 let entry = store.get(&outcome.digest).unwrap().unwrap();
874 assert_eq!(
875 std::fs::read(store.tool_path(&entry, "synth").unwrap()).unwrap(),
876 here,
877 "the host-platform binary landed"
878 );
879 }
880
881 #[test]
883 fn a_layer_with_nothing_for_the_host_platform_fails_closed() {
884 let (_tmp, store, mut marks) = setup();
885 let there = b"there-tool".to_vec();
886 let d_there = manifest_digest(&there);
887 let bytes = crate::manifest::fixtures::manifest_with_platform_tools(
888 "2026.07.0",
889 "qualified",
890 1,
891 "2026-07-31T09:14:00Z",
892 &[("synth", &d_there, Some("other-platform"))],
893 );
894 let source = MemorySource::new()
895 .with_manifest(&bytes)
896 .with_blob(&d_there, &there);
897 let policy = InstallPolicy {
898 index: None,
899 now: "2026-08-07T00:00:00Z",
900 staleness_threshold_days: 90,
901 platform: "test-platform",
902 };
903 let err = install(
904 &pin("2026.07.0"),
905 &source,
906 &AcceptAll,
907 &store,
908 &mut marks,
909 &policy,
910 )
911 .unwrap_err();
912 assert!(
913 matches!(err, InstallError::NoPlatformEntry { .. }),
914 "got: {err}"
915 );
916 assert!(store.list().unwrap().is_empty(), "no wrong-arch bytes land");
917 }
918
919 #[test]
921 fn unstamped_legacy_entries_install_on_any_platform() {
922 let (_tmp, store, mut marks) = setup();
923 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
925 let policy = InstallPolicy {
926 index: None,
927 now: "2026-08-07T00:00:00Z",
928 staleness_threshold_days: 90,
929 platform: "any-platform-at-all",
930 };
931 assert!(
932 install(
933 &pin("2026.07.0"),
934 &source,
935 &AcceptAll,
936 &store,
937 &mut marks,
938 &policy
939 )
940 .is_ok()
941 );
942 }
943
944 #[test]
946 fn channel_and_layer_mismatches_are_refused() {
947 let (_tmp, store, mut marks) = setup();
948 let synth = b"s".to_vec();
950 let d = manifest_digest(&synth);
951 let bytes = manifest_with_tools(
952 "2026.07.0",
953 "rolling",
954 1,
955 "2026-07-31T09:14:00Z",
956 &[("synth", &d)],
957 );
958 let source = MemorySource::new()
959 .with_manifest(&bytes)
960 .with_blob(&d, &synth);
961 let err = install(
962 &pin("2026.07.0"),
963 &source,
964 &AcceptAll,
965 &store,
966 &mut marks,
967 &policy(),
968 )
969 .unwrap_err();
970 assert!(
971 matches!(err, InstallError::ChannelMismatch { .. }),
972 "got: {err}"
973 );
974 }
975
976 #[test]
978 fn install_carries_the_attestations_the_source_holds_into_the_installed_layer() {
979 use crate::attest::{AttestationKind, sign, statement};
984 let (sk, pk) = crate::verify::generate_root_keypair();
985 let (_tmp, store, mut marks) = setup();
986 let (bytes, blobs) = july();
987 let layer_digest = manifest_digest(&bytes);
988 let sbom = b"{\"bomFormat\":\"CycloneDX\"}";
989 let st = statement(
990 "2026.07.0",
991 &layer_digest,
992 AttestationKind::Sbom,
993 sbom,
994 "acme-ci",
995 );
996 let envelope = sign(&st, &sk, "root-1").unwrap();
997 let source = memory_source(&bytes, &blobs).with_attestation(envelope.as_bytes(), sbom);
998
999 let outcome = install(
1000 &pin("2026.07.0"),
1001 &source,
1002 &AcceptAll,
1003 &store,
1004 &mut marks,
1005 &policy(),
1006 )
1007 .unwrap();
1008 assert_eq!(
1009 outcome.attestations_carried, 1,
1010 "install must report what travelled"
1011 );
1012 assert_eq!(outcome.attestation_note, None);
1013
1014 let entry = store.get(&outcome.digest).unwrap().unwrap();
1016 let carried = crate::attestcarry::read_persisted(&entry.root, "2026.07.0").unwrap();
1017 assert_eq!(carried.len(), 1, "the evidence reached the installed layer");
1018 assert_eq!(
1019 carried[0].bytes, sbom,
1020 "the attested bytes are stored VERBATIM — varve transports another party's \
1021 judgement, it never restates it"
1022 );
1023 let reports = crate::attestcarry::report(&carried, &layer_digest, "2026.07.0", &pk);
1024 assert!(reports[0].binds, "reason: {:?}", reports[0].reason);
1025 assert_eq!(reports[0].producer, "acme-ci");
1026 }
1027
1028 #[test]
1030 fn a_source_that_loses_its_attestations_still_installs_but_says_so() {
1031 struct LosesAttestations(MemorySource);
1038 impl LayerSource for LosesAttestations {
1039 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
1040 self.0.fetch_manifest(layer)
1041 }
1042 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
1043 self.0.fetch_blob(digest)
1044 }
1045 fn fetch_attestations(
1046 &self,
1047 _layer: &LayerRef,
1048 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
1049 Err(SourceError::Transport("the mirror dropped them".into()))
1050 }
1051 }
1052 let (_tmp, store, mut marks) = setup();
1053 let (bytes, blobs) = july();
1054 let outcome = install(
1055 &pin("2026.07.0"),
1056 &LosesAttestations(memory_source(&bytes, &blobs)),
1057 &AcceptAll,
1058 &store,
1059 &mut marks,
1060 &policy(),
1061 )
1062 .expect("a layer whose own signature and digests are good is still a good layer");
1063 assert_eq!(outcome.attestations_carried, 0);
1064 let note = outcome
1065 .attestation_note
1066 .expect("the loss must be reported, never swallowed");
1067 assert!(note.contains("dropped them"), "note: {note}");
1068 }
1069
1070 #[test]
1078 fn a_new_machine_refuses_a_layer_below_the_realms_signed_floor() {
1079 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1080 let (sk, pk) = crate::verify::generate_root_keypair();
1081 let (_tmp, store, mut marks) = setup();
1082
1083 let (bytes, blobs) = july(); let mut source = MemorySource::new().with_manifest(&bytes);
1085 for (d, b) in &blobs {
1086 source = source.with_blob(d, b);
1087 }
1088 let source = source
1089 .with_line_index(
1090 LineIndex {
1091 line: "2026.07".into(),
1092 counter: 1,
1093 issued_at: "2026-08-07T00:00:00Z".into(),
1094 layers: vec![IndexedLayer {
1095 layer: "2026.07.0".into(),
1096 digest: manifest_digest(&bytes),
1097 channel: "qualified".into(),
1098 counter: 1,
1099 }],
1100 }
1101 .sign(&sk, "root-1")
1102 .unwrap()
1103 .as_bytes(),
1104 )
1105 .with_line_status(
1107 crate::linestatus::LineStatus {
1108 line: "2026.07".into(),
1109 counter: 1,
1110 issued_at: "2026-08-07T00:00:00Z".into(),
1111 min_counter: Some(5),
1112 support_until: None,
1113 yanked: Default::default(),
1114 known_problems: Vec::new(),
1115 }
1116 .sign(&sk, "root-1")
1117 .unwrap()
1118 .as_bytes(),
1119 );
1120
1121 let policy = InstallPolicy {
1122 index: Some(IndexPolicy {
1123 realm: "acme",
1124 root_public_key: &pk,
1125 required: true,
1126 }),
1127 ..policy()
1128 };
1129 let err = install(
1130 &pin("2026.07.0"),
1131 &source,
1132 &AcceptAll,
1133 &store,
1134 &mut marks,
1135 &policy,
1136 )
1137 .expect_err("a first contact below the signed floor must be refused");
1138 match &err {
1139 InstallError::BelowFloor {
1140 line,
1141 presented,
1142 floor,
1143 } => {
1144 assert_eq!(line, "2026.07");
1145 assert_eq!(*presented, 1);
1146 assert_eq!(*floor, 5);
1147 }
1148 other => panic!("expected BelowFloor, got {other:?}"),
1149 }
1150 assert!(err.to_string().contains("never installed"), "{err}");
1152 }
1153
1154 #[test]
1158 fn a_line_whose_realm_states_no_floor_installs_as_it_always_did() {
1159 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1160 let (sk, pk) = crate::verify::generate_root_keypair();
1161 let (_tmp, store, mut marks) = setup();
1162
1163 let (bytes, blobs) = july();
1164 let mut source = MemorySource::new().with_manifest(&bytes);
1165 for (d, b) in &blobs {
1166 source = source.with_blob(d, b);
1167 }
1168 let source = source
1169 .with_line_index(
1170 LineIndex {
1171 line: "2026.07".into(),
1172 counter: 1,
1173 issued_at: "2026-08-07T00:00:00Z".into(),
1174 layers: vec![IndexedLayer {
1175 layer: "2026.07.0".into(),
1176 digest: manifest_digest(&bytes),
1177 channel: "qualified".into(),
1178 counter: 1,
1179 }],
1180 }
1181 .sign(&sk, "root-1")
1182 .unwrap()
1183 .as_bytes(),
1184 )
1185 .with_line_status(
1186 crate::linestatus::LineStatus {
1187 line: "2026.07".into(),
1188 counter: 1,
1189 issued_at: "2026-08-07T00:00:00Z".into(),
1190 min_counter: None,
1191 support_until: None,
1192 yanked: Default::default(),
1193 known_problems: Vec::new(),
1194 }
1195 .sign(&sk, "root-1")
1196 .unwrap()
1197 .as_bytes(),
1198 );
1199
1200 let policy = InstallPolicy {
1201 index: Some(IndexPolicy {
1202 realm: "acme",
1203 root_public_key: &pk,
1204 required: true,
1205 }),
1206 ..policy()
1207 };
1208 install(
1209 &pin("2026.07.0"),
1210 &source,
1211 &AcceptAll,
1212 &store,
1213 &mut marks,
1214 &policy,
1215 )
1216 .expect("no stated floor must not change anything");
1217 }
1218
1219 #[test]
1221 fn install_raises_the_mark_from_the_index_even_when_the_layer_is_hidden() {
1222 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1228 let (sk, pk) = crate::verify::generate_root_keypair();
1229 let (_tmp, store, mut marks) = setup();
1230
1231 let (bytes, blobs) = july(); let mut source = MemorySource::new().with_manifest(&bytes);
1233 for (d, b) in &blobs {
1234 source = source.with_blob(d, b);
1235 }
1236 let source = source.with_line_index(
1237 LineIndex {
1238 line: "2026.07".into(),
1239 counter: 1,
1240 issued_at: "2026-08-07T00:00:00Z".into(),
1241 layers: vec![
1242 IndexedLayer {
1243 layer: "2026.07.0".into(),
1244 digest: manifest_digest(&bytes),
1245 channel: "qualified".into(),
1246 counter: 1,
1247 },
1248 IndexedLayer {
1250 layer: "2026.07.9".into(),
1251 digest: "sha256:hidden".into(),
1252 channel: "qualified".into(),
1253 counter: 42,
1254 },
1255 ],
1256 }
1257 .sign(&sk, "root-1")
1258 .unwrap()
1259 .as_bytes(),
1260 );
1261
1262 let policy = InstallPolicy {
1263 index: Some(IndexPolicy {
1264 realm: "acme",
1265 root_public_key: &pk,
1266 required: true,
1267 }),
1268 ..policy()
1269 };
1270 let outcome = install(
1271 &pin("2026.07.0"),
1272 &source,
1273 &AcceptAll,
1274 &store,
1275 &mut marks,
1276 &policy,
1277 )
1278 .expect("a pinned layer must install even when the line has moved on");
1279
1280 assert_eq!(
1283 outcome.index_high_water,
1284 Some(42),
1285 "the realm's assertion must reach the consumer even when the source \
1286 withheld the layer it refers to"
1287 );
1288 assert_eq!(outcome.layer.to_string(), "2026.07.0");
1295 assert_eq!(
1296 marks.mark(&"2026.07".parse().unwrap()),
1297 Some(1),
1298 "the mark records what this machine ACCEPTED, not what exists"
1299 );
1300 }
1301
1302 #[test]
1304 fn a_replayed_older_index_is_refused_on_the_next_install_not_only_in_theory() {
1305 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1311 let (sk, pk) = crate::verify::generate_root_keypair();
1312 let (_tmp, store, mut marks) = setup();
1313 let (bytes, blobs) = july();
1314
1315 let signed_index = |counter: u64| {
1316 LineIndex {
1317 line: "2026.07".into(),
1318 counter,
1319 issued_at: "2026-08-07T00:00:00Z".into(),
1320 layers: vec![IndexedLayer {
1321 layer: "2026.07.0".into(),
1322 digest: manifest_digest(&bytes),
1323 channel: "qualified".into(),
1324 counter: 1,
1325 }],
1326 }
1327 .sign(&sk, "root-1")
1328 .unwrap()
1329 };
1330 struct CountingSource {
1338 inner: MemorySource,
1339 blob_fetches: std::cell::Cell<usize>,
1340 }
1341 impl LayerSource for CountingSource {
1342 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
1343 self.inner.fetch_manifest(layer)
1344 }
1345 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
1346 self.blob_fetches.set(self.blob_fetches.get() + 1);
1347 self.inner.fetch_blob(digest)
1348 }
1349 fn fetch_line_index(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
1350 self.inner.fetch_line_index(line)
1351 }
1352 fn served_layers(&self, line: &str) -> Result<Option<Vec<String>>, SourceError> {
1353 self.inner.served_layers(line)
1354 }
1355 }
1356 let source_with = |envelope: &str| {
1357 let mut s = MemorySource::new().with_manifest(&bytes);
1358 for (d, b) in &blobs {
1359 s = s.with_blob(d, b);
1360 }
1361 CountingSource {
1362 inner: s.with_line_index(envelope.as_bytes()),
1363 blob_fetches: std::cell::Cell::new(0),
1364 }
1365 };
1366 let policy = InstallPolicy {
1367 index: Some(IndexPolicy {
1368 realm: "acme",
1369 root_public_key: &pk,
1370 required: true,
1371 }),
1372 ..policy()
1373 };
1374
1375 install(
1376 &pin("2026.07.0"),
1377 &source_with(&signed_index(9)),
1378 &AcceptAll,
1379 &store,
1380 &mut marks,
1381 &policy,
1382 )
1383 .expect("the first install accepts index #9");
1384
1385 let replay = source_with(&signed_index(4));
1388 let err = install(
1389 &pin("2026.07.0"),
1390 &replay,
1391 &AcceptAll,
1392 &store,
1393 &mut marks,
1394 &policy,
1395 )
1396 .expect_err("a superseded index must not be replayable over the held one");
1397 assert_eq!(
1398 replay.blob_fetches.get(),
1399 0,
1400 "the index check runs BEFORE anything is fetched or laid down — a \
1401 refusal discovered only when the cache was written would already \
1402 have installed the layer and advanced the mark"
1403 );
1404 assert!(
1405 matches!(
1406 err,
1407 InstallError::Index(crate::lineindex::IndexError::Stale {
1408 presented: 4,
1409 cached: 9,
1410 ..
1411 })
1412 ),
1413 "got: {err}"
1414 );
1415 assert!(
1416 err.to_string().contains('4') && err.to_string().contains('9'),
1417 "names both counters: {err}"
1418 );
1419
1420 assert_eq!(
1423 crate::lineindex::IndexCache::at_root(store.root())
1424 .load("2026.07")
1425 .unwrap()
1426 .unwrap()
1427 .counter,
1428 9
1429 );
1430 }
1431
1432 #[test]
1434 fn install_refuses_a_source_that_hides_an_indexed_layer() {
1435 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1438 let (sk, pk) = crate::verify::generate_root_keypair();
1439 let (_tmp, store, mut marks) = setup();
1440
1441 let (bytes, blobs) = july();
1442 let mut source = MemorySource::new().with_manifest(&bytes);
1443 for (d, b) in &blobs {
1444 source = source.with_blob(d, b);
1445 }
1446 let source = source.serving(&["2026.07.0"]).with_line_index(
1449 LineIndex {
1450 line: "2026.07".into(),
1451 counter: 1,
1452 issued_at: "2026-08-07T00:00:00Z".into(),
1453 layers: vec![IndexedLayer {
1454 layer: "2026.07.5".into(),
1455 digest: "sha256:withheld".into(),
1456 channel: "qualified".into(),
1457 counter: 5,
1458 }],
1459 }
1460 .sign(&sk, "root-1")
1461 .unwrap()
1462 .as_bytes(),
1463 );
1464
1465 let policy = InstallPolicy {
1466 index: Some(IndexPolicy {
1467 realm: "acme",
1468 root_public_key: &pk,
1469 required: true,
1470 }),
1471 ..policy()
1472 };
1473 let err = install(
1474 &pin("2026.07.0"),
1475 &source,
1476 &AcceptAll,
1477 &store,
1478 &mut marks,
1479 &policy,
1480 )
1481 .expect_err("a source hiding an indexed layer must be refused");
1482 let msg = err.to_string();
1483 assert!(msg.contains("2026.07.5"), "names the hidden layer: {msg}");
1484 assert!(store.list().unwrap().is_empty(), "no install on a refusal");
1486 }
1487}