Skip to main content

varve_core/
store.rs

1//! The core — the local content-addressed store (REQ-COEXIST-001, DD-006).
2//!
3//! ```text
4//! <root>/core/sha256-<hex>/          # one layer, keyed by its manifest digest
5//!   layer.json                       # the layer manifest, kept for verify/archive
6//!   bin/<tool>                       # the tools — dispatched by name
7//!   payloads/<name>/<version>        # everything else — held by name AND version
8//! ```
9//!
10//! The split is REQ-STORE-002. A tool is dispatched by name (`varve which`,
11//! `varve run`, the argv[0] shims), so a name must resolve to exactly one
12//! binary and `bin/<name>` is right. A crate, a WIT package, an SDK or a wasm
13//! component is not dispatched at all, and a dependency graph ordinarily holds
14//! several versions of one name — laying those down by name alone made the
15//! second entry silently overwrite the first's bytes.
16//!
17//! Keyed by manifest digest, so layers coexist by construction: installing
18//! August cannot disturb July, and selecting either costs no download.
19//! The store is written at lay-down time and read-only ever after — nothing
20//! in resolution or listing mutates it (REQ-SCOPE-001's read-only half).
21
22use std::collections::BTreeMap;
23use std::path::{Path, PathBuf};
24
25use serde::Deserialize;
26use sha2::{Digest, Sha256};
27
28use crate::layer::LayerId;
29use crate::manifest::ManifestEntry;
30
31/// The directory holding non-dispatchable payloads, keyed by name AND version.
32pub const PAYLOAD_DIR: &str = "payloads";
33
34/// One payload to lay down. What it *is* decides where it lands: a dispatchable
35/// payload is placed by name, everything else by name and version.
36#[derive(Debug, Clone, Copy)]
37pub struct Payload<'a> {
38    pub name: &'a str,
39    /// The version from the signed manifest. Absent only for pre-kind layers
40    /// and for tools, which are not keyed by version.
41    pub version: Option<&'a str>,
42    /// Dispatched by name — see `PayloadKind::is_dispatchable`.
43    pub dispatchable: bool,
44    pub bytes: &'a [u8],
45}
46
47impl<'a> Payload<'a> {
48    /// A dispatchable tool binary: `bin/<name>`, the original layout.
49    pub fn tool(name: &'a str, bytes: &'a [u8]) -> Self {
50        Payload {
51            name,
52            version: None,
53            dispatchable: true,
54            bytes,
55        }
56    }
57}
58
59/// Is this manifest entry dispatched by name (REQ-STORE-002 clause 1)?
60///
61/// An entry with no kind annotation is a `tool` (pre-kind layers), so it is.
62/// An entry whose kind THIS build does not recognise is not: varve cannot
63/// dispatch what it cannot classify, and placing an unknown kind by name alone
64/// is exactly the overwrite this requirement exists to prevent — a newer
65/// varve's versioned kind must not collapse onto one path here.
66pub fn entry_is_dispatchable(entry: &ManifestEntry) -> bool {
67    matches!(entry.kind(), Ok(kind) if kind.is_dispatchable())
68}
69
70/// The version a manifest entry declares, if any.
71pub fn entry_version(entry: &ManifestEntry) -> Option<&str> {
72    entry
73        .annotations
74        .get("eu.pulseengine.tool.version")
75        .map(String::as_str)
76}
77
78/// Refuse a name or version that is not a single, safe path component. These
79/// strings come out of a signed manifest, but "signed" means "attributable",
80/// not "benign": a realm root signing `../../evil` must not be able to place
81/// bytes outside the layer it is laying down.
82fn safe_component(what: &'static str, value: &str) -> Result<(), StoreError> {
83    let bad = |why: &str| {
84        Err(StoreError::UnsafeComponent {
85            what,
86            value: value.to_string(),
87            why: why.to_string(),
88        })
89    };
90    if value.is_empty() {
91        return bad("empty");
92    }
93    if value == "." || value == ".." {
94        return bad("a relative path element");
95    }
96    if let Some(c) = value
97        .chars()
98        .find(|c| matches!(c, '/' | '\\' | '\0') || c.is_control())
99    {
100        return bad(&format!("contains {c:?}"));
101    }
102    Ok(())
103}
104
105/// Where a payload lives inside a layer root, relative to it (clause 2).
106///
107/// Dispatchable → `bin/<name>`, unchanged and byte-compatible with every layer
108/// installed before this requirement. Non-dispatchable with a version →
109/// `payloads/<name>/<version>`, so `serde@1.0.200` and `serde@1.0.210` are two
110/// files, not one file written twice. A non-dispatchable payload with NO
111/// version keeps the legacy `bin/<name>` place — that is what a pre-kind layer
112/// looks like on disk, and the collision guard in `lay_down` still refuses to
113/// let two of them land on one path.
114pub fn payload_rel_path(
115    dispatchable: bool,
116    name: &str,
117    version: Option<&str>,
118) -> Result<PathBuf, StoreError> {
119    safe_component("payload name", name)?;
120    match (dispatchable, version) {
121        (false, Some(version)) => {
122            safe_component("payload version", version)?;
123            Ok(PathBuf::from(PAYLOAD_DIR).join(name).join(version))
124        }
125        _ => Ok(PathBuf::from("bin").join(name)),
126    }
127}
128
129/// The minimal slice of a layer manifest the store needs: the annotations
130/// carrying the layer identity. Everything else is preserved verbatim in
131/// `layer.json` for later verification and archiving.
132#[derive(Debug, Clone, Deserialize)]
133struct ManifestEnvelope {
134    #[serde(default)]
135    annotations: BTreeMap<String, String>,
136}
137
138/// One layer present in the core.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct InstalledLayer {
141    /// `sha256:<hex>` of the manifest bytes — the store key.
142    pub digest: String,
143    /// The layer identity from the manifest annotations.
144    pub layer: LayerId,
145    /// The channel annotation (`qualified` / `rolling`), verbatim.
146    pub channel: String,
147    /// Root directory of this layer in the core.
148    pub root: PathBuf,
149}
150
151/// The core store rooted at a directory (defaults to `~/.varve` in the CLI;
152/// injectable here so tests and future tools own their roots).
153#[derive(Debug, Clone)]
154pub struct Store {
155    root: PathBuf,
156}
157
158/// Store failures. Note what is *absent*: there is no variant for "fell back
159/// to another layer" — the API cannot express fallback.
160#[derive(Debug, thiserror::Error)]
161pub enum StoreError {
162    #[error("io error at {path}")]
163    Io {
164        path: String,
165        #[source]
166        source: std::io::Error,
167    },
168    #[error("{path}: layer.json is not a valid layer manifest: {reason}")]
169    BadManifest { path: String, reason: String },
170    #[error("{what} {value:?} is not a usable path component ({why}) — refusing to lay it down")]
171    UnsafeComponent {
172        what: &'static str,
173        value: String,
174        why: String,
175    },
176    #[error(
177        "two payloads of this layer both claim {path} ('{first}' and '{second}') — refusing to \
178         lay one down over the other. A layer may hold several versions of one name, but each \
179         must be a distinct payload; two entries with one identity would mean the wrong bytes \
180         land under the right name."
181    )]
182    Collision {
183        path: String,
184        first: String,
185        second: String,
186    },
187}
188
189impl Store {
190    pub fn at(root: impl Into<PathBuf>) -> Self {
191        Store { root: root.into() }
192    }
193
194    pub fn root(&self) -> &Path {
195        &self.root
196    }
197
198    /// Lay a layer down in the core from its manifest bytes and tool
199    /// binaries. Returns the manifest digest (`sha256:<hex>`) — the store key.
200    ///
201    /// The dispatchable-only convenience over [`Store::lay_down_payloads`],
202    /// which is the single writer.
203    pub fn lay_down(
204        &self,
205        manifest_bytes: &[u8],
206        tools: &[(&str, &[u8])],
207    ) -> Result<String, StoreError> {
208        let payloads: Vec<Payload<'_>> = tools
209            .iter()
210            .map(|(name, bytes)| Payload::tool(name, bytes))
211            .collect();
212        self.lay_down_payloads(manifest_bytes, &payloads)
213    }
214
215    /// Lay a layer down in the core from its manifest bytes and its payloads.
216    /// Returns the manifest digest (`sha256:<hex>`) — the store key.
217    ///
218    /// This is the write path shared by the installer and by tests; nothing
219    /// else writes to the core. Two payloads that would land on ONE path are
220    /// refused (`StoreError::Collision`) rather than written in turn: relaxing
221    /// the deposit-time identity check without this would turn a clean error
222    /// into silent data loss — the second entry's bytes under the first's name,
223    /// with verification then failing on the *other* entry and nothing
224    /// explaining why (REQ-STORE-002).
225    pub fn lay_down_payloads(
226        &self,
227        manifest_bytes: &[u8],
228        payloads: &[Payload<'_>],
229    ) -> Result<String, StoreError> {
230        let digest = manifest_digest(manifest_bytes);
231        let entry = self.core_dir().join(digest.replace(':', "-"));
232        let io = |path: &Path, source: std::io::Error| StoreError::Io {
233            path: path.display().to_string(),
234            source,
235        };
236
237        // Resolve every destination BEFORE writing anything: an unusable name
238        // or a collision must leave the core untouched, not half-written.
239        let mut placed: BTreeMap<PathBuf, String> = BTreeMap::new();
240        let mut plan: Vec<(PathBuf, &Payload<'_>)> = Vec::new();
241        for payload in payloads {
242            let rel = payload_rel_path(payload.dispatchable, payload.name, payload.version)?;
243            let who = match payload.version {
244                Some(v) => format!("{}@{v}", payload.name),
245                None => payload.name.to_string(),
246            };
247            if let Some(first) = placed.get(&rel) {
248                return Err(StoreError::Collision {
249                    path: rel.display().to_string(),
250                    first: first.clone(),
251                    second: who,
252                });
253            }
254            placed.insert(rel.clone(), who);
255            plan.push((rel, payload));
256        }
257
258        let bin = entry.join("bin");
259        std::fs::create_dir_all(&bin).map_err(|e| io(&bin, e))?;
260        let manifest_path = entry.join("layer.json");
261        std::fs::write(&manifest_path, manifest_bytes).map_err(|e| io(&manifest_path, e))?;
262        for (rel, payload) in plan {
263            let path = entry.join(rel);
264            if let Some(parent) = path.parent() {
265                std::fs::create_dir_all(parent).map_err(|e| io(parent, e))?;
266            }
267            std::fs::write(&path, payload.bytes).map_err(|e| io(&path, e))?;
268            #[cfg(unix)]
269            {
270                use std::os::unix::fs::PermissionsExt;
271                // Only what is dispatched gets the execute bit. A `.crate`
272                // tarball or a WIT package is data varve hands to another tool.
273                let mode = if payload.dispatchable { 0o755 } else { 0o644 };
274                std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))
275                    .map_err(|e| io(&path, e))?;
276            }
277        }
278        Ok(digest)
279    }
280
281    /// Every layer present in the core, in stable (digest) order.
282    pub fn list(&self) -> Result<Vec<InstalledLayer>, StoreError> {
283        let core = self.core_dir();
284        if !core.exists() {
285            return Ok(Vec::new());
286        }
287        let mut names: Vec<String> = std::fs::read_dir(&core)
288            .map_err(|e| StoreError::Io {
289                path: core.display().to_string(),
290                source: e,
291            })?
292            .filter_map(|e| e.ok())
293            .filter(|e| e.path().is_dir())
294            .map(|e| e.file_name().to_string_lossy().into_owned())
295            .filter(|n| n.starts_with("sha256-"))
296            .collect();
297        names.sort();
298        names
299            .into_iter()
300            .map(|name| self.read_entry(&name.replacen('-', ":", 1)))
301            .collect()
302    }
303
304    /// Look up a layer by manifest digest (`sha256:<hex>`).
305    /// The varve root this store lives under: itself, or the parent of a realm
306    /// partition (`<root>/realms/<fingerprint>`).
307    pub fn varve_root(&self) -> std::path::PathBuf {
308        let root = self.root();
309        if root
310            .parent()
311            .and_then(|p| p.file_name())
312            .is_some_and(|n| n == "realms")
313        {
314            root.parent()
315                .and_then(|p| p.parent())
316                .map(|p| p.to_path_buf())
317                .unwrap_or_else(|| root.to_path_buf())
318        } else {
319            root.to_path_buf()
320        }
321    }
322
323    /// Find a layer by digest in ANY partition under this varve root — the
324    /// top-level core or any realm's. A digest is content-addressed, so where
325    /// it happens to live does not change what it is; a cross-realm composition
326    /// include is installed under the INCLUDED realm's fingerprint, not the
327    /// including project's, and looking only in one partition reported it as
328    /// missing while `list` showed it installed (REQ-STORE-001).
329    ///
330    /// Locating a layer is not accepting it: the caller still verifies it
331    /// against the trust root of the realm that vouches for it.
332    pub fn find_anywhere(
333        &self,
334        digest: &str,
335    ) -> Result<Option<(Store, InstalledLayer)>, StoreError> {
336        if let Some(entry) = self.get(digest)? {
337            return Ok(Some((self.clone(), entry)));
338        }
339        let root = self.varve_root();
340        // The top-level core, then every realm partition, in a stable order.
341        let mut candidates = vec![Store::at(&root)];
342        if let Ok(rd) = std::fs::read_dir(root.join("realms")) {
343            let mut parts: Vec<std::path::PathBuf> =
344                rd.filter_map(|e| e.ok()).map(|e| e.path()).collect();
345            parts.sort();
346            candidates.extend(parts.into_iter().map(Store::at));
347        }
348        for candidate in candidates {
349            if candidate.root() == self.root() {
350                continue;
351            }
352            if let Some(entry) = candidate.get(digest)? {
353                return Ok(Some((candidate, entry)));
354            }
355        }
356        Ok(None)
357    }
358
359    pub fn get(&self, digest: &str) -> Result<Option<InstalledLayer>, StoreError> {
360        let entry = self.core_dir().join(digest.replace(':', "-"));
361        if !entry.join("layer.json").is_file() {
362            return Ok(None);
363        }
364        self.read_entry(digest).map(Some)
365    }
366
367    /// Path of one tool's binary within an installed layer, if present.
368    /// Dispatch is by name, so this takes a bare name — see `entry_path` for
369    /// payloads that are held rather than dispatched.
370    pub fn tool_path(&self, layer: &InstalledLayer, tool: &str) -> Option<PathBuf> {
371        let path = layer.root.join("bin").join(tool);
372        path.is_file().then_some(path)
373    }
374
375    /// Path of the bytes one MANIFEST ENTRY refers to within an installed
376    /// layer, if present. This is the read side of `lay_down_payloads`, and
377    /// every consumer of a layer's bytes (`verify`, `archive`, the export
378    /// adapters) goes through it so the two cannot drift.
379    ///
380    /// Backward compatibility: a layer installed BEFORE REQ-STORE-002 holds its
381    /// crates at `bin/<name>`, so a versioned payload that is not at its
382    /// versioned path falls back there. Such a layer can hold only one version
383    /// per name — the old deposit check made sure of it — so the fallback is
384    /// unambiguous.
385    pub fn entry_path(&self, layer: &InstalledLayer, entry: &ManifestEntry) -> Option<PathBuf> {
386        let name = entry.annotations.get("eu.pulseengine.tool")?;
387        let dispatchable = entry_is_dispatchable(entry);
388        let rel = payload_rel_path(dispatchable, name, entry_version(entry)).ok()?;
389        let path = layer.root.join(rel);
390        if path.is_file() {
391            return Some(path);
392        }
393        (!dispatchable)
394            .then(|| layer.root.join("bin").join(name))
395            .filter(|legacy| legacy.is_file())
396    }
397
398    fn core_dir(&self) -> PathBuf {
399        self.root.join("core")
400    }
401
402    fn read_entry(&self, digest: &str) -> Result<InstalledLayer, StoreError> {
403        let root = self.core_dir().join(digest.replace(':', "-"));
404        let manifest_path = root.join("layer.json");
405        let bad = |reason: String| StoreError::BadManifest {
406            path: manifest_path.display().to_string(),
407            reason,
408        };
409        let bytes = std::fs::read(&manifest_path).map_err(|source| StoreError::Io {
410            path: manifest_path.display().to_string(),
411            source,
412        })?;
413        let envelope: ManifestEnvelope =
414            serde_json::from_slice(&bytes).map_err(|e| bad(e.to_string()))?;
415        let layer_str = envelope
416            .annotations
417            .get("eu.pulseengine.varve.layer")
418            .ok_or_else(|| bad("missing eu.pulseengine.varve.layer annotation".into()))?;
419        let layer: LayerId = layer_str
420            .parse()
421            .map_err(|e: crate::layer::LayerIdError| bad(e.to_string()))?;
422        let channel = envelope
423            .annotations
424            .get("eu.pulseengine.varve.channel")
425            .cloned()
426            .unwrap_or_default();
427        Ok(InstalledLayer {
428            digest: digest.to_string(),
429            layer,
430            channel,
431            root,
432        })
433    }
434}
435
436/// Compute the store key for manifest bytes: `sha256:<hex>`.
437impl Store {
438    /// Every tool name the layer's SIGNED manifest carries, whatever this host
439    /// laid down. Used to tell "the pin asks for something that was never in
440    /// this layer" apart from "the install is incomplete" — the two need
441    /// opposite advice, and conflating them produced an error whose stated fix
442    /// could not work.
443    pub fn manifest_tool_names(&self, layer: &InstalledLayer) -> Result<Vec<String>, StoreError> {
444        let payload = std::fs::read(layer.root.join("layer.json")).map_err(|e| StoreError::Io {
445            path: layer.root.join("layer.json").display().to_string(),
446            source: e,
447        })?;
448        let json: serde_json::Value = match serde_json::from_slice(&payload) {
449            Ok(j) => j,
450            Err(_) => return Ok(Vec::new()),
451        };
452        Ok(json["manifests"]
453            .as_array()
454            .map(|es| {
455                es.iter()
456                    .filter_map(|e| e["annotations"]["eu.pulseengine.tool"].as_str())
457                    .map(str::to_string)
458                    .collect()
459            })
460            .unwrap_or_default())
461    }
462}
463
464pub fn manifest_digest(bytes: &[u8]) -> String {
465    format!("sha256:{}", hex::encode(Sha256::digest(bytes)))
466}
467
468#[cfg(test)]
469pub(crate) mod fixtures {
470    /// A minimal, valid layer manifest for tests.
471    pub fn manifest(layer: &str, channel: &str) -> Vec<u8> {
472        format!(
473            r#"{{
474  "schemaVersion": 2,
475  "mediaType": "application/vnd.oci.image.index.v1+json",
476  "artifactType": "application/vnd.pulseengine.varve.layer.v1+json",
477  "annotations": {{
478    "eu.pulseengine.varve.layer": "{layer}",
479    "eu.pulseengine.varve.channel": "{channel}"
480  }},
481  "manifests": []
482}}"#
483        )
484        .into_bytes()
485    }
486
487    /// A layer from before channel annotations existed. The resolver's channel
488    /// guard must exempt it: a pre-channel layer states no channel, so it
489    /// contradicts no pin.
490    pub fn manifest_without_channel(layer: &str) -> Vec<u8> {
491        format!(
492            r#"{{
493  "schemaVersion": 2,
494  "mediaType": "application/vnd.oci.image.index.v1+json",
495  "artifactType": "application/vnd.pulseengine.varve.layer.v1+json",
496  "annotations": {{
497    "eu.pulseengine.varve.layer": "{layer}"
498  }},
499  "manifests": []
500}}"#
501        )
502        .into_bytes()
503    }
504}
505
506#[cfg(test)]
507mod partition_tests {
508    use super::*;
509
510    // rivet: verifies REQ-STORE-001
511    #[test]
512    fn a_layer_is_found_in_any_partition_under_the_same_root() {
513        // A cross-realm composition include lives under the INCLUDED realm's
514        // fingerprint, not the including project's. Looking in one partition
515        // reported it missing while `list` showed it installed — `verify`,
516        // `which` and `run` disagreed with `list`, and the corrective advice
517        // failed (REQ-STORE-001).
518        let tmp = tempfile::tempdir().unwrap();
519        let root = tmp.path();
520        let mine = Store::at(root.join("realms").join("aaaa"));
521        let theirs = Store::at(root.join("realms").join("bbbb"));
522        let digest = theirs
523            .lay_down(
524                &fixtures::manifest("2026.08.0", "qualified"),
525                &[("btool", b"b")],
526            )
527            .unwrap();
528
529        // My own partition does not have it…
530        assert!(mine.get(&digest).unwrap().is_none());
531        // …but it is installed under this varve root, and locating it says so.
532        let (owner, entry) = mine.find_anywhere(&digest).unwrap().expect("found");
533        assert_eq!(entry.digest, digest);
534        assert_eq!(owner.root(), theirs.root(), "found in the owning partition");
535        // The tool resolves through the partition that actually holds it.
536        assert!(owner.tool_path(&entry, "btool").is_some());
537    }
538
539    // rivet: verifies REQ-STORE-001
540    #[test]
541    fn the_varve_root_is_recovered_from_a_realm_partition() {
542        let tmp = tempfile::tempdir().unwrap();
543        let root = tmp.path();
544        assert_eq!(
545            Store::at(root.join("realms").join("ffff")).varve_root(),
546            root.to_path_buf()
547        );
548        // A non-partition store is its own root.
549        assert_eq!(Store::at(root).varve_root(), root.to_path_buf());
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556
557    fn store() -> (tempfile::TempDir, Store) {
558        let tmp = tempfile::tempdir().unwrap();
559        let store = Store::at(tmp.path().join("varve-root"));
560        (tmp, store)
561    }
562
563    // rivet: verifies REQ-COEXIST-001
564    #[test]
565    fn two_layers_coexist_and_are_independently_addressable() {
566        let (_tmp, store) = store();
567        let july = fixtures::manifest("2026.07.0", "qualified");
568        let august = fixtures::manifest("2026.08.0", "qualified");
569        let d_july = store.lay_down(&july, &[("synth", b"july-synth")]).unwrap();
570        let d_august = store
571            .lay_down(&august, &[("synth", b"august-synth")])
572            .unwrap();
573        assert_ne!(d_july, d_august);
574
575        let listed = store.list().unwrap();
576        assert_eq!(listed.len(), 2);
577
578        let july_entry = store.get(&d_july).unwrap().unwrap();
579        let august_entry = store.get(&d_august).unwrap().unwrap();
580        assert_eq!(july_entry.layer.to_string(), "2026.07.0");
581        assert_eq!(august_entry.layer.to_string(), "2026.08.0");
582
583        // The same tool name resolves to different bytes per layer — the
584        // wohl-on-July-while-relay-on-August afternoon, on one machine.
585        let july_synth = store.tool_path(&july_entry, "synth").unwrap();
586        let august_synth = store.tool_path(&august_entry, "synth").unwrap();
587        assert_eq!(std::fs::read(july_synth).unwrap(), b"july-synth");
588        assert_eq!(std::fs::read(august_synth).unwrap(), b"august-synth");
589    }
590
591    // rivet: verifies REQ-COEXIST-001
592    #[test]
593    fn store_key_is_the_manifest_digest() {
594        let (_tmp, store) = store();
595        let bytes = fixtures::manifest("2026.07.0", "qualified");
596        let digest = store.lay_down(&bytes, &[]).unwrap();
597        assert_eq!(digest, manifest_digest(&bytes));
598        let entry = store.get(&digest).unwrap().unwrap();
599        assert!(
600            entry
601                .root
602                .ends_with(format!("core/{}", digest.replace(':', "-"))),
603            "entry rooted at digest-keyed dir, got {}",
604            entry.root.display()
605        );
606        // layer.json preserved verbatim for verify/archive.
607        assert_eq!(std::fs::read(entry.root.join("layer.json")).unwrap(), bytes);
608    }
609
610    // rivet: verifies REQ-PIN-001
611    #[test]
612    fn missing_layer_is_none_not_an_invention() {
613        let (_tmp, store) = store();
614        assert_eq!(
615            store
616                .get("sha256:0000000000000000000000000000000000000000000000000000000000000000")
617                .unwrap(),
618            None
619        );
620        assert_eq!(store.list().unwrap(), vec![]);
621    }
622
623    /// A manifest entry as a signed layer carries it.
624    fn entry(name: &str, version: Option<&str>, kind: Option<&str>) -> ManifestEntry {
625        let mut annotations = BTreeMap::new();
626        annotations.insert("eu.pulseengine.tool".to_string(), name.to_string());
627        if let Some(v) = version {
628            annotations.insert("eu.pulseengine.tool.version".to_string(), v.to_string());
629        }
630        if let Some(k) = kind {
631            annotations.insert(crate::kind::ANN_KIND.to_string(), k.to_string());
632        }
633        ManifestEntry {
634            digest: manifest_digest(name.as_bytes()),
635            annotations,
636        }
637    }
638
639    fn held<'a>(name: &'a str, version: &'a str, bytes: &'a [u8]) -> Payload<'a> {
640        Payload {
641            name,
642            version: Some(version),
643            dispatchable: false,
644            bytes,
645        }
646    }
647
648    // rivet: verifies REQ-STORE-002
649    #[test]
650    fn two_versions_of_one_name_are_two_files_neither_overwriting_the_other() {
651        // Clause 2. `lay_down` wrote every payload to `bin/<name>`, so relaxing
652        // the deposit check alone would have made the second serde silently
653        // overwrite the first: the WRONG BYTES land under the right name, and
654        // verification then fails on the OTHER entry with nothing explaining
655        // why. A clean error turned into silent data loss.
656        let (_tmp, store) = store();
657        let manifest = fixtures::manifest("2026.08.0", "qualified");
658        let digest = store
659            .lay_down_payloads(
660                &manifest,
661                &[
662                    held("serde", "1.0.200", b"serde-200-bytes"),
663                    held("serde", "1.0.210", b"serde-210-bytes"),
664                ],
665            )
666            .unwrap();
667        let layer = store.get(&digest).unwrap().unwrap();
668
669        // Each version is its OWN file, holding its OWN bytes.
670        let two_hundred = layer.root.join("payloads/serde/1.0.200");
671        let two_ten = layer.root.join("payloads/serde/1.0.210");
672        assert_eq!(std::fs::read(&two_hundred).unwrap(), b"serde-200-bytes");
673        assert_eq!(std::fs::read(&two_ten).unwrap(), b"serde-210-bytes");
674        // …and nothing landed under the bare name, where one would have won.
675        assert!(!layer.root.join("bin/serde").exists());
676
677        // Both are reachable FROM THEIR MANIFEST ENTRIES — the lookup every
678        // consumer uses, so `verify`, `archive` and `export-cargo` each see the
679        // version they asked for rather than whichever landed last.
680        assert_eq!(
681            store.entry_path(&layer, &entry("serde", Some("1.0.200"), Some("crate"))),
682            Some(two_hundred)
683        );
684        assert_eq!(
685            store.entry_path(&layer, &entry("serde", Some("1.0.210"), Some("crate"))),
686            Some(two_ten)
687        );
688    }
689
690    // rivet: verifies REQ-STORE-002
691    #[test]
692    fn two_payloads_claiming_one_path_are_refused_before_anything_is_written() {
693        // The guard that makes the relaxation safe. deposit refuses two tools
694        // under one name, but deposit is not the only producer: install accepts
695        // ANY manifest a realm root signed, including one built by other
696        // software. If two entries ever reach one path, the store must say so
697        // loudly rather than write one over the other.
698        let (_tmp, store) = store();
699        let manifest = fixtures::manifest("2026.08.0", "qualified");
700        let err = store
701            .lay_down_payloads(
702                &manifest,
703                &[
704                    Payload::tool("synth", b"first-bytes"),
705                    Payload::tool("synth", b"second-bytes"),
706                ],
707            )
708            .unwrap_err();
709        assert!(
710            matches!(&err, StoreError::Collision { path, .. } if path.contains("synth")),
711            "got: {err}"
712        );
713        // And the core is untouched: destinations are resolved before any byte
714        // is written, so a colliding layer never half-lands.
715        assert!(store.list().unwrap().is_empty(), "nothing may be laid down");
716
717        // The same collision through the versionless legacy placement.
718        let err = store
719            .lay_down_payloads(
720                &manifest,
721                &[
722                    Payload {
723                        name: "wit-pkg",
724                        version: None,
725                        dispatchable: false,
726                        bytes: b"a",
727                    },
728                    Payload {
729                        name: "wit-pkg",
730                        version: None,
731                        dispatchable: false,
732                        bytes: b"b",
733                    },
734                ],
735            )
736            .unwrap_err();
737        assert!(matches!(err, StoreError::Collision { .. }), "got: {err}");
738    }
739
740    // rivet: verifies REQ-STORE-002
741    #[test]
742    fn a_name_or_version_that_escapes_the_layer_is_refused() {
743        // The names now compose a PATH, and they come out of a manifest.
744        // "Signed" means attributable, not benign: a realm root must not be
745        // able to place bytes outside the layer it is laying down.
746        let (_tmp, store) = store();
747        let manifest = fixtures::manifest("2026.08.0", "qualified");
748        for (name, version) in [
749            ("../../escape", Some("1.0.0")),
750            ("serde", Some("../../escape")),
751            ("a/b", Some("1.0.0")),
752            ("..", Some("1.0.0")),
753            ("", Some("1.0.0")),
754            ("serde", Some("")),
755        ] {
756            let err = store
757                .lay_down_payloads(&manifest, &[held(name, version.unwrap(), b"x")])
758                .unwrap_err();
759            assert!(
760                matches!(err, StoreError::UnsafeComponent { .. }),
761                "{name:?}@{version:?} must be refused, got: {err}"
762            );
763        }
764        assert!(store.list().unwrap().is_empty());
765        // The escape did not happen by any other route either.
766        assert!(!store.root().join("escape").exists());
767    }
768
769    // rivet: verifies REQ-STORE-002
770    #[test]
771    fn a_layer_installed_before_this_change_still_resolves_its_crate() {
772        // Backward compatibility, stated as a test rather than a hope: a layer
773        // laid down by an older varve holds its crate at `bin/<name>`, and
774        // `verify`/`archive`/`export-cargo` must still find it there. Such a
775        // layer can hold only ONE version per name — the old deposit check made
776        // sure of it — so the fallback is unambiguous.
777        let (_tmp, store) = store();
778        let manifest = fixtures::manifest("2026.08.0", "qualified");
779        let digest = store
780            .lay_down(&manifest, &[("legacy-crate", b"old-layout-bytes")])
781            .unwrap();
782        let layer = store.get(&digest).unwrap().unwrap();
783        let path = store
784            .entry_path(&layer, &entry("legacy-crate", Some("0.1.0"), Some("crate")))
785            .expect("a pre-REQ-STORE-002 layer must keep resolving");
786        assert_eq!(std::fs::read(path).unwrap(), b"old-layout-bytes");
787    }
788
789    // rivet: verifies REQ-STORE-002
790    #[test]
791    fn a_tool_keeps_bin_and_a_held_payload_never_borrows_it() {
792        // Dispatch is by name, so `bin/<name>` is not merely retained for
793        // compatibility — it is the contract `varve which`, `varve run` and the
794        // argv[0] shims resolve through. A held payload must never satisfy a
795        // dispatch lookup by landing there.
796        let (_tmp, store) = store();
797        let manifest = fixtures::manifest("2026.08.0", "qualified");
798        let digest = store
799            .lay_down_payloads(
800                &manifest,
801                &[
802                    Payload::tool("synth", b"synth-binary"),
803                    held("serde", "1.0.200", b"serde-crate"),
804                ],
805            )
806            .unwrap();
807        let layer = store.get(&digest).unwrap().unwrap();
808        assert_eq!(
809            std::fs::read(store.tool_path(&layer, "synth").unwrap()).unwrap(),
810            b"synth-binary"
811        );
812        assert_eq!(store.tool_path(&layer, "serde"), None, "not dispatchable");
813        #[cfg(unix)]
814        {
815            use std::os::unix::fs::PermissionsExt;
816            let mode =
817                |p: std::path::PathBuf| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
818            assert_eq!(mode(layer.root.join("bin/synth")), 0o755);
819            assert_eq!(
820                mode(layer.root.join("payloads/serde/1.0.200")),
821                0o644,
822                "a .crate tarball is data, not something to execute"
823            );
824        }
825    }
826
827    // rivet: verifies REQ-STORE-002
828    #[test]
829    fn an_unrecognised_kind_is_held_by_version_never_dispatched_by_name() {
830        // A layer deposited by a NEWER varve carries kinds this build has never
831        // heard of, and installs and verifies normally (DD-003, kind.rs). If an
832        // unknown kind were placed by name alone, two versions of it would
833        // overwrite each other — the very loss this requirement forbids, one
834        // release later.
835        let e = entry("future", Some("2.0.0"), Some("quantum-blob"));
836        assert!(!entry_is_dispatchable(&e));
837        assert_eq!(
838            payload_rel_path(false, "future", Some("2.0.0")).unwrap(),
839            PathBuf::from("payloads/future/2.0.0")
840        );
841        // …while an entry with NO kind annotation is a tool, as pre-kind
842        // layers require.
843        assert!(entry_is_dispatchable(&entry("synth", Some("0.45.0"), None)));
844        assert_eq!(
845            payload_rel_path(true, "synth", Some("0.45.0")).unwrap(),
846            PathBuf::from("bin/synth")
847        );
848    }
849
850    // rivet: verifies REQ-SDK-001
851    #[test]
852    fn a_tree_shaped_payload_is_held_by_name_and_version_like_any_other() {
853        // REQ-SDK-001 clause 1, and the correction the clause needed. A tree
854        // payload is held as the single archive its producer SIGNED — one blob,
855        // one digest, at `payloads/<name>/<version>` exactly like a `.crate` or
856        // a `.vsix`. It is not unpacked here, and that is the design rather than
857        // a shortfall:
858        //
859        //   * `verify` and `archive` re-hash ONE file at `entry_path` against
860        //     ONE signed digest, so an unpacked store entry would be neither
861        //     verifiable nor archivable — the opposite of clause 2;
862        //   * relocation can only ever SHORTEN a path, and the store path is
863        //     ~90 characters before any content, so an SDK could not be
864        //     relocated INTO the store even if it were unpacked there.
865        //
866        // The tree is materialised where it is used, by `sdkexport`.
867        let (_tmp, store) = store();
868        let manifest = fixtures::manifest("2026.08.0", "qualified");
869        let digest = store
870            .lay_down_payloads(
871                &manifest,
872                &[
873                    held("poky-cortexa53", "4.0.15", b"sdk-tarball-4.0.15"),
874                    held("poky-cortexa53", "5.0.2", b"sdk-tarball-5.0.2"),
875                    held("zephyr-hal", "3.6.0", b"zephyr-module-tarball"),
876                ],
877            )
878            .unwrap();
879        let layer = store.get(&digest).unwrap().unwrap();
880
881        // Two SDK versions coexist, neither overwriting the other — a project
882        // migrating between Yocto releases holds both.
883        for (version, want) in [
884            ("4.0.15", &b"sdk-tarball-4.0.15"[..]),
885            ("5.0.2", &b"sdk-tarball-5.0.2"[..]),
886        ] {
887            let path = store
888                .entry_path(&layer, &entry("poky-cortexa53", Some(version), Some("sdk")))
889                .expect("an sdk resolves from its manifest entry like any held payload");
890            assert_eq!(
891                path,
892                layer
893                    .root
894                    .join(format!("payloads/poky-cortexa53/{version}"))
895            );
896            assert_eq!(std::fs::read(&path).unwrap(), want);
897        }
898        // An SDK is data handed to a compiler, never something varve dispatches.
899        assert!(!crate::kind::PayloadKind::Sdk.is_dispatchable());
900        assert_eq!(store.tool_path(&layer, "poky-cortexa53"), None);
901        assert!(!layer.root.join("bin/poky-cortexa53").exists());
902        // The other tree kinds are held the same way — nothing about `sdk` is
903        // special in the store, which is what "like any other held payload"
904        // means.
905        assert!(
906            store
907                .entry_path(
908                    &layer,
909                    &entry("zephyr-hal", Some("3.6.0"), Some("zephyr-module"))
910                )
911                .is_some()
912        );
913    }
914
915    // rivet: verifies REQ-PIN-001
916    #[test]
917    fn missing_tool_in_an_installed_layer_is_none() {
918        let (_tmp, store) = store();
919        let bytes = fixtures::manifest("2026.07.0", "qualified");
920        let digest = store.lay_down(&bytes, &[("rivet", b"r")]).unwrap();
921        let entry = store.get(&digest).unwrap().unwrap();
922        assert!(store.tool_path(&entry, "rivet").is_some());
923        assert_eq!(store.tool_path(&entry, "synth"), None);
924    }
925}