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("blob {digest} fetched for tool '{tool}' does not match its signed digest — refusing")]
95 BlobDigestMismatch { tool: String, digest: String },
96 #[error("manifest entry {digest} is missing the eu.pulseengine.tool annotation")]
97 UnnamedEntry { digest: String },
98 #[error(
99 "layer {layer} carries no entry for platform {platform} — refusing to install a \
100 wrong-architecture toolchain; use --platform only if you know why"
101 )]
102 NoPlatformEntry { layer: String, platform: String },
103 #[error(transparent)]
104 Store(#[from] StoreError),
105 #[error(transparent)]
106 State(#[from] RollbackError),
107}
108
109pub struct InstallPolicy<'a> {
112 pub now: &'a str,
114 pub staleness_threshold_days: u32,
115 pub platform: &'a str,
118 pub index: Option<crate::lineindex::IndexPolicy<'a>>,
122}
123
124pub fn install(
125 pin: &Pin,
126 source: &dyn LayerSource,
127 verifier: &dyn ManifestVerifier,
128 store: &Store,
129 marks: &mut HighWaterMarks,
130 policy: &InstallPolicy<'_>,
131) -> Result<InstallOutcome, InstallError> {
132 let layer_ref = match &pin.digest {
134 Some(digest) => LayerRef::Digest(digest.clone()),
135 None => LayerRef::Name(pin.layer.clone()),
136 };
137 let fetched = source.fetch_manifest(&layer_ref)?;
138
139 let bytes = verifier.verify(&fetched)?;
143
144 let manifest = LayerManifest::parse(&bytes)?;
146 let digest = manifest_digest(&bytes);
147 if let Some(pinned) = &pin.digest
148 && &digest != pinned
149 {
150 return Err(InstallError::DigestMismatch {
151 pinned: pinned.clone(),
152 got: digest,
153 });
154 }
155 if manifest.layer != pin.layer {
156 return Err(InstallError::LayerMismatch {
157 pinned: pin.layer.to_string(),
158 got: manifest.layer.to_string(),
159 });
160 }
161 let pinned_channel = match pin.channel {
162 crate::pin::Channel::Qualified => "qualified",
163 crate::pin::Channel::Rolling => "rolling",
164 };
165 if manifest.channel != pinned_channel {
166 return Err(InstallError::ChannelMismatch {
167 pinned: pinned_channel.to_string(),
168 got: manifest.channel.clone(),
169 });
170 }
171
172 let line = manifest.layer.line();
178 let line_str = line.to_string();
179 let index_cache = crate::lineindex::IndexCache::at_root(store.root());
180 let mut index_high_water: Option<u64> = None;
181 let mut accepted_index: Option<(crate::lineindex::LineIndex, Vec<u8>)> = None;
185 if let Some(index_policy) = &policy.index {
186 let envelope = source.fetch_line_index(&line_str)?;
187 let served = source.served_layers(&line_str)?;
188 let cached = index_cache.load(&line_str)?;
193 let verified = crate::lineindex::check(
194 &line_str,
195 envelope.as_deref(),
196 served.as_deref(),
197 cached.as_ref(),
198 index_policy,
199 )?;
200 index_high_water = verified.as_ref().and_then(|i| i.high_water());
201 if let (Some(doc), Some(bytes)) = (verified, envelope) {
202 accepted_index = Some((doc, bytes));
203 }
204 }
205
206 if let RollbackVerdict::Rollback {
208 line,
209 presented,
210 high_water,
211 } = marks.check(&manifest)
212 {
213 return Err(InstallError::Rollback {
214 line,
215 presented,
216 high_water,
217 });
218 }
219
220 struct Fetched {
227 name: String,
228 version: Option<String>,
229 dispatchable: bool,
230 bytes: Vec<u8>,
231 }
232 let mut tools: Vec<Fetched> = Vec::new();
233 let mut matched = 0usize;
234 for entry in &manifest.entries {
235 if !crate::platform::entry_matches(
236 entry
237 .annotations
238 .get(crate::platform::ANN_PLATFORM)
239 .map(String::as_str),
240 policy.platform,
241 ) {
242 continue;
243 }
244 if entry.kind() == Ok(crate::kind::PayloadKind::Layer) {
249 continue;
250 }
251 matched += 1;
252 let tool = entry
253 .annotations
254 .get("eu.pulseengine.tool")
255 .ok_or_else(|| InstallError::UnnamedEntry {
256 digest: entry.digest.clone(),
257 })?
258 .clone();
259 let blob = source.fetch_blob(&entry.digest)?;
260 if manifest_digest(&blob) != entry.digest {
261 return Err(InstallError::BlobDigestMismatch {
262 tool,
263 digest: entry.digest.clone(),
264 });
265 }
266 tools.push(Fetched {
267 name: tool,
268 version: crate::store::entry_version(entry).map(str::to_string),
269 dispatchable: crate::store::entry_is_dispatchable(entry),
270 bytes: blob,
271 });
272 }
273
274 if matched == 0 && !manifest.entries.is_empty() {
277 return Err(InstallError::NoPlatformEntry {
278 layer: manifest.layer.to_string(),
279 platform: policy.platform.to_string(),
280 });
281 }
282
283 let payloads: Vec<crate::store::Payload<'_>> = tools
285 .iter()
286 .map(|t| crate::store::Payload {
287 name: t.name.as_str(),
288 version: t.version.as_deref(),
289 dispatchable: t.dispatchable,
290 bytes: t.bytes.as_slice(),
291 })
292 .collect();
293 let stored_digest = store.lay_down_payloads(&bytes, &payloads)?;
294 debug_assert_eq!(stored_digest, digest);
295
296 if fetched != bytes
300 && let Some(entry) = store.get(&digest)?
301 {
302 let path = entry.root.join(crate::reverify::ENVELOPE_FILE);
303 std::fs::write(&path, &fetched).map_err(|source| StoreError::Io {
304 path: path.display().to_string(),
305 source,
306 })?;
307 }
308
309 let mut attestations_carried = 0usize;
316 let mut attestation_note = None;
317 if let Some(entry) = store.get(&digest)? {
318 match crate::attestcarry::carry_from_source(source, &layer_ref, &entry.root) {
319 Ok(n) => attestations_carried = n,
320 Err(e) => attestation_note = Some(e.to_string()),
326 }
327 }
328
329 marks.advance(&manifest)?;
332 if let Some((doc, bytes)) = &accepted_index {
333 index_cache.update(&line_str, bytes, doc)?;
334 }
335
336 let staleness_days = crate::rollback::staleness_warning(
337 &manifest.issued_at,
338 policy.now,
339 policy.staleness_threshold_days,
340 );
341 Ok(InstallOutcome {
342 digest,
343 layer: manifest.layer.clone(),
344 counter: manifest.counter,
345 staleness_days,
346 index_high_water,
347 attestations_carried,
348 attestation_note,
349 })
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::manifest::fixtures::manifest_with_tools;
356 use crate::pin::Pin;
357 use crate::rollback::HighWaterMarks;
358 use crate::source::{DirSource, MemorySource};
359
360 struct AcceptAll;
361 impl ManifestVerifier for AcceptAll {
362 fn verify(&self, fetched: &[u8]) -> Result<Vec<u8>, VerifyError> {
363 Ok(fetched.to_vec())
364 }
365 }
366
367 struct RejectAll;
368 impl ManifestVerifier for RejectAll {
369 fn verify(&self, _: &[u8]) -> Result<Vec<u8>, VerifyError> {
370 Err(VerifyError("untrusted signature (test)".into()))
371 }
372 }
373
374 fn pin(layer: &str) -> Pin {
375 Pin::parse(
376 &format!(
377 "manifest-version = 1\n[toolchain]\nchannel = \"qualified\"\nlayer = \"{layer}\"\n"
378 ),
379 "varve.toml",
380 )
381 .unwrap()
382 }
383
384 fn policy() -> InstallPolicy<'static> {
385 InstallPolicy {
386 index: None,
387 now: "2026-08-07T00:00:00Z",
388 staleness_threshold_days: 90,
389 platform: "test-platform",
390 }
391 }
392
393 fn july() -> (Vec<u8>, Vec<(String, Vec<u8>)>) {
395 let synth = b"july-synth".to_vec();
396 let rivet = b"july-rivet".to_vec();
397 let blobs = vec![
398 (manifest_digest(&synth), synth),
399 (manifest_digest(&rivet), rivet),
400 ];
401 let bytes = manifest_with_tools(
402 "2026.07.0",
403 "qualified",
404 1,
405 "2026-07-31T09:14:00Z",
406 &[("synth", &blobs[0].0), ("rivet", &blobs[1].0)],
407 );
408 (bytes, blobs)
409 }
410
411 fn memory_source(manifest: &[u8], blobs: &[(String, Vec<u8>)]) -> MemorySource {
412 let mut source = MemorySource::new().with_manifest(manifest);
413 for (digest, bytes) in blobs {
414 source = source.with_blob(digest, bytes);
415 }
416 source
417 }
418
419 fn setup() -> (tempfile::TempDir, Store, HighWaterMarks) {
420 let tmp = tempfile::tempdir().unwrap();
421 let root = tmp.path().join("varve-root");
422 let store = Store::at(&root);
423 let marks = HighWaterMarks::load(&root).unwrap();
424 (tmp, store, marks)
425 }
426
427 #[test]
429 fn installs_a_verified_layer_end_to_end() {
430 let (_tmp, store, mut marks) = setup();
431 let (bytes, blobs) = july();
432 let source = memory_source(&bytes, &blobs);
433 let outcome = install(
434 &pin("2026.07.0"),
435 &source,
436 &AcceptAll,
437 &store,
438 &mut marks,
439 &policy(),
440 )
441 .unwrap();
442 assert_eq!(outcome.layer.to_string(), "2026.07.0");
443 assert_eq!(outcome.digest, manifest_digest(&bytes));
444 let entry = store.get(&outcome.digest).unwrap().unwrap();
446 assert!(store.tool_path(&entry, "synth").is_some());
447 assert!(store.tool_path(&entry, "rivet").is_some());
448 }
449
450 #[test]
452 fn installing_two_versions_of_one_crate_lands_both_sets_of_bytes() {
453 use crate::manifest::fixtures::manifest_with_payloads;
459 let (_tmp, store, mut marks) = setup();
460 let (a, b) = (
461 b"serde-1.0.200-crate".to_vec(),
462 b"serde-1.0.210-crate".to_vec(),
463 );
464 let (da, db) = (manifest_digest(&a), manifest_digest(&b));
465 let bytes = manifest_with_payloads(
466 "2026.07.0",
467 "qualified",
468 1,
469 "2026-07-31T09:14:00Z",
470 &[
471 ("serde", "1.0.200", "crate", &da),
472 ("serde", "1.0.210", "crate", &db),
473 ],
474 );
475 let source = memory_source(&bytes, &[(da, a.clone()), (db, b.clone())]);
476 let outcome = install(
477 &pin("2026.07.0"),
478 &source,
479 &AcceptAll,
480 &store,
481 &mut marks,
482 &policy(),
483 )
484 .expect("two versions of one crate is the ordinary shape of a dependency graph");
485
486 let entry = store.get(&outcome.digest).unwrap().unwrap();
487 assert_eq!(
488 std::fs::read(entry.root.join("payloads/serde/1.0.200")).unwrap(),
489 a
490 );
491 assert_eq!(
492 std::fs::read(entry.root.join("payloads/serde/1.0.210")).unwrap(),
493 b,
494 "the second version must not have overwritten the first"
495 );
496 assert!(!entry.root.join("bin/serde").exists());
498 }
499
500 #[test]
502 fn a_signed_manifest_whose_entries_share_one_identity_is_refused_not_overwritten() {
503 use crate::manifest::fixtures::manifest_with_payloads;
509 let (_tmp, store, mut marks) = setup();
510 let (a, b) = (b"first-bytes".to_vec(), b"second-bytes".to_vec());
511 let (da, db) = (manifest_digest(&a), manifest_digest(&b));
512 let bytes = manifest_with_payloads(
513 "2026.07.0",
514 "qualified",
515 1,
516 "2026-07-31T09:14:00Z",
517 &[
518 ("serde", "1.0.200", "crate", &da),
519 ("serde", "1.0.200", "crate", &db),
520 ],
521 );
522 let source = memory_source(&bytes, &[(da, a), (db, b)]);
523 let err = install(
524 &pin("2026.07.0"),
525 &source,
526 &AcceptAll,
527 &store,
528 &mut marks,
529 &policy(),
530 )
531 .expect_err("one identity, two payloads: the store must refuse");
532 assert!(
533 matches!(err, InstallError::Store(StoreError::Collision { .. })),
534 "got: {err}"
535 );
536 assert!(store.list().unwrap().is_empty(), "nothing may be laid down");
537 assert_eq!(
538 marks.mark(&"2026.07".parse().unwrap()),
539 None,
540 "a refused install must not burn the mark"
541 );
542 }
543
544 #[test]
546 fn kill_criterion_two_sources_one_verdict() {
547 let (bytes, blobs) = july();
550 let tmp = tempfile::tempdir().unwrap();
551 let dir = DirSource::at(tmp.path().join("archive"));
552 dir.put(
553 &bytes,
554 &blobs
555 .iter()
556 .map(|(d, b)| (d.as_str(), b.as_slice()))
557 .collect::<Vec<_>>(),
558 )
559 .unwrap();
560 let mem = memory_source(&bytes, &blobs);
561
562 let run = |source: &dyn LayerSource, verifier: &dyn ManifestVerifier| {
563 let (_t, store, mut marks) = setup();
564 install(
565 &pin("2026.07.0"),
566 source,
567 verifier,
568 &store,
569 &mut marks,
570 &policy(),
571 )
572 .map_err(|e| e.to_string())
573 };
574
575 let accept_mem = run(&mem, &AcceptAll).unwrap();
576 let accept_dir = run(&dir, &AcceptAll).unwrap();
577 assert_eq!(accept_mem, accept_dir, "same bytes, same acceptance");
578
579 let reject_mem = run(&mem, &RejectAll).unwrap_err();
580 let reject_dir = run(&dir, &RejectAll).unwrap_err();
581 assert_eq!(reject_mem, reject_dir, "same bytes, same rejection");
582 }
583
584 #[test]
586 fn an_unverified_manifest_fetches_no_blobs_and_installs_nothing() {
587 struct CountingSource {
588 inner: MemorySource,
589 blob_fetches: std::cell::Cell<usize>,
590 }
591 impl LayerSource for CountingSource {
592 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
593 self.inner.fetch_manifest(layer)
594 }
595 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
596 self.blob_fetches.set(self.blob_fetches.get() + 1);
597 self.inner.fetch_blob(digest)
598 }
599 }
600 let (_tmp, store, mut marks) = setup();
601 let (bytes, blobs) = july();
602 let source = CountingSource {
603 inner: memory_source(&bytes, &blobs),
604 blob_fetches: std::cell::Cell::new(0),
605 };
606 let err = install(
607 &pin("2026.07.0"),
608 &source,
609 &RejectAll,
610 &store,
611 &mut marks,
612 &policy(),
613 )
614 .unwrap_err();
615 assert!(matches!(err, InstallError::Verify(_)), "got: {err}");
616 assert_eq!(
617 source.blob_fetches.get(),
618 0,
619 "no blob leaves the source before the signature verdict"
620 );
621 assert!(store.list().unwrap().is_empty(), "nothing laid down");
622 }
623
624 #[test]
626 fn a_source_that_alters_a_blob_is_caught_by_the_signed_digest() {
627 let (_tmp, store, mut marks) = setup();
628 let (bytes, blobs) = july();
629 let mut source = MemorySource::new().with_manifest(&bytes);
631 source = source.with_blob(&blobs[0].0, b"EVIL");
632 source = source.with_blob(&blobs[1].0, &blobs[1].1);
633 let err = install(
634 &pin("2026.07.0"),
635 &source,
636 &AcceptAll,
637 &store,
638 &mut marks,
639 &policy(),
640 )
641 .unwrap_err();
642 assert!(
643 matches!(err, InstallError::BlobDigestMismatch { .. }),
644 "got: {err}"
645 );
646 assert!(
647 store.list().unwrap().is_empty(),
648 "tampered layer must not land"
649 );
650 }
651
652 #[test]
654 fn a_source_that_answers_a_digest_request_with_other_bytes_is_caught() {
655 struct LyingSource(Vec<u8>);
656 impl LayerSource for LyingSource {
657 fn fetch_manifest(&self, _: &LayerRef) -> Result<Vec<u8>, SourceError> {
658 Ok(self.0.clone())
659 }
660 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
661 Err(SourceError::NotFound(digest.into()))
662 }
663 }
664 let (_tmp, store, mut marks) = setup();
665 let (bytes, _) = july();
667 let other = manifest_with_tools("2026.07.0", "qualified", 1, "2026-07-31T09:14:00Z", &[]);
668 let pinned = manifest_digest(&other);
669 let hex = pinned.strip_prefix("sha256:").unwrap();
670 let p = Pin::parse(
671 &format!(
672 "manifest-version = 1\n[toolchain]\nchannel = \"qualified\"\nlayer = \"2026.07.0\"\ndigest = \"sha256:{hex}\"\n"
673 ),
674 "varve.toml",
675 )
676 .unwrap();
677 let err = install(
678 &p,
679 &LyingSource(bytes),
680 &AcceptAll,
681 &store,
682 &mut marks,
683 &policy(),
684 )
685 .unwrap_err();
686 assert!(
687 matches!(err, InstallError::DigestMismatch { .. }),
688 "got: {err}"
689 );
690 }
691
692 #[test]
694 fn a_rolled_back_layer_is_refused_at_install() {
695 let (_tmp, store, mut marks) = setup();
696 let newer = manifest_with_tools("2026.07.2", "qualified", 5, "2026-08-01T00:00:00Z", &[]);
698 marks
699 .advance(&LayerManifest::parse(&newer).unwrap())
700 .unwrap();
701 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
703 let err = install(
704 &pin("2026.07.0"),
705 &source,
706 &AcceptAll,
707 &store,
708 &mut marks,
709 &policy(),
710 )
711 .unwrap_err();
712 assert!(
713 matches!(
714 err,
715 InstallError::Rollback {
716 presented: 1,
717 high_water: 5,
718 ..
719 }
720 ),
721 "got: {err}"
722 );
723 assert!(store.list().unwrap().is_empty());
724 }
725
726 #[test]
728 fn a_failed_install_does_not_advance_the_high_water_mark() {
729 let (_tmp, store, mut marks) = setup();
730 let (bytes, blobs) = july();
731 let source = MemorySource::new()
733 .with_manifest(&bytes)
734 .with_blob(&blobs[0].0, &blobs[0].1);
735 let err = install(
736 &pin("2026.07.0"),
737 &source,
738 &AcceptAll,
739 &store,
740 &mut marks,
741 &policy(),
742 )
743 .unwrap_err();
744 assert!(
745 matches!(err, InstallError::Source(SourceError::NotFound(_))),
746 "got: {err}"
747 );
748 let m = LayerManifest::parse(&bytes).unwrap();
749 assert_eq!(
750 marks.mark(m.layer.line()),
751 None,
752 "failed install must not burn the mark"
753 );
754 }
755
756 #[test]
758 fn staleness_is_surfaced_on_an_accepted_layer() {
759 let (_tmp, store, mut marks) = setup();
760 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
762 let policy = InstallPolicy {
763 index: None,
764 now: "2026-12-01T00:00:00Z",
765 staleness_threshold_days: 90,
766 platform: "test-platform",
767 };
768 let outcome = install(
769 &pin("2026.07.0"),
770 &source,
771 &AcceptAll,
772 &store,
773 &mut marks,
774 &policy,
775 )
776 .unwrap();
777 assert_eq!(outcome.staleness_days, Some(123));
778 }
779
780 #[test]
782 fn install_selects_only_entries_for_the_target_platform() {
783 let (_tmp, store, mut marks) = setup();
784 let here = b"here-tool".to_vec();
785 let there = b"there-tool".to_vec();
786 let (d_here, d_there) = (manifest_digest(&here), manifest_digest(&there));
787 let bytes = crate::manifest::fixtures::manifest_with_platform_tools(
788 "2026.07.0",
789 "qualified",
790 1,
791 "2026-07-31T09:14:00Z",
792 &[
793 ("synth", &d_here, Some("test-platform")),
794 ("synth", &d_there, Some("other-platform")),
795 ],
796 );
797 let source = MemorySource::new()
800 .with_manifest(&bytes)
801 .with_blob(&d_here, &here);
802 let policy = InstallPolicy {
803 index: None,
804 now: "2026-08-07T00:00:00Z",
805 staleness_threshold_days: 90,
806 platform: "test-platform",
807 };
808 let outcome = install(
809 &pin("2026.07.0"),
810 &source,
811 &AcceptAll,
812 &store,
813 &mut marks,
814 &policy,
815 )
816 .unwrap();
817 let entry = store.get(&outcome.digest).unwrap().unwrap();
818 assert_eq!(
819 std::fs::read(store.tool_path(&entry, "synth").unwrap()).unwrap(),
820 here,
821 "the host-platform binary landed"
822 );
823 }
824
825 #[test]
827 fn a_layer_with_nothing_for_the_host_platform_fails_closed() {
828 let (_tmp, store, mut marks) = setup();
829 let there = b"there-tool".to_vec();
830 let d_there = manifest_digest(&there);
831 let bytes = crate::manifest::fixtures::manifest_with_platform_tools(
832 "2026.07.0",
833 "qualified",
834 1,
835 "2026-07-31T09:14:00Z",
836 &[("synth", &d_there, Some("other-platform"))],
837 );
838 let source = MemorySource::new()
839 .with_manifest(&bytes)
840 .with_blob(&d_there, &there);
841 let policy = InstallPolicy {
842 index: None,
843 now: "2026-08-07T00:00:00Z",
844 staleness_threshold_days: 90,
845 platform: "test-platform",
846 };
847 let err = install(
848 &pin("2026.07.0"),
849 &source,
850 &AcceptAll,
851 &store,
852 &mut marks,
853 &policy,
854 )
855 .unwrap_err();
856 assert!(
857 matches!(err, InstallError::NoPlatformEntry { .. }),
858 "got: {err}"
859 );
860 assert!(store.list().unwrap().is_empty(), "no wrong-arch bytes land");
861 }
862
863 #[test]
865 fn unstamped_legacy_entries_install_on_any_platform() {
866 let (_tmp, store, mut marks) = setup();
867 let (bytes, blobs) = july(); let source = memory_source(&bytes, &blobs);
869 let policy = InstallPolicy {
870 index: None,
871 now: "2026-08-07T00:00:00Z",
872 staleness_threshold_days: 90,
873 platform: "any-platform-at-all",
874 };
875 assert!(
876 install(
877 &pin("2026.07.0"),
878 &source,
879 &AcceptAll,
880 &store,
881 &mut marks,
882 &policy
883 )
884 .is_ok()
885 );
886 }
887
888 #[test]
890 fn channel_and_layer_mismatches_are_refused() {
891 let (_tmp, store, mut marks) = setup();
892 let synth = b"s".to_vec();
894 let d = manifest_digest(&synth);
895 let bytes = manifest_with_tools(
896 "2026.07.0",
897 "rolling",
898 1,
899 "2026-07-31T09:14:00Z",
900 &[("synth", &d)],
901 );
902 let source = MemorySource::new()
903 .with_manifest(&bytes)
904 .with_blob(&d, &synth);
905 let err = install(
906 &pin("2026.07.0"),
907 &source,
908 &AcceptAll,
909 &store,
910 &mut marks,
911 &policy(),
912 )
913 .unwrap_err();
914 assert!(
915 matches!(err, InstallError::ChannelMismatch { .. }),
916 "got: {err}"
917 );
918 }
919
920 #[test]
922 fn install_carries_the_attestations_the_source_holds_into_the_installed_layer() {
923 use crate::attest::{AttestationKind, sign, statement};
928 let (sk, pk) = crate::verify::generate_root_keypair();
929 let (_tmp, store, mut marks) = setup();
930 let (bytes, blobs) = july();
931 let layer_digest = manifest_digest(&bytes);
932 let sbom = b"{\"bomFormat\":\"CycloneDX\"}";
933 let st = statement(
934 "2026.07.0",
935 &layer_digest,
936 AttestationKind::Sbom,
937 sbom,
938 "acme-ci",
939 );
940 let envelope = sign(&st, &sk, "root-1").unwrap();
941 let source = memory_source(&bytes, &blobs).with_attestation(envelope.as_bytes(), sbom);
942
943 let outcome = install(
944 &pin("2026.07.0"),
945 &source,
946 &AcceptAll,
947 &store,
948 &mut marks,
949 &policy(),
950 )
951 .unwrap();
952 assert_eq!(
953 outcome.attestations_carried, 1,
954 "install must report what travelled"
955 );
956 assert_eq!(outcome.attestation_note, None);
957
958 let entry = store.get(&outcome.digest).unwrap().unwrap();
960 let carried = crate::attestcarry::read_persisted(&entry.root, "2026.07.0").unwrap();
961 assert_eq!(carried.len(), 1, "the evidence reached the installed layer");
962 assert_eq!(
963 carried[0].bytes, sbom,
964 "the attested bytes are stored VERBATIM — varve transports another party's \
965 judgement, it never restates it"
966 );
967 let reports = crate::attestcarry::report(&carried, &layer_digest, "2026.07.0", &pk);
968 assert!(reports[0].binds, "reason: {:?}", reports[0].reason);
969 assert_eq!(reports[0].producer, "acme-ci");
970 }
971
972 #[test]
974 fn a_source_that_loses_its_attestations_still_installs_but_says_so() {
975 struct LosesAttestations(MemorySource);
982 impl LayerSource for LosesAttestations {
983 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
984 self.0.fetch_manifest(layer)
985 }
986 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
987 self.0.fetch_blob(digest)
988 }
989 fn fetch_attestations(
990 &self,
991 _layer: &LayerRef,
992 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
993 Err(SourceError::Transport("the mirror dropped them".into()))
994 }
995 }
996 let (_tmp, store, mut marks) = setup();
997 let (bytes, blobs) = july();
998 let outcome = install(
999 &pin("2026.07.0"),
1000 &LosesAttestations(memory_source(&bytes, &blobs)),
1001 &AcceptAll,
1002 &store,
1003 &mut marks,
1004 &policy(),
1005 )
1006 .expect("a layer whose own signature and digests are good is still a good layer");
1007 assert_eq!(outcome.attestations_carried, 0);
1008 let note = outcome
1009 .attestation_note
1010 .expect("the loss must be reported, never swallowed");
1011 assert!(note.contains("dropped them"), "note: {note}");
1012 }
1013
1014 #[test]
1016 fn install_raises_the_mark_from_the_index_even_when_the_layer_is_hidden() {
1017 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1023 let (sk, pk) = crate::verify::generate_root_keypair();
1024 let (_tmp, store, mut marks) = setup();
1025
1026 let (bytes, blobs) = july(); let mut source = MemorySource::new().with_manifest(&bytes);
1028 for (d, b) in &blobs {
1029 source = source.with_blob(d, b);
1030 }
1031 let source = source.with_line_index(
1032 LineIndex {
1033 line: "2026.07".into(),
1034 counter: 1,
1035 issued_at: "2026-08-07T00:00:00Z".into(),
1036 layers: vec![
1037 IndexedLayer {
1038 layer: "2026.07.0".into(),
1039 digest: manifest_digest(&bytes),
1040 channel: "qualified".into(),
1041 counter: 1,
1042 },
1043 IndexedLayer {
1045 layer: "2026.07.9".into(),
1046 digest: "sha256:hidden".into(),
1047 channel: "qualified".into(),
1048 counter: 42,
1049 },
1050 ],
1051 }
1052 .sign(&sk, "root-1")
1053 .unwrap()
1054 .as_bytes(),
1055 );
1056
1057 let policy = InstallPolicy {
1058 index: Some(IndexPolicy {
1059 realm: "acme",
1060 root_public_key: &pk,
1061 required: true,
1062 }),
1063 ..policy()
1064 };
1065 let outcome = install(
1066 &pin("2026.07.0"),
1067 &source,
1068 &AcceptAll,
1069 &store,
1070 &mut marks,
1071 &policy,
1072 )
1073 .expect("a pinned layer must install even when the line has moved on");
1074
1075 assert_eq!(
1078 outcome.index_high_water,
1079 Some(42),
1080 "the realm's assertion must reach the consumer even when the source \
1081 withheld the layer it refers to"
1082 );
1083 assert_eq!(outcome.layer.to_string(), "2026.07.0");
1090 assert_eq!(
1091 marks.mark(&"2026.07".parse().unwrap()),
1092 Some(1),
1093 "the mark records what this machine ACCEPTED, not what exists"
1094 );
1095 }
1096
1097 #[test]
1099 fn a_replayed_older_index_is_refused_on_the_next_install_not_only_in_theory() {
1100 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1106 let (sk, pk) = crate::verify::generate_root_keypair();
1107 let (_tmp, store, mut marks) = setup();
1108 let (bytes, blobs) = july();
1109
1110 let signed_index = |counter: u64| {
1111 LineIndex {
1112 line: "2026.07".into(),
1113 counter,
1114 issued_at: "2026-08-07T00:00:00Z".into(),
1115 layers: vec![IndexedLayer {
1116 layer: "2026.07.0".into(),
1117 digest: manifest_digest(&bytes),
1118 channel: "qualified".into(),
1119 counter: 1,
1120 }],
1121 }
1122 .sign(&sk, "root-1")
1123 .unwrap()
1124 };
1125 struct CountingSource {
1133 inner: MemorySource,
1134 blob_fetches: std::cell::Cell<usize>,
1135 }
1136 impl LayerSource for CountingSource {
1137 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
1138 self.inner.fetch_manifest(layer)
1139 }
1140 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
1141 self.blob_fetches.set(self.blob_fetches.get() + 1);
1142 self.inner.fetch_blob(digest)
1143 }
1144 fn fetch_line_index(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
1145 self.inner.fetch_line_index(line)
1146 }
1147 fn served_layers(&self, line: &str) -> Result<Option<Vec<String>>, SourceError> {
1148 self.inner.served_layers(line)
1149 }
1150 }
1151 let source_with = |envelope: &str| {
1152 let mut s = MemorySource::new().with_manifest(&bytes);
1153 for (d, b) in &blobs {
1154 s = s.with_blob(d, b);
1155 }
1156 CountingSource {
1157 inner: s.with_line_index(envelope.as_bytes()),
1158 blob_fetches: std::cell::Cell::new(0),
1159 }
1160 };
1161 let policy = InstallPolicy {
1162 index: Some(IndexPolicy {
1163 realm: "acme",
1164 root_public_key: &pk,
1165 required: true,
1166 }),
1167 ..policy()
1168 };
1169
1170 install(
1171 &pin("2026.07.0"),
1172 &source_with(&signed_index(9)),
1173 &AcceptAll,
1174 &store,
1175 &mut marks,
1176 &policy,
1177 )
1178 .expect("the first install accepts index #9");
1179
1180 let replay = source_with(&signed_index(4));
1183 let err = install(
1184 &pin("2026.07.0"),
1185 &replay,
1186 &AcceptAll,
1187 &store,
1188 &mut marks,
1189 &policy,
1190 )
1191 .expect_err("a superseded index must not be replayable over the held one");
1192 assert_eq!(
1193 replay.blob_fetches.get(),
1194 0,
1195 "the index check runs BEFORE anything is fetched or laid down — a \
1196 refusal discovered only when the cache was written would already \
1197 have installed the layer and advanced the mark"
1198 );
1199 assert!(
1200 matches!(
1201 err,
1202 InstallError::Index(crate::lineindex::IndexError::Stale {
1203 presented: 4,
1204 cached: 9,
1205 ..
1206 })
1207 ),
1208 "got: {err}"
1209 );
1210 assert!(
1211 err.to_string().contains('4') && err.to_string().contains('9'),
1212 "names both counters: {err}"
1213 );
1214
1215 assert_eq!(
1218 crate::lineindex::IndexCache::at_root(store.root())
1219 .load("2026.07")
1220 .unwrap()
1221 .unwrap()
1222 .counter,
1223 9
1224 );
1225 }
1226
1227 #[test]
1229 fn install_refuses_a_source_that_hides_an_indexed_layer() {
1230 use crate::lineindex::{IndexPolicy, IndexedLayer, LineIndex};
1233 let (sk, pk) = crate::verify::generate_root_keypair();
1234 let (_tmp, store, mut marks) = setup();
1235
1236 let (bytes, blobs) = july();
1237 let mut source = MemorySource::new().with_manifest(&bytes);
1238 for (d, b) in &blobs {
1239 source = source.with_blob(d, b);
1240 }
1241 let source = source.serving(&["2026.07.0"]).with_line_index(
1244 LineIndex {
1245 line: "2026.07".into(),
1246 counter: 1,
1247 issued_at: "2026-08-07T00:00:00Z".into(),
1248 layers: vec![IndexedLayer {
1249 layer: "2026.07.5".into(),
1250 digest: "sha256:withheld".into(),
1251 channel: "qualified".into(),
1252 counter: 5,
1253 }],
1254 }
1255 .sign(&sk, "root-1")
1256 .unwrap()
1257 .as_bytes(),
1258 );
1259
1260 let policy = InstallPolicy {
1261 index: Some(IndexPolicy {
1262 realm: "acme",
1263 root_public_key: &pk,
1264 required: true,
1265 }),
1266 ..policy()
1267 };
1268 let err = install(
1269 &pin("2026.07.0"),
1270 &source,
1271 &AcceptAll,
1272 &store,
1273 &mut marks,
1274 &policy,
1275 )
1276 .expect_err("a source hiding an indexed layer must be refused");
1277 let msg = err.to_string();
1278 assert!(msg.contains("2026.07.5"), "names the hidden layer: {msg}");
1279 assert!(store.list().unwrap().is_empty(), "no install on a refusal");
1281 }
1282}