yah_qed/artifact_retrieval.rs
1//! Content-addressed landing for artifacts retrieved off a remote build-worker
2//! (R590-F6 leg 2).
3//!
4//! A native remote step (the rusty_v8 musl build on us-west-002) writes its
5//! output tarball to a path *inside* the build-worker container. On-box green
6//! (R590-B5 + redeploy) proves placement + the native build, but nothing pulls
7//! the bytes back to camp. This module is the landing half of that retrieval:
8//! the runner fetches the produced bytes over the [`task::remote::WardenClient`]
9//! transport seam, then [`ContentAddressedStore::land`]s them here.
10//!
11//! **Content-addressed, not name-addressed.** Unlike [`crate::artifact_local`]
12//! (the GHA `upload-artifact`/`download-artifact` store, keyed by artifact
13//! *name*), a retrieved build output is keyed by the BLAKE3 of its bytes. That
14//! address IS the integrity check the ticket calls "BLAKE3-preservation": the
15//! landed file's content hash equals the hash of what the worker emitted, so a
16//! byte rewritten in transit changes the address and is impossible to miss.
17//!
18//! **Feeds, does not duplicate, the publish leg.** The landed file is exactly
19//! R546-T3's bootstrap-publish input — the bytes behind the zero-sentinel
20//! hashes in `.yah/services/yah-cloud/components/rusty-v8-musl/workload.toml`. The W164 derived-static-asset
21//! reconciler / [`crate::types::Outcome::Publish`] consume the landed path; this
22//! module does not itself upload anything.
23
24use std::io::Write;
25use std::path::{Path, PathBuf};
26
27/// An artifact retrieved off a build-worker and landed in the local
28/// content-addressed store.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct RetrievedArtifact {
31 /// BLAKE3 of the bytes, lowercase hex — the store address and the
32 /// integrity check the consumer contract pins.
33 pub blake3: String,
34 /// Absolute path to the landed file (`<root>/<blake3>`).
35 pub path: PathBuf,
36 /// Byte length of the landed content.
37 pub size: u64,
38}
39
40/// A directory that stores blobs under their BLAKE3 hex address.
41///
42/// Landing is idempotent: re-landing identical bytes is a no-op that returns the
43/// same address (content-addressing makes a second write pointless). Writes go
44/// through a temp file + atomic rename so a crash mid-write can never leave a
45/// truncated blob at the address of its full content.
46#[derive(Debug, Clone)]
47pub struct ContentAddressedStore {
48 root: PathBuf,
49}
50
51impl ContentAddressedStore {
52 /// Root the store at `root` (created lazily on the first `land`). For the
53 /// qed runner this is `<camp_root>/.yah/cache/artifacts`.
54 pub fn new(root: impl Into<PathBuf>) -> Self {
55 Self { root: root.into() }
56 }
57
58 /// The store's root directory.
59 pub fn root(&self) -> &Path {
60 &self.root
61 }
62
63 /// Land `bytes` under their BLAKE3 address and return the
64 /// [`RetrievedArtifact`] describing where they went.
65 ///
66 /// The returned `blake3` is computed from the in-memory bytes; the file on
67 /// disk is those exact bytes, so re-hashing the file yields the same address
68 /// (the BLAKE3-preservation guarantee, exercised by the unit tests).
69 pub fn land(&self, bytes: &[u8]) -> std::io::Result<RetrievedArtifact> {
70 let blake3 = blake3::hash(bytes).to_hex().to_string();
71 std::fs::create_dir_all(&self.root)?;
72 let path = self.root.join(&blake3);
73
74 // Idempotent: identical content already at this address — nothing to do.
75 if !path.exists() {
76 // Temp file in the same dir so the rename is atomic (same
77 // filesystem). The temp name carries the pid + address to avoid
78 // colliding with a concurrent landing of different content.
79 let tmp = self.root.join(format!(".tmp-{}-{}", std::process::id(), &blake3));
80 let mut f = std::fs::File::create(&tmp)?;
81 f.write_all(bytes)?;
82 f.sync_all()?;
83 // rename is atomic on the same fs; if a racing landing beat us to
84 // the address the content is identical, so overwriting is harmless.
85 std::fs::rename(&tmp, &path)?;
86 }
87
88 Ok(RetrievedArtifact {
89 blake3,
90 path,
91 size: bytes.len() as u64,
92 })
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use tempfile::TempDir;
100
101 #[test]
102 fn land_is_content_addressed_and_preserves_blake3() {
103 let dir = TempDir::new().unwrap();
104 let store = ContentAddressedStore::new(dir.path().join("artifacts"));
105 let bytes = b"librusty_v8_release_x86_64-unknown-linux-musl.a bytes \x00\xff";
106
107 let landed = store.land(bytes).unwrap();
108
109 // Address == BLAKE3 of the input.
110 assert_eq!(landed.blake3, blake3::hash(bytes).to_hex().to_string());
111 // Landed file lives at <root>/<blake3>.
112 assert_eq!(landed.path, store.root().join(&landed.blake3));
113 assert_eq!(landed.size, bytes.len() as u64);
114
115 // BLAKE3-preservation: the bytes on disk re-hash to the same address —
116 // nothing was lost or rewritten in transit.
117 let on_disk = std::fs::read(&landed.path).unwrap();
118 assert_eq!(on_disk, bytes);
119 assert_eq!(blake3::hash(&on_disk).to_hex().to_string(), landed.blake3);
120 }
121
122 #[test]
123 fn land_is_idempotent_for_identical_bytes() {
124 let dir = TempDir::new().unwrap();
125 let store = ContentAddressedStore::new(dir.path().join("artifacts"));
126 let bytes = b"same bytes twice";
127
128 let a = store.land(bytes).unwrap();
129 let b = store.land(bytes).unwrap();
130 assert_eq!(a, b, "re-landing identical bytes returns the same address");
131
132 // Exactly one blob at the address (plus no leftover temp files).
133 let entries: Vec<_> = std::fs::read_dir(store.root())
134 .unwrap()
135 .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
136 .collect();
137 assert_eq!(entries, vec![a.blake3], "one content-addressed blob, no temp debris");
138 }
139
140 #[test]
141 fn distinct_bytes_land_at_distinct_addresses() {
142 let dir = TempDir::new().unwrap();
143 let store = ContentAddressedStore::new(dir.path().join("artifacts"));
144 let a = store.land(b"alpha").unwrap();
145 let b = store.land(b"beta").unwrap();
146 assert_ne!(a.blake3, b.blake3);
147 assert_ne!(a.path, b.path);
148 }
149}