use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::process::Command;
pub const MSVC_DETERMINISM_RUSTFLAGS: &[&str] = &[
"-C codegen-units=1",
"-C link-arg=/Brepro",
"-C link-arg=/OPT:NOICF",
"-C link-arg=/INCREMENTAL:NO",
"-C link-arg=/DEBUG:NONE",
"-C strip=symbols",
];
pub fn merge_msvc_determinism_rustflags(base: &str) -> String {
let mut out = base.trim().to_string();
for &flag in MSVC_DETERMINISM_RUSTFLAGS {
let flag_tokens: Vec<&str> = flag.split_whitespace().collect();
let present = out
.split_whitespace()
.collect::<Vec<_>>()
.windows(flag_tokens.len())
.any(|w| w == flag_tokens.as_slice());
if present {
continue;
}
if !out.is_empty() {
out.push(' ');
}
out.push_str(flag);
}
out
}
pub fn msvc_c_toolchain_env(triple: &str) -> Vec<(String, String)> {
if !crate::target::is_windows_msvc(triple) {
return Vec::new();
}
let underscored = triple.replace('-', "_");
vec![
(format!("CC_{triple}"), "clang-cl".to_string()),
(format!("CC_{underscored}"), "clang-cl".to_string()),
(format!("CXX_{triple}"), "clang-cl".to_string()),
(format!("CXX_{underscored}"), "clang-cl".to_string()),
]
}
pub fn host_is_windows_msvc() -> bool {
match crate::partial::detect_host_target() {
Ok(h) => crate::target::is_windows_msvc(&h),
Err(e) => {
tracing::warn!(
error = %e,
"host-target detection failed; treating host as non-windows-msvc and skipping MSVC determinism flags"
);
false
}
}
}
pub fn host_is_macos() -> bool {
match crate::partial::detect_host_target() {
Ok(h) => crate::target::is_darwin(&h),
Err(e) => {
tracing::warn!(
error = %e,
"host-target detection failed; treating host as non-macOS (Linux xar .pkg path is gated, not allow-listed)"
);
false
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeterminismState {
pub sde: i64,
pub compile_time_allowlist: Vec<(String, String)>,
pub runtime_allowlist: Vec<(String, String)>,
}
impl DeterminismState {
pub fn seed_from_commit(commit_ts: i64) -> Result<Self> {
if commit_ts < 0 {
anyhow::bail!(
"commit_ts must be non-negative (got {}); a corrupted commit graph or future-bug? \
Negative SOURCE_DATE_EPOCH would propagate to child processes and be \
misinterpreted by shells/build tools.",
commit_ts
);
}
let mut installer_allow: Vec<(&str, &str)> = vec![
(
"*.msi",
"WiX candle/light regenerates a random SummaryInformation PackageCode GUID and stamps wall-clock Created/LastModified into the MSI summary-information stream, independent of SOURCE_DATE_EPOCH (wixtoolset/issues#8978); not byte-reproducible — proven by stage-msi::msi_is_byte_reproducible_across_time. Built natively on the windows determinism shard under A′.",
),
(
"*.dmg",
"hdiutil writes a fresh per-segment SegmentID GUID into the UDIF koly trailer every run and pins no SOURCE_DATE_EPOCH; not byte-reproducible — proven by stage-dmg::dmg_is_byte_reproducible_across_time. Built natively on the macos determinism shard under A′.",
),
(
"*.flatpak",
"flatpak build-bundle wraps an OSTree commit whose metadata (commit object timestamp + per-object headers) is not byte-stable across runs even at a fixed SOURCE_DATE_EPOCH; empirically confirmed non-reproducible via two-build cmp",
),
(
"*.deb",
"nfpm GPG-signs the deb (_gpgorigin ar member); the signature embeds a non-pinnable creation time / randomized salt and is not byte-reproducible even at a fixed SOURCE_DATE_EPOCH. The package body (debian-binary, control.tar.gz, data.tar.gz) IS byte-reproducible — gated by stage-nfpm::signed_deb_body_is_byte_reproducible_across_time — and the signature is verified cryptographically (gpg --verify), not by byte-equality.",
),
(
"*.rpm",
"nfpm GPG-signs the rpm (RPM signature header); the signature embeds a non-pinnable creation time / randomized salt and is not byte-reproducible even at a fixed SOURCE_DATE_EPOCH. The package body (main header + cpio payload, i.e. everything after the signature header) IS byte-reproducible — gated by stage-nfpm::signed_rpm_body_is_byte_reproducible_across_time — and the signature is verified cryptographically (gpg --verify), not by byte-equality.",
),
];
if host_is_macos() {
installer_allow.push((
"*.pkg",
"macOS-native pkgbuild stamps a wall-clock xar TOC and ignores SOURCE_DATE_EPOCH; not byte-reproducible — proven by stage-pkg::native_pkgbuild_pkg_is_byte_reproducible_across_time. Built natively on the macos determinism shard under A′ (the Linux xar/mkbom/cpio .pkg path is proven reproducible and gated, never allow-listed)",
));
}
let sbom_allow: &[(&str, &str)] = &[
(
"*.cdx.json",
"CycloneDX SBOM embeds a random serialNumber UUID and a generation timestamp (syft does not honor SOURCE_DATE_EPOCH for it); not byte-reproducible across runs",
),
(
"*.spdx.json",
"SPDX SBOM embeds a documentNamespace UUID and a created timestamp; not byte-reproducible across runs",
),
(
"*.sbom.json",
"SBOM document embeds a per-document unique identifier and generation timestamp; not byte-reproducible across runs",
),
];
let mut compile_time_allowlist: Vec<(String, String)> = Vec::new();
for (pattern, reason) in installer_allow.iter().chain(sbom_allow) {
compile_time_allowlist.push(((*pattern).into(), (*reason).into()));
compile_time_allowlist.push((
format!("{}.sha256", pattern),
format!("derivative of {pattern}: {reason}"),
));
}
Ok(Self {
sde: commit_ts,
compile_time_allowlist,
runtime_allowlist: Vec::new(),
})
}
pub fn export_env(&self, cmd: &mut Command) {
cmd.env("SOURCE_DATE_EPOCH", self.sde.to_string());
}
pub fn resolve_reason(&self, artifact: &str) -> Option<&str> {
for (name, reason) in &self.compile_time_allowlist {
if matches_artifact_pattern(name, artifact) {
return Some(reason.as_str());
}
}
for (name, reason) in &self.runtime_allowlist {
if matches_artifact_pattern(name, artifact) {
return Some(reason.as_str());
}
}
None
}
pub fn append_runtime(&mut self, artifact: String, reason: String) {
self.runtime_allowlist.push((artifact, reason));
}
}
fn matches_artifact_pattern(pattern: &str, artifact: &str) -> bool {
if let Some(suffix) = pattern.strip_prefix('*') {
return artifact.ends_with(suffix);
}
pattern == artifact
}
pub const COMBINED_CHECKSUMS_AGGREGATE_ID: &str = "combined-checksums";
pub const ARTIFACTS_MANIFEST_AGGREGATE_ID: &str = "artifacts-manifest";
pub trait AggregateKind {
fn id(&self) -> &'static str;
fn matches(&self, name: &str) -> bool;
fn members_by_unit(&self, bytes: &[u8]) -> Result<BTreeMap<String, String>>;
}
fn aggregate_basename(name: &str) -> String {
name.rsplit(['/', '\\'])
.next()
.unwrap_or(name)
.to_lowercase()
}
pub struct CombinedChecksums;
impl AggregateKind for CombinedChecksums {
fn id(&self) -> &'static str {
COMBINED_CHECKSUMS_AGGREGATE_ID
}
fn matches(&self, name: &str) -> bool {
let base = aggregate_basename(name);
base.ends_with("checksums.txt")
|| base.ends_with("sha256sums")
|| base.ends_with("sha256sum")
}
fn members_by_unit(&self, bytes: &[u8]) -> Result<BTreeMap<String, String>> {
let text =
std::str::from_utf8(bytes).context("combined checksums file is not valid UTF-8")?;
let mut out = BTreeMap::new();
for raw in text.lines() {
let line = raw.trim_end_matches(['\r', '\n']);
if line.trim().is_empty() {
continue;
}
let filename = line
.rsplit(" ")
.next()
.map(str::trim)
.filter(|s| !s.is_empty())
.with_context(|| format!("checksum line missing a filename field: {line:?}"))?;
out.insert(line.to_string(), filename.to_string());
}
Ok(out)
}
}
pub struct ArtifactsManifest;
impl AggregateKind for ArtifactsManifest {
fn id(&self) -> &'static str {
ARTIFACTS_MANIFEST_AGGREGATE_ID
}
fn matches(&self, name: &str) -> bool {
aggregate_basename(name) == crate::dist::ARTIFACTS_JSON
}
fn members_by_unit(&self, bytes: &[u8]) -> Result<BTreeMap<String, String>> {
let value: serde_json::Value =
serde_json::from_slice(bytes).context("parsing artifacts.json dist manifest")?;
let entries = value
.as_array()
.context("artifacts.json dist manifest must be a JSON array")?;
let mut out = BTreeMap::new();
for entry in entries {
let path = entry
.get("path")
.and_then(serde_json::Value::as_str)
.context("artifacts.json entry missing a string `path` field")?;
let member = path
.rsplit(['/', '\\'])
.next()
.filter(|s| !s.is_empty())
.unwrap_or(path)
.to_string();
let token = entry
.get("metadata")
.and_then(|m| {
m.get("sha256")
.or_else(|| m.get("SHA256"))
.or_else(|| m.get("Checksum"))
})
.and_then(serde_json::Value::as_str)
.map(str::to_string)
.unwrap_or_else(|| serde_json::to_string(entry).unwrap_or_default());
let unit_key = format!("{path}\u{1f}{token}");
out.insert(unit_key, member);
}
Ok(out)
}
}
pub fn aggregate_kinds() -> Vec<Box<dyn AggregateKind>> {
vec![Box::new(CombinedChecksums), Box::new(ArtifactsManifest)]
}
pub fn aggregate_kind_for(name: &str) -> Option<Box<dyn AggregateKind>> {
aggregate_kinds().into_iter().find(|k| k.matches(name))
}
pub fn combined_checksum_members_from_manifest(bytes: &[u8]) -> Result<BTreeSet<String>> {
let value: serde_json::Value =
serde_json::from_slice(bytes).context("parsing artifacts.json dist manifest")?;
let entries = value
.as_array()
.context("artifacts.json dist manifest must be a JSON array")?;
let mut out = BTreeSet::new();
for entry in entries {
let is_combined = entry
.get("metadata")
.and_then(|m| m.get(crate::artifact::COMBINED_CHECKSUM_META))
.and_then(serde_json::Value::as_str)
.is_some_and(|v| v == crate::artifact::COMBINED_CHECKSUM_VALUE);
if !is_combined {
continue;
}
let Some(path) = entry.get("path").and_then(serde_json::Value::as_str) else {
continue;
};
let base = path
.rsplit(['/', '\\'])
.next()
.filter(|s| !s.is_empty())
.unwrap_or(path);
out.insert(base.to_string());
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn msvc_rustflags_const_matches_cargo_config() {
let cargo_config = include_str!("../../../.cargo/config.toml");
for &flag in MSVC_DETERMINISM_RUSTFLAGS {
let (lead, rest) = flag.split_once(' ').expect("each flag is two tokens");
let toml_pair = format!("\"{lead}\", \"{rest}\"");
assert!(
cargo_config.contains(&toml_pair),
".cargo/config.toml must carry `{toml_pair}` to stay aligned with \
MSVC_DETERMINISM_RUSTFLAGS; the two drifted"
);
}
}
#[test]
fn merge_msvc_into_empty_yields_full_set() {
let merged = merge_msvc_determinism_rustflags("");
for &flag in MSVC_DETERMINISM_RUSTFLAGS {
assert!(
merged.contains(flag),
"merged set must contain `{flag}`. got={merged}"
);
}
assert!(
merged.contains("/Brepro"),
"the COFF TimeDateStamp fix must be present"
);
}
#[test]
fn merge_msvc_preserves_existing_flags_and_appends() {
let base = "-C linker=link.exe --remap-path-prefix=/w=/build";
let merged = merge_msvc_determinism_rustflags(base);
assert!(
merged.starts_with(base),
"existing flags must be preserved verbatim. got={merged}"
);
assert!(
merged.contains("-C link-arg=/Brepro"),
"MSVC flags must be appended. got={merged}"
);
}
#[test]
fn merge_msvc_is_idempotent_no_duplicate_brepro() {
let once = merge_msvc_determinism_rustflags("");
let twice = merge_msvc_determinism_rustflags(&once);
assert_eq!(
once, twice,
"merging an already-merged set must not duplicate flags"
);
assert_eq!(
twice.matches("/Brepro").count(),
1,
"/Brepro must appear exactly once after a double merge. got={twice}"
);
}
#[test]
fn msvc_c_toolchain_env_pins_clang_cl_x86_64() {
let env = msvc_c_toolchain_env("x86_64-pc-windows-msvc");
assert_eq!(
env,
vec![
(
"CC_x86_64-pc-windows-msvc".to_string(),
"clang-cl".to_string()
),
(
"CC_x86_64_pc_windows_msvc".to_string(),
"clang-cl".to_string()
),
(
"CXX_x86_64-pc-windows-msvc".to_string(),
"clang-cl".to_string()
),
(
"CXX_x86_64_pc_windows_msvc".to_string(),
"clang-cl".to_string()
),
]
);
}
#[test]
fn msvc_c_toolchain_env_pins_clang_cl_aarch64() {
let env = msvc_c_toolchain_env("aarch64-pc-windows-msvc");
assert_eq!(
env,
vec![
(
"CC_aarch64-pc-windows-msvc".to_string(),
"clang-cl".to_string()
),
(
"CC_aarch64_pc_windows_msvc".to_string(),
"clang-cl".to_string()
),
(
"CXX_aarch64-pc-windows-msvc".to_string(),
"clang-cl".to_string()
),
(
"CXX_aarch64_pc_windows_msvc".to_string(),
"clang-cl".to_string()
),
]
);
}
#[test]
fn msvc_c_toolchain_env_empty_for_non_msvc_targets() {
assert!(msvc_c_toolchain_env("x86_64-unknown-linux-gnu").is_empty());
assert!(msvc_c_toolchain_env("aarch64-apple-darwin").is_empty());
}
#[test]
fn host_is_windows_msvc_matches_real_host() {
let expected = crate::partial::detect_host_target()
.map(|h| crate::target::is_windows_msvc(&h))
.unwrap_or(false);
assert_eq!(host_is_windows_msvc(), expected);
}
#[test]
fn sde_from_commit_timestamp_is_idempotent() {
let s = DeterminismState::seed_from_commit(1_715_000_000).expect("non-negative");
assert_eq!(s.sde, 1_715_000_000);
let s2 = DeterminismState::seed_from_commit(1_715_000_000).expect("non-negative");
assert_eq!(s, s2);
}
#[test]
fn cargo_crate_is_gated_not_allowlisted() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("anodizer-0.2.1.crate").is_none());
assert!(s.resolve_reason("anodizer-core-0.11.3.crate").is_none());
assert!(
s.resolve_reason("anodizer-core-0.11.3.crate.sha256")
.is_none()
);
}
#[test]
fn rpm_and_deb_are_allowlisted_due_to_gpg_signing() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
let rpm = s.resolve_reason("foo-1.0.rpm").expect("matches *.rpm");
assert!(
rpm.contains("signature") || rpm.contains("GPG"),
"rpm reason must cite the signature: {rpm}"
);
let deb = s
.resolve_reason("foo_1.0_amd64.deb")
.expect("matches *.deb");
assert!(
deb.contains("signature") || deb.contains("GPG"),
"deb reason must cite the signature: {deb}"
);
assert!(s.resolve_reason("foo-1.0.rpm.sha256").is_some());
assert!(s.resolve_reason("foo_1.0_amd64.deb.sha256").is_some());
}
#[test]
fn snap_is_gated_not_allowlisted() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("probe_1.2.3_amd64.snap").is_none());
assert!(s.resolve_reason("probe_1.2.3_amd64.snap.sha256").is_none());
}
#[test]
fn apk_is_signed_but_gated() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("foo_1.0_amd64.apk").is_none());
assert!(s.resolve_reason("foo_1.0_amd64.apk.sha256").is_none());
}
#[test]
fn compile_time_allowlist_resolves_for_flatpak() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
let reason = s
.resolve_reason("anodizer_0.9.1_linux_amd64.flatpak")
.expect("matches *.flatpak");
assert!(reason.contains("OSTree"));
assert!(
s.resolve_reason("anodizer_0.9.1_linux_amd64.flatpak.sha256")
.is_some()
);
}
#[test]
fn pkg_allowlist_is_host_keyed() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
let resolved = s.resolve_reason("anodizer-0.2.1.pkg").is_some();
assert_eq!(
resolved,
host_is_macos(),
"`.pkg` must be allow-listed iff the host is macOS (Linux xar path is gated)"
);
if !host_is_macos() {
assert!(
s.resolve_reason("anodizer-0.2.1.pkg").is_none(),
"Linux xar .pkg path is proven reproducible and must be gated"
);
assert!(s.resolve_reason("anodizer-0.2.1.pkg.sha256").is_none());
}
}
#[test]
fn appbundle_is_gated_not_allowlisted() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("anodizer_arm64.app").is_none());
assert!(s.resolve_reason("anodizer_amd64.app").is_none());
assert!(s.resolve_reason("anodizer_arm64.app.sha256").is_none());
}
#[test]
fn nsis_installer_is_gated_not_allowlisted() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("anodizer_x64-setup.exe").is_none());
assert!(s.resolve_reason("anodizer_x64_setup.exe").is_none());
assert!(
s.resolve_reason("anodizer_arm64-setup.exe.sha256")
.is_none()
);
}
#[test]
fn raw_windows_binary_is_not_allowlisted_or_misclassified() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("anodizer.exe").is_none());
assert!(s.resolve_reason("anodizer.exe.sha256").is_none());
}
#[test]
fn msi_and_dmg_reasons_cite_pending_shard_proof() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
let msi = s
.resolve_reason("anodizer-0.2.1.msi")
.expect("matches *.msi");
assert!(
msi.contains("msi_is_byte_reproducible_across_time"),
"msi reason must cite the pending shard test: {msi}"
);
let dmg = s
.resolve_reason("anodizer-0.2.1.dmg")
.expect("matches *.dmg");
assert!(
dmg.contains("dmg_is_byte_reproducible_across_time"),
"dmg reason must cite the pending shard test: {dmg}"
);
}
#[test]
fn compile_time_allowlist_resolves_for_sbom_documents() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
for name in [
"cfgd-0.4.0-linux-amd64.tar.gz.cdx.json",
"cfgd-0.4.0-linux-amd64.tar.gz.spdx.json",
"cfgd-0.4.0-linux-amd64.tar.gz.sbom.json",
] {
assert!(
s.resolve_reason(name).is_some(),
"SBOM document {name} must be allow-listed"
);
}
}
#[test]
fn compile_time_allowlist_resolves_for_sbom_checksum_sidecars() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
let reason = s
.resolve_reason("cfgd-0.4.0-linux-amd64.tar.gz.cdx.json.sha256")
.expect("matches *.cdx.json.sha256");
assert!(reason.contains("derivative of"));
}
#[test]
fn artifacts_manifest_is_not_blanket_allowlisted() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(
s.resolve_reason("artifacts.json").is_none(),
"artifacts.json must not be blanket-excused; the registry judges it per-member"
);
assert!(s.resolve_reason("anodizer_0.12.0_checksums.txt").is_none());
assert!(s.resolve_reason("config.json").is_none());
assert!(s.resolve_reason("metadata.json").is_none());
}
#[test]
fn aggregate_registry_matches_checksums_and_manifest() {
assert_eq!(
aggregate_kind_for("anodizer_0.12.0_checksums.txt").map(|k| k.id()),
Some(COMBINED_CHECKSUMS_AGGREGATE_ID)
);
assert_eq!(
aggregate_kind_for("anodizer-core/anodizer-core_0.12.0_checksums.txt").map(|k| k.id()),
Some(COMBINED_CHECKSUMS_AGGREGATE_ID)
);
assert_eq!(
aggregate_kind_for("SHA256SUMS").map(|k| k.id()),
Some(COMBINED_CHECKSUMS_AGGREGATE_ID)
);
assert_eq!(
aggregate_kind_for("artifacts.json").map(|k| k.id()),
Some(ARTIFACTS_MANIFEST_AGGREGATE_ID)
);
assert_eq!(
aggregate_kind_for("anodizer-core/artifacts.json").map(|k| k.id()),
Some(ARTIFACTS_MANIFEST_AGGREGATE_ID)
);
assert!(aggregate_kind_for("anodizer_0.12.0_linux_amd64.tar.gz.sha256").is_none());
assert!(aggregate_kind_for("metadata.json").is_none());
}
#[test]
fn combined_checksums_members_by_unit_round_trips() {
let bytes = b"aaaa foo_1.0_amd64.deb\nbbbb bar-1.0.tar.gz\n\ncccc baz.cdx.json\n";
let units = CombinedChecksums.members_by_unit(bytes).expect("parses");
let members: std::collections::BTreeSet<&str> =
units.values().map(String::as_str).collect();
assert_eq!(
members,
["foo_1.0_amd64.deb", "bar-1.0.tar.gz", "baz.cdx.json"]
.into_iter()
.collect()
);
assert!(units.contains_key("aaaa foo_1.0_amd64.deb"));
assert_eq!(units.len(), 3);
}
#[test]
fn artifacts_manifest_members_by_unit_round_trips_and_keys_on_digest() {
let json = br#"[
{"kind":"archive","path":"./dist/foo.tar.gz","name":"foo.tar.gz",
"metadata":{"sha256":"deadbeef"}},
{"kind":"linux_package","path":"./dist/nfpm/foo_1.0_amd64.deb","name":"foo_1.0_amd64.deb",
"metadata":{"Checksum":"sha256:cafef00d"}}
]"#;
let units = ArtifactsManifest.members_by_unit(json).expect("parses");
let members: std::collections::BTreeSet<&str> =
units.values().map(String::as_str).collect();
assert_eq!(
members,
["foo.tar.gz", "foo_1.0_amd64.deb"].into_iter().collect()
);
let drifted = br#"[
{"kind":"archive","path":"./dist/foo.tar.gz","name":"foo.tar.gz",
"metadata":{"sha256":"00000000"}}
]"#;
let drifted_units = ArtifactsManifest.members_by_unit(drifted).expect("parses");
let original_key = units
.keys()
.find(|k| k.starts_with("./dist/foo.tar.gz"))
.unwrap();
assert!(
!drifted_units.contains_key(original_key),
"a changed digest must yield a new unit_key (value-change ⇒ add/remove)"
);
}
#[test]
fn aggregate_members_by_unit_fail_closed_on_garbage() {
assert!(
CombinedChecksums
.members_by_unit(&[0xff, 0xfe, 0x00])
.is_err()
);
assert!(ArtifactsManifest.members_by_unit(b"not json").is_err());
assert!(ArtifactsManifest.members_by_unit(b"{}").is_err());
}
#[test]
fn aggregate_ids_are_distinct_and_stable() {
assert_ne!(
COMBINED_CHECKSUMS_AGGREGATE_ID,
ARTIFACTS_MANIFEST_AGGREGATE_ID
);
assert_eq!(CombinedChecksums.id(), COMBINED_CHECKSUMS_AGGREGATE_ID);
assert_eq!(ArtifactsManifest.id(), ARTIFACTS_MANIFEST_AGGREGATE_ID);
}
#[test]
fn combined_checksum_marker_recognizes_operator_renamed_file() {
let manifest = br#"[
{"kind":"archive","path":"./dist/foo.tar.gz","name":"foo.tar.gz",
"metadata":{"sha256":"aaaa"}},
{"kind":"checksum","path":"./dist/SHA512SUMS","name":"SHA512SUMS",
"metadata":{"combined":"true"}}
]"#;
let markers = combined_checksum_members_from_manifest(manifest).expect("parses");
assert!(
markers.contains("SHA512SUMS"),
"marker recognizer must flag the renamed combined file: {markers:?}"
);
assert!(!CombinedChecksums.matches("SHA512SUMS"));
assert!(aggregate_kind_for("SHA512SUMS").is_none());
assert!(!markers.contains("foo.tar.gz"));
}
#[test]
fn combined_checksum_marker_fails_closed_on_non_array() {
assert!(combined_checksum_members_from_manifest(b"not json").is_err());
assert!(combined_checksum_members_from_manifest(b"{}").is_err());
assert!(
combined_checksum_members_from_manifest(b"[]")
.expect("empty array parses")
.is_empty()
);
}
#[test]
fn nondeterministic_allowlist_compile_time_wins_on_collision() {
let mut s = DeterminismState::seed_from_commit(0).expect("non-negative");
s.append_runtime(
"*.flatpak".into(),
"operator escape (wrong runtime reason)".into(),
);
let reason = s
.resolve_reason("anodizer_0.9.1_linux_amd64.flatpak")
.unwrap();
assert!(
reason.contains("OSTree"),
"compile-time reason takes precedence"
);
}
#[test]
fn nondeterministic_allowlist_serializes_with_both_categories() {
let mut s = DeterminismState::seed_from_commit(0).expect("non-negative");
s.append_runtime("foo.bin".into(), "tool-bug-1234".into());
let json = serde_json::to_string(&s).unwrap();
assert!(json.contains("compile_time_allowlist"));
assert!(json.contains("runtime_allowlist"));
assert!(json.contains("foo.bin"));
}
#[test]
fn export_env_sets_source_date_epoch() {
let s = DeterminismState::seed_from_commit(1_715_000_000).expect("non-negative");
let mut cmd = Command::new("true");
s.export_env(&mut cmd);
let env_vars: Vec<(_, _)> = cmd
.get_envs()
.filter_map(|(k, v)| v.map(|v| (k.to_owned(), v.to_owned())))
.collect();
let sde_entry = env_vars.iter().find(|(k, _)| k == "SOURCE_DATE_EPOCH");
assert!(sde_entry.is_some());
assert_eq!(sde_entry.unwrap().1, "1715000000");
}
#[test]
fn resolve_reason_returns_none_for_unrecognized() {
let s = DeterminismState::seed_from_commit(0).expect("non-negative");
assert!(s.resolve_reason("unrelated.txt").is_none());
}
#[test]
fn seed_from_commit_accepts_zero() {
let s = DeterminismState::seed_from_commit(0).expect("zero is non-negative");
assert_eq!(s.sde, 0);
}
#[test]
fn seed_from_commit_accepts_positive() {
let s = DeterminismState::seed_from_commit(1_715_000_000).expect("non-negative");
assert_eq!(s.sde, 1_715_000_000);
}
#[test]
fn seed_from_commit_rejects_negative() {
let err = DeterminismState::seed_from_commit(-1).expect_err("negative must error");
let msg = format!("{err:#}");
assert!(
msg.contains("non-negative") && msg.contains("-1"),
"error must name the bad input and the constraint: {msg}"
);
}
}