facett-ops/tests/release_manifest.rs
//! **Row 10 — a wheel on disk is not a release.**
//!
//! `publish-pip` produces a verified wheel and `publish-holger` serves it, but neither
//! leaves anything a consumer could check the bytes against **away from this machine**.
//! A release is the artefacts plus a statement of what they are: a `SHA256SUMS` a
//! downloader can run `sha256sum -c` on, and a provenance record saying which commit,
//! which toolchain, which host, and — the part that matters — **hashes of the produced
//! bytes rather than of the recipe that was supposed to produce them.**
//!
//! The distinction is the whole point. A provenance record built from the variant
//! declaration describes what was *asked for*; two builds of the same declaration can
//! differ (that is why row 3 measured the wasm growing by 1.7 MB). `capture` is handed
//! the artefact PATHS and hashes what is actually there.
//!
//! ## The door this row could not open, and which is open now
//!
//! This section used to say the row deliberately stopped short:
//!
//! > The brief's door is `dwarves buildthing facett --variant facet-wrapped-web`.
//! > That needs the forge to have an artifact kind for a **package**, and it does
//! > not: `ArtifactKind` is `Lib | Exe | Wasm | Kernel | Container | Service | Vm |
//! > Iso | Installer`. A wheel is none of those, and `Wasm` would make the forge
//! > build a bare `.wasm` — not run wasm-bindgen, not build the wheel, not verify
//! > it. Adding a kind is a change to a shared edda crate that every matcher on it
//! > has to answer, so it is posted rather than made here.
//!
//! It was posted, and on 2026-09-06 (HO-T5) it was made. edda 0.1.13 has
//! `ArtifactKind::Package` and `[[build.package]]`; `facett-build-thing` declares
//! `facet-wrapped-web`; the forge routes it. The paragraph is kept rather than
//! deleted because the reasoning in it is still the reason the kind is `package`
//! and not `wasm`.
//!
//! What changed for THIS file: `write_sha256sums` is edda's now, and these tests run
//! against it through the same thin wrapper `publish-pip` calls. That is the point of
//! leaving them here — they were written for the behaviour, and the behaviour did not
//! move even though the code did. A green run of this file after the move is the
//! evidence that one writer replaced two without changing what either produced.
use std::path::PathBuf;
fn scratch(tag: &str) -> PathBuf {
let base = std::env::var("CARGO_TARGET_TMPDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::temp_dir());
let d = base.join(format!("relman-{tag}"));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
#[test]
fn the_manifest_lists_every_artifact_with_its_real_hash() {
let d = scratch("hashes");
std::fs::write(d.join("facet_wrapped_web-0.1.19-py3-none-any.whl"), b"wheel bytes").unwrap();
std::fs::write(d.join("facet_wrapped_web-0.1.19.tar.gz"), b"sdist bytes").unwrap();
let sums = facett_ops::pip::write_sha256sums(&d).expect("writes");
let text = std::fs::read_to_string(&sums).unwrap();
// `sha256sum -c` format: "<64 hex> <name>". Two spaces, and the NAME only —
// a path would make the file uncheckable from the directory it ships in.
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 2, "one line per artifact: {text}");
for l in &lines {
let (hash, name) = l.split_once(" ").unwrap_or_else(|| panic!("bad line `{l}`"));
assert_eq!(hash.len(), 64, "sha256 is 64 hex chars: `{l}`");
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
assert!(!name.contains('/'), "the name must be bare, not a path: `{l}`");
// The hash must be OF THE BYTES, not of the name or a constant.
let bytes = std::fs::read(d.join(name)).unwrap();
let want = facett_ops::pip::sha256_hex(&bytes);
assert_eq!(hash, want, "`{name}` is listed with a hash that is not its own");
}
}
#[test]
fn a_changed_byte_changes_the_listed_hash() {
// The check that makes the one above mean something: if the manifest were keyed on
// filename or length, everything above would still pass.
let d = scratch("changed");
let f = d.join("facet_wrapped_web-0.1.19-py3-none-any.whl");
std::fs::write(&f, b"aaaa").unwrap();
let first = std::fs::read_to_string(facett_ops::pip::write_sha256sums(&d).unwrap()).unwrap();
std::fs::write(&f, b"aaab").unwrap(); // same length, one byte different
let second = std::fs::read_to_string(facett_ops::pip::write_sha256sums(&d).unwrap()).unwrap();
assert_ne!(first, second, "a one-byte change left the manifest identical");
}
#[test]
fn the_manifest_does_not_list_itself_or_anything_that_is_not_an_artifact() {
// SHA256SUMS listing its own (pre-write) hash would be unverifiable, and listing
// a stray file makes `sha256sum -c` fail for a reason that has nothing to do with
// the release.
let d = scratch("selfref");
std::fs::write(d.join("facet_wrapped_web-0.1.19-py3-none-any.whl"), b"w").unwrap();
std::fs::write(d.join("notes.txt"), b"scratch").unwrap();
std::fs::create_dir_all(d.join("subdir")).unwrap();
let sums = facett_ops::pip::write_sha256sums(&d).unwrap();
let text = std::fs::read_to_string(&sums).unwrap();
assert!(!text.contains("SHA256SUMS"), "the manifest lists itself: {text}");
assert!(!text.contains("notes.txt"), "a non-artifact was listed: {text}");
assert_eq!(text.lines().count(), 1);
}
#[test]
fn rewriting_the_manifest_is_idempotent() {
// `publish-pip` is re-run constantly. A manifest that grew a duplicate line per
// run would still `sha256sum -c` clean, and would quietly stop being a listing.
let d = scratch("idem");
std::fs::write(d.join("facet_wrapped_web-0.1.19-py3-none-any.whl"), b"w").unwrap();
let a = std::fs::read_to_string(facett_ops::pip::write_sha256sums(&d).unwrap()).unwrap();
let b = std::fs::read_to_string(facett_ops::pip::write_sha256sums(&d).unwrap()).unwrap();
assert_eq!(a, b);
assert_eq!(a.lines().count(), 1);
}
#[test]
fn an_empty_dist_is_refused_rather_than_producing_an_empty_manifest() {
// An empty SHA256SUMS passes `sha256sum -c` trivially — it checks nothing and
// says so to no one.
let d = scratch("empty");
assert!(
facett_ops::pip::write_sha256sums(&d).is_err(),
"an empty dist/ must be refused, not manifested"
);
}