Skip to main content

varve_core/
mirror.rs

1//! A realm served from more than one place (REQ-MIRROR-001).
2//!
3//! Today a realm names exactly one registry. If it is unreachable — an outage,
4//! a partition, an org-level package change, a blocked region — no consumer can
5//! install or update, and a fresh one cannot bootstrap at all.
6//!
7//! ## Why mirroring is safe here, and why that is the point
8//!
9//! In most package managers a mirror is a trust decision: you are choosing
10//! another party to believe. Here it is not. A layer is accepted because its
11//! manifest verifies against the realm's trust root and its payload digests
12//! match — the registry is transport, not authority, and `source.rs` says so
13//! in its first paragraph: *"A source can obtain bytes. It has no voice in
14//! whether those bytes are accepted."*
15//!
16//! So a tampered mirror fails the DSSE check and a truncated one fails the
17//! digest check, exactly as the primary would. A second source widens
18//! availability and not the trust surface, which is precisely why it is worth
19//! having.
20//!
21//! ## The clause that matters
22//!
23//! Falling through on failure is the whole feature, and it is also the way to
24//! ruin it. If "the bytes did not verify" were a reason to try the next
25//! source, then a mirror serving bad bytes would be silently skipped — and the
26//! single most interesting event this design can surface, an attacker or a
27//! corruption at one source, would become invisible. The system would look
28//! healthier the more it was attacked.
29//!
30//! That is structural here rather than a rule to remember: verification runs
31//! ABOVE this type, in the install pipeline, on whatever a source returns.
32//! This type only sees `SourceError`, which cannot express "did not verify" —
33//! there is deliberately no such variant. A future refactor that tried to make
34//! one would have to change `source.rs`'s contract to do it.
35
36use crate::source::{LayerRef, LayerSource, SourceError};
37use std::cell::RefCell;
38
39/// One named place a realm's layers can be obtained from.
40pub struct Mirror {
41    /// How the source is named to an operator — a registry URL, a path.
42    pub label: String,
43    pub source: Box<dyn LayerSource>,
44}
45
46/// An ordered list of sources for one realm.
47///
48/// Order is the realm's stated preference, not a race: a deterministic order
49/// means an operator can predict which source served them, and a run that
50/// picked a different mirror each time would make an incident unreproducible.
51pub struct Mirrors {
52    mirrors: Vec<Mirror>,
53    /// The label of the source that last answered, for reporting.
54    served_by: RefCell<Option<String>>,
55    /// Why each earlier source did not answer, for the error if none does.
56    attempts: RefCell<Vec<(String, String)>>,
57}
58
59impl Mirrors {
60    pub fn new(mirrors: Vec<Mirror>) -> Self {
61        Mirrors {
62            mirrors,
63            served_by: RefCell::new(None),
64            attempts: RefCell::new(Vec::new()),
65        }
66    }
67
68    /// Which source last answered (REQ-MIRROR-001 clause 4).
69    ///
70    /// Reportable *before* an incident, not only during one: an operator who
71    /// cannot tell that they have been on a mirror for a month cannot tell
72    /// that the primary has been down for a month.
73    pub fn served_by(&self) -> Option<String> {
74        self.served_by.borrow().clone()
75    }
76
77    /// What was tried, and what each said.
78    pub fn attempts(&self) -> Vec<(String, String)> {
79        self.attempts.borrow().clone()
80    }
81
82    pub fn labels(&self) -> Vec<&str> {
83        self.mirrors.iter().map(|m| m.label.as_str()).collect()
84    }
85
86    /// Try each source in order.
87    ///
88    /// `NotFound` and `Transport` both continue: a mirror that has not synced
89    /// this layer yet is as unhelpful as one that is unreachable, and neither
90    /// says anything about the bytes. Nothing else can arrive here —
91    /// `SourceError` has no variant meaning "did not verify", by design.
92    fn try_each<T>(
93        &self,
94        what: &str,
95        mut f: impl FnMut(&dyn LayerSource) -> Result<T, SourceError>,
96    ) -> Result<T, SourceError> {
97        self.attempts.borrow_mut().clear();
98        if self.mirrors.is_empty() {
99            return Err(SourceError::Transport(
100                "this realm declares no sources at all".into(),
101            ));
102        }
103        for m in &self.mirrors {
104            match f(m.source.as_ref()) {
105                Ok(v) => {
106                    *self.served_by.borrow_mut() = Some(m.label.clone());
107                    return Ok(v);
108                }
109                Err(e) => {
110                    self.attempts
111                        .borrow_mut()
112                        .push((m.label.clone(), e.to_string()));
113                }
114            }
115        }
116        let tried = self
117            .attempts
118            .borrow()
119            .iter()
120            .map(|(l, e)| format!("\n  {l}: {e}"))
121            .collect::<String>();
122        Err(SourceError::Transport(format!(
123            "no source could supply {what}. Tried {} source(s):{tried}",
124            self.mirrors.len()
125        )))
126    }
127}
128
129impl LayerSource for Mirrors {
130    fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
131        self.try_each("the layer manifest", |s| s.fetch_manifest(layer))
132    }
133
134    fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
135        self.try_each(&format!("blob {digest}"), |s| s.fetch_blob(digest))
136    }
137
138    fn fetch_line_status(&self, layer: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
139        // `Ok(None)` is "this source carries none", which is not an error and
140        // not a reason to keep looking on THIS method — but a mirror that has
141        // the status when the primary does not is worth reaching, so an
142        // explicit None continues while an error also continues.
143        self.try_each_optional("the line-status document", |s| s.fetch_line_status(layer))
144    }
145
146    fn fetch_line_index(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
147        self.try_each_optional("the line index", |s| s.fetch_line_index(line))
148    }
149
150    fn fetch_published_line_status(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
151        // A mirror that carries a correction the primary is missing is worth
152        // reaching — that asymmetry is the reason a yank can be suppressed by
153        // one registry and still arrive, so an explicit None keeps looking.
154        self.try_each_optional("the published line-status document", |s| {
155            s.fetch_published_line_status(line)
156        })
157    }
158
159    fn fetch_attestations(
160        &self,
161        layer: &LayerRef,
162    ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
163        self.try_each("the attestations", |s| s.fetch_attestations(layer))
164    }
165}
166
167impl Mirrors {
168    /// For the `Option`-returning fetches: a source that returns `None` has
169    /// answered honestly, but another source may still carry the document, so
170    /// keep looking and report `None` only when nobody has it.
171    fn try_each_optional(
172        &self,
173        what: &str,
174        mut f: impl FnMut(&dyn LayerSource) -> Result<Option<Vec<u8>>, SourceError>,
175    ) -> Result<Option<Vec<u8>>, SourceError> {
176        self.attempts.borrow_mut().clear();
177        let mut any_answered = false;
178        for m in &self.mirrors {
179            match f(m.source.as_ref()) {
180                Ok(Some(v)) => {
181                    *self.served_by.borrow_mut() = Some(m.label.clone());
182                    return Ok(Some(v));
183                }
184                Ok(None) => {
185                    any_answered = true;
186                    self.attempts
187                        .borrow_mut()
188                        .push((m.label.clone(), "carries none".into()));
189                }
190                Err(e) => {
191                    self.attempts
192                        .borrow_mut()
193                        .push((m.label.clone(), e.to_string()));
194                }
195            }
196        }
197        if any_answered {
198            // At least one source answered "I do not carry this", which is a
199            // legitimate absence rather than a failure to reach anyone.
200            return Ok(None);
201        }
202        let tried = self
203            .attempts
204            .borrow()
205            .iter()
206            .map(|(l, e)| format!("\n  {l}: {e}"))
207            .collect::<String>();
208        Err(SourceError::Transport(format!(
209            "no source could be reached for {what}:{tried}"
210        )))
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use std::cell::Cell;
218    use std::rc::Rc;
219
220    struct Fake {
221        manifest: Option<Vec<u8>>,
222        err: Option<SourceError>,
223        status: Option<Option<Vec<u8>>>,
224        /// The document published under the line's OWN tag, separately from
225        /// the baseline carried beside a layer (REQ-POSTDEPOSIT-001). Fake did
226        /// not model this at all, so every source fell through to the trait
227        /// default `Ok(None)` and four mutants on the Mirrors delegation were
228        /// indistinguishable from the real thing.
229        published: Option<Option<Vec<u8>>>,
230        calls: Rc<Cell<usize>>,
231    }
232
233    fn ok(bytes: &[u8]) -> Box<Fake> {
234        Box::new(Fake {
235            manifest: Some(bytes.to_vec()),
236            err: None,
237            status: Some(Some(bytes.to_vec())),
238            published: Some(Some(bytes.to_vec())),
239            calls: Rc::new(Cell::new(0)),
240        })
241    }
242    fn down(msg: &str) -> Box<Fake> {
243        Box::new(Fake {
244            manifest: None,
245            err: Some(SourceError::Transport(msg.into())),
246            status: None,
247            published: None,
248            calls: Rc::new(Cell::new(0)),
249        })
250    }
251    fn empty() -> Box<Fake> {
252        Box::new(Fake {
253            manifest: None,
254            err: Some(SourceError::NotFound("layer".into())),
255            status: Some(None),
256            published: Some(None),
257            calls: Rc::new(Cell::new(0)),
258        })
259    }
260
261    impl LayerSource for Fake {
262        fn fetch_manifest(&self, _l: &LayerRef) -> Result<Vec<u8>, SourceError> {
263            self.calls.set(self.calls.get() + 1);
264            match (&self.manifest, &self.err) {
265                (Some(m), _) => Ok(m.clone()),
266                (None, Some(e)) => Err(clone_err(e)),
267                _ => Err(SourceError::NotFound("x".into())),
268            }
269        }
270        fn fetch_blob(&self, _d: &str) -> Result<Vec<u8>, SourceError> {
271            self.fetch_manifest(&LayerRef::Digest("x".into()))
272        }
273        fn fetch_published_line_status(&self, _l: &str) -> Result<Option<Vec<u8>>, SourceError> {
274            self.calls.set(self.calls.get() + 1);
275            match (&self.published, &self.err) {
276                (Some(s), _) => Ok(s.clone()),
277                (None, Some(e)) => Err(clone_err(e)),
278                (None, None) => Ok(None),
279            }
280        }
281
282        fn fetch_line_status(&self, _l: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
283            self.calls.set(self.calls.get() + 1);
284            match (&self.status, &self.err) {
285                (Some(s), _) => Ok(s.clone()),
286                (None, Some(e)) => Err(clone_err(e)),
287                _ => Ok(None),
288            }
289        }
290        fn fetch_line_index(&self, _line: &str) -> Result<Option<Vec<u8>>, SourceError> {
291            self.fetch_line_status(&LayerRef::Digest("x".into()))
292        }
293        fn fetch_attestations(
294            &self,
295            _l: &LayerRef,
296        ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
297            self.calls.set(self.calls.get() + 1);
298            match (&self.manifest, &self.err) {
299                (Some(_), _) => Ok(Vec::new()),
300                (None, Some(e)) => Err(clone_err(e)),
301                _ => Ok(Vec::new()),
302            }
303        }
304    }
305
306    fn clone_err(e: &SourceError) -> SourceError {
307        match e {
308            SourceError::NotFound(s) => SourceError::NotFound(s.clone()),
309            SourceError::Transport(s) => SourceError::Transport(s.clone()),
310            other => SourceError::Transport(other.to_string()),
311        }
312    }
313
314    fn mirrors(v: Vec<(&str, Box<Fake>)>) -> Mirrors {
315        Mirrors::new(
316            v.into_iter()
317                .map(|(l, s)| Mirror {
318                    label: l.into(),
319                    source: s as Box<dyn LayerSource>,
320                })
321                .collect(),
322        )
323    }
324
325    // rivet: verifies REQ-MIRROR-001
326    #[test]
327    fn the_first_source_that_answers_serves_the_layer() {
328        let m = mirrors(vec![("primary", ok(b"manifest")), ("mirror", ok(b"other"))]);
329        assert_eq!(
330            m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
331            b"manifest".to_vec()
332        );
333        assert_eq!(m.served_by().as_deref(), Some("primary"));
334    }
335
336    /// The whole point: an unreachable primary must not stop an install.
337    // rivet: verifies REQ-MIRROR-001
338    #[test]
339    fn an_unreachable_source_falls_through_to_the_next() {
340        let m = mirrors(vec![
341            ("primary", down("dial tcp: no such host")),
342            ("mirror", ok(b"manifest")),
343        ]);
344        assert_eq!(
345            m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
346            b"manifest".to_vec()
347        );
348        assert_eq!(m.served_by().as_deref(), Some("mirror"));
349    }
350
351    /// A mirror that has not synced this layer is as unhelpful as one that is
352    /// down, and neither says anything about the bytes.
353    // rivet: verifies REQ-MIRROR-001
354    #[test]
355    fn a_source_that_lacks_the_layer_falls_through_too() {
356        let m = mirrors(vec![("primary", empty()), ("mirror", ok(b"manifest"))]);
357        assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_ok());
358        assert_eq!(m.served_by().as_deref(), Some("mirror"));
359    }
360
361    /// Clause 4. An operator who cannot tell they have been on a mirror for a
362    /// month cannot tell the primary has been down for a month.
363    // rivet: verifies REQ-MIRROR-001
364    #[test]
365    fn which_source_served_the_layer_is_reportable() {
366        let m = mirrors(vec![
367            ("oci://primary", down("503")),
368            ("oci://backup", ok(b"m")),
369        ]);
370        m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
371        assert_eq!(m.served_by().as_deref(), Some("oci://backup"));
372        let attempts = m.attempts();
373        assert_eq!(attempts.len(), 1);
374        assert_eq!(attempts[0].0, "oci://primary");
375        assert!(attempts[0].1.contains("503"), "{attempts:?}");
376    }
377
378    /// When nothing answers, the error names every source and what each said —
379    /// "the registry is down" is not actionable when there were three.
380    // rivet: verifies REQ-MIRROR-001
381    #[test]
382    fn when_no_source_answers_the_error_names_all_of_them() {
383        let m = mirrors(vec![
384            ("oci://a", down("no such host")),
385            ("oci://b", down("503 Service Unavailable")),
386        ]);
387        let e = m
388            .fetch_manifest(&LayerRef::Digest("d".into()))
389            .expect_err("must fail");
390        let msg = e.to_string();
391        assert!(
392            msg.contains("oci://a") && msg.contains("no such host"),
393            "{msg}"
394        );
395        assert!(msg.contains("oci://b") && msg.contains("503"), "{msg}");
396        assert!(msg.contains("2 source(s)"), "{msg}");
397        assert!(m.served_by().is_none());
398    }
399
400    /// A realm with no sources is a configuration error, not an empty loop
401    /// that silently reports "not found".
402    // rivet: verifies REQ-MIRROR-001
403    #[test]
404    fn a_realm_with_no_sources_says_so() {
405        let m = Mirrors::new(Vec::new());
406        let e = m
407            .fetch_manifest(&LayerRef::Digest("d".into()))
408            .expect_err("must fail");
409        assert!(e.to_string().contains("no sources at all"), "{e}");
410    }
411
412    /// A later source is not consulted once an earlier one answers — order is
413    /// a stated preference, and an operator must be able to predict it.
414    // rivet: verifies REQ-MIRROR-001
415    #[test]
416    fn a_source_after_the_one_that_answered_is_not_consulted() {
417        let second = ok(b"second");
418        let counter = Rc::clone(&second.calls);
419        let m = mirrors(vec![("primary", ok(b"first")), ("mirror", second)]);
420        m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
421        assert_eq!(counter.get(), 0, "the later source was consulted anyway");
422    }
423
424    /// Every fetch must fall through, not just the manifest. Each is a
425    /// separate delegation, and a copy-paste slip in one of them would fail
426    /// over on the manifest and then stall on the blob — an install that gets
427    /// halfway and dies is worse than one that never starts.
428    // rivet: verifies REQ-MIRROR-001
429    #[test]
430    fn every_kind_of_fetch_falls_through_not_only_the_manifest() {
431        let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"bytes"))]);
432        assert_eq!(m.fetch_blob("sha256:x").unwrap(), b"bytes".to_vec());
433        assert_eq!(m.served_by().as_deref(), Some("mirror"));
434
435        let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"index"))]);
436        assert_eq!(
437            m.fetch_line_index("2026.09").unwrap(),
438            Some(b"index".to_vec())
439        );
440        assert_eq!(m.served_by().as_deref(), Some("mirror"));
441
442        let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"att"))]);
443        assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_ok());
444        assert_eq!(m.served_by().as_deref(), Some("mirror"));
445    }
446
447    /// ...and every kind must REFUSE when nobody answers, rather than
448    /// returning an empty result that reads as "there is none".
449    // rivet: verifies REQ-MIRROR-001
450    #[test]
451    fn every_kind_of_fetch_refuses_when_no_source_answers() {
452        let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
453        assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_err());
454        assert!(m.fetch_blob("sha256:x").is_err());
455        assert!(m.fetch_line_index("2026.09").is_err());
456        assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_err());
457        assert!(
458            m.served_by().is_none(),
459            "nothing served, yet a source is named"
460        );
461    }
462
463    /// The labels are what an operator is shown; an empty list would make
464    /// every mirror diagnostic say nothing.
465    // rivet: verifies REQ-MIRROR-001
466    #[test]
467    fn the_configured_sources_are_reportable_in_order() {
468        let m = mirrors(vec![("oci://a", ok(b"x")), ("oci://b", ok(b"y"))]);
469        assert_eq!(m.labels(), vec!["oci://a", "oci://b"]);
470        assert_eq!(Mirrors::new(Vec::new()).labels(), Vec::<&str>::new());
471    }
472
473    /// `Ok(None)` from every source is a legitimate absence — line-status is
474    /// updatable evidence and some layers have none. That must not be reported
475    /// as "no source could be reached".
476    // rivet: verifies REQ-MIRROR-001
477    #[test]
478    fn a_document_no_source_carries_is_absent_not_unreachable() {
479        let m = mirrors(vec![("a", empty()), ("b", empty())]);
480        assert_eq!(
481            m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
482            None
483        );
484    }
485
486    /// ...but if nobody could be REACHED, that is not absence.
487    // rivet: verifies REQ-MIRROR-001
488    #[test]
489    fn a_document_nobody_could_be_asked_about_is_not_reported_as_absent() {
490        let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
491        let e = m
492            .fetch_line_status(&LayerRef::Digest("d".into()))
493            .expect_err("must not report absence");
494        assert!(e.to_string().contains("could be reached"), "{e}");
495    }
496
497    /// A mirror carrying a CORRECTION the primary is missing must be reached.
498    /// That asymmetry is the reason a yank can be suppressed by one registry
499    /// and still arrive, so an explicit None has to keep looking rather than
500    /// end the search.
501    // rivet: verifies REQ-POSTDEPOSIT-001
502    #[test]
503    fn a_mirror_carrying_a_correction_is_reached_past_one_that_has_none() {
504        let m = mirrors(vec![("a", empty()), ("b", ok(b"correction"))]);
505        assert_eq!(
506            m.fetch_published_line_status("2026.09").unwrap(),
507            Some(b"correction".to_vec())
508        );
509        assert_eq!(m.served_by().as_deref(), Some("b"));
510    }
511
512    /// Nobody reachable is not "there is no correction". Reporting absence
513    /// here would let a network fault look exactly like a line with nothing
514    /// to say — and a suppressed yank is the difference.
515    // rivet: verifies REQ-POSTDEPOSIT-001
516    #[test]
517    fn a_correction_nobody_could_be_asked_about_is_not_reported_as_absent() {
518        let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
519        let e = m
520            .fetch_published_line_status("2026.09")
521            .expect_err("must not report absence");
522        assert!(e.to_string().contains("could be reached"), "{e}");
523    }
524
525    /// A line with genuinely nothing published is `Ok(None)` — distinct from
526    /// both of the above.
527    // rivet: verifies REQ-POSTDEPOSIT-001
528    #[test]
529    fn a_line_with_no_correction_published_is_absent_not_an_error() {
530        let m = mirrors(vec![("a", empty()), ("b", empty())]);
531        assert_eq!(m.fetch_published_line_status("2026.09").unwrap(), None);
532    }
533
534    /// A source that carries the status is preferred over one that does not,
535    /// even when the one that does not comes first.
536    // rivet: verifies REQ-MIRROR-001
537    #[test]
538    fn a_source_carrying_the_document_is_reached_past_one_that_does_not() {
539        let m = mirrors(vec![("a", empty()), ("b", ok(b"status"))]);
540        assert_eq!(
541            m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
542            Some(b"status".to_vec())
543        );
544        assert_eq!(m.served_by().as_deref(), Some("b"));
545    }
546}