use std::path::Path;
use crate::attest::{AttestError, AttestationStatement};
pub const STATEMENT_ARTIFACT_TYPE: &str = crate::attest::PAYLOAD_TYPE;
pub const ATTESTATION_ARTIFACT_TYPE: &str =
"application/vnd.pulseengine.varve.attestation-bytes.v1";
pub const ANN_STATEMENT: &str = "eu.pulseengine.varve.attests";
#[derive(Debug, thiserror::Error)]
pub enum CarryError {
#[error(transparent)]
Attest(#[from] AttestError),
#[error("io error at {path}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error("{path}: {reason}")]
Layout { path: String, reason: String },
#[error(
"layer {layer} carries an attestation statement whose attested bytes are not present in \
the layout (statement {statement}, expected blob {digest}). The statement travelled and \
the evidence did not — which is exactly the mirror-boundary failure carriage exists to \
prevent."
)]
OrphanStatement {
layer: String,
statement: String,
digest: String,
},
#[error(
"layer {layer}: the layout index names an attestation blob '{digest}', which is not a \
sha256 content address — refusing to resolve it as a path"
)]
MalformedDigest { layer: String, digest: String },
#[error(transparent)]
Source(#[from] crate::source::SourceError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CarriedAttestation {
pub statement_digest: String,
pub statement: Vec<u8>,
pub bytes: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttestationReport {
pub kind: String,
pub producer: String,
pub binds: bool,
pub reason: Option<String>,
}
fn io(path: &Path, source: std::io::Error) -> CarryError {
CarryError::Io {
path: path.display().to_string(),
source,
}
}
fn blob_path(layout: &Path, digest: &str) -> std::path::PathBuf {
layout
.join("blobs")
.join("sha256")
.join(digest.strip_prefix("sha256:").unwrap_or(digest))
}
fn is_content_address(digest: &str) -> bool {
digest
.strip_prefix("sha256:")
.is_some_and(|hex| hex.len() == 64 && hex.bytes().all(|b| b.is_ascii_hexdigit()))
}
pub fn attach(
layout: &Path,
statement_envelope: &[u8],
attested_bytes: &[u8],
) -> Result<String, CarryError> {
let st_digest = crate::store::manifest_digest(statement_envelope);
let bytes_digest = crate::store::manifest_digest(attested_bytes);
let dir = layout.join("blobs").join("sha256");
std::fs::create_dir_all(&dir).map_err(|e| io(&dir, e))?;
for (digest, content) in [
(&st_digest, statement_envelope),
(&bytes_digest, attested_bytes),
] {
let p = blob_path(layout, digest);
std::fs::write(&p, content).map_err(|e| io(&p, e))?;
}
let index_path = layout.join("index.json");
let mut index: serde_json::Value =
serde_json::from_slice(&std::fs::read(&index_path).map_err(|e| io(&index_path, e))?)
.map_err(|e| CarryError::Layout {
path: index_path.display().to_string(),
reason: format!("index.json: {e}"),
})?;
let entries = index["manifests"]
.as_array_mut()
.ok_or_else(|| CarryError::Layout {
path: index_path.display().to_string(),
reason: "index.json has no manifests array".into(),
})?;
entries.retain(|e| {
!(e["digest"] == *st_digest
|| (e["artifactType"] == ATTESTATION_ARTIFACT_TYPE
&& e["annotations"][ANN_STATEMENT] == *st_digest))
});
entries.push(serde_json::json!({
"mediaType": "application/json",
"artifactType": STATEMENT_ARTIFACT_TYPE,
"digest": st_digest,
"size": statement_envelope.len(),
}));
entries.push(serde_json::json!({
"mediaType": "application/octet-stream",
"artifactType": ATTESTATION_ARTIFACT_TYPE,
"digest": bytes_digest,
"size": attested_bytes.len(),
"annotations": { ANN_STATEMENT: st_digest }
}));
std::fs::write(
&index_path,
serde_json::to_vec_pretty(&index).expect("index serializes"),
)
.map_err(|e| io(&index_path, e))?;
Ok(st_digest)
}
pub fn read_all(layout: &Path, layer: &str) -> Result<Vec<CarriedAttestation>, CarryError> {
let index_path = layout.join("index.json");
let bytes = match std::fs::read(&index_path) {
Ok(b) => b,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(source) => return Err(io(&index_path, source)),
};
let index: serde_json::Value =
serde_json::from_slice(&bytes).map_err(|e| CarryError::Layout {
path: index_path.display().to_string(),
reason: format!("index.json: {e}"),
})?;
let Some(entries) = index["manifests"].as_array() else {
return Ok(Vec::new());
};
let mut out = Vec::new();
for entry in entries {
if entry["artifactType"] != STATEMENT_ARTIFACT_TYPE {
continue;
}
let Some(st_digest) = entry["digest"].as_str() else {
continue;
};
if !is_content_address(st_digest) {
return Err(CarryError::MalformedDigest {
layer: layer.to_string(),
digest: st_digest.to_string(),
});
}
let st_path = blob_path(layout, st_digest);
let statement = std::fs::read(&st_path).map_err(|e| io(&st_path, e))?;
let bytes_digest = entries
.iter()
.find(|e| {
e["artifactType"] == ATTESTATION_ARTIFACT_TYPE
&& e["annotations"][ANN_STATEMENT] == *st_digest
})
.and_then(|e| e["digest"].as_str());
let Some(bytes_digest) = bytes_digest else {
return Err(CarryError::OrphanStatement {
layer: layer.to_string(),
statement: st_digest.to_string(),
digest: "<no referrer entry>".into(),
});
};
if !is_content_address(bytes_digest) {
return Err(CarryError::MalformedDigest {
layer: layer.to_string(),
digest: bytes_digest.to_string(),
});
}
let b_path = blob_path(layout, bytes_digest);
let attested = std::fs::read(&b_path).map_err(|_| CarryError::OrphanStatement {
layer: layer.to_string(),
statement: st_digest.to_string(),
digest: bytes_digest.to_string(),
})?;
out.push(CarriedAttestation {
statement_digest: st_digest.to_string(),
statement,
bytes: attested,
});
}
out.sort_by(|a, b| a.statement_digest.cmp(&b.statement_digest));
Ok(out)
}
pub fn report(
carried: &[CarriedAttestation],
layer_manifest_digest: &str,
layer_name: &str,
root_pk: &[u8],
) -> Vec<AttestationReport> {
carried
.iter()
.map(|c| {
let st: AttestationStatement =
match crate::attest::verify_statement(&c.statement, root_pk) {
Ok(st) => st,
Err(e) => {
return AttestationReport {
kind: "<unverified>".into(),
producer: "<unverified>".into(),
binds: false,
reason: Some(e.to_string()),
};
}
};
let reason = crate::attest::check(&st, &c.bytes, layer_manifest_digest, layer_name)
.err()
.map(|e| e.to_string());
AttestationReport {
kind: st.kind.to_string(),
producer: st.producer.clone(),
binds: reason.is_none(),
reason,
}
})
.collect()
}
pub const STORE_DIR: &str = "attestations";
pub fn persist(layer_root: &Path, carried: &[CarriedAttestation]) -> Result<(), CarryError> {
if carried.is_empty() {
return Ok(());
}
let dir = layer_root.join(STORE_DIR);
std::fs::create_dir_all(&dir).map_err(|e| io(&dir, e))?;
for c in carried {
let digest = crate::store::manifest_digest(&c.statement);
let stem = digest.strip_prefix("sha256:").unwrap_or(&digest);
let st = dir.join(format!("{stem}.statement.json"));
std::fs::write(&st, &c.statement).map_err(|e| io(&st, e))?;
let by = dir.join(format!("{stem}.bytes"));
std::fs::write(&by, &c.bytes).map_err(|e| io(&by, e))?;
}
Ok(())
}
pub fn read_persisted(
layer_root: &Path,
layer: &str,
) -> Result<Vec<CarriedAttestation>, CarryError> {
let dir = layer_root.join(STORE_DIR);
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(source) => return Err(io(&dir, source)),
};
let mut out = Vec::new();
for entry in entries {
let path = entry.map_err(|e| io(&dir, e))?.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
let Some(stem) = name.strip_suffix(".statement.json") else {
continue;
};
let statement = std::fs::read(&path).map_err(|e| io(&path, e))?;
let bytes_path = dir.join(format!("{stem}.bytes"));
let bytes = std::fs::read(&bytes_path).map_err(|_| CarryError::OrphanStatement {
layer: layer.to_string(),
statement: format!("sha256:{stem}"),
digest: bytes_path.display().to_string(),
})?;
out.push(CarriedAttestation {
statement_digest: format!("sha256:{stem}"),
statement,
bytes,
});
}
out.sort_by(|a, b| a.statement_digest.cmp(&b.statement_digest));
Ok(out)
}
pub fn carry_from_source(
source: &dyn crate::source::LayerSource,
layer: &crate::source::LayerRef,
layer_root: &Path,
) -> Result<usize, CarryError> {
let carried = source.fetch_attestations(layer)?;
persist(layer_root, &carried)?;
Ok(carried.len())
}
pub fn report_installed(
layer_root: &Path,
layer_name: &str,
layer_manifest_digest: &str,
root_pk: &[u8],
) -> Result<Vec<AttestationReport>, CarryError> {
let carried = read_persisted(layer_root, layer_name)?;
Ok(report(&carried, layer_manifest_digest, layer_name, root_pk))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attest::{AttestationKind, sign, statement};
use crate::verify::generate_root_keypair;
fn layout() -> tempfile::TempDir {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("index.json"),
br#"{"schemaVersion":2,"manifests":[]}"#,
)
.unwrap();
tmp
}
const LAYER: &str = "2026.08.0";
const LAYER_DIGEST: &str =
"sha256:1111111111111111111111111111111111111111111111111111111111111111";
#[test]
fn an_attestation_travels_with_the_layer_and_still_binds() {
let (sk, pk) = generate_root_keypair();
let tmp = layout();
let bytes = br#"{"_type":"https://in-toto.io/Statement/v1","subject":[]}"#;
let st = statement(
LAYER,
LAYER_DIGEST,
AttestationKind::Provenance,
bytes,
"acme-ci",
);
let envelope = sign(&st, &sk, "root-1").unwrap();
attach(tmp.path(), envelope.as_bytes(), bytes).unwrap();
let carried = read_all(tmp.path(), LAYER).unwrap();
assert_eq!(carried.len(), 1, "one attestation carried");
assert_eq!(
carried[0].bytes, bytes,
"the attested bytes travel VERBATIM — varve carries another party's \
judgement, it does not restate it"
);
let reports = report(&carried, LAYER_DIGEST, LAYER, &pk);
assert_eq!(reports.len(), 1);
assert!(reports[0].binds, "reason: {:?}", reports[0].reason);
assert_eq!(reports[0].producer, "acme-ci");
}
#[test]
fn a_statement_whose_evidence_did_not_travel_is_an_error_not_a_skip() {
let (sk, _pk) = generate_root_keypair();
let tmp = layout();
let bytes = b"the-evidence";
let st = statement(LAYER, LAYER_DIGEST, AttestationKind::Sbom, bytes, "acme-ci");
let envelope = sign(&st, &sk, "root-1").unwrap();
attach(tmp.path(), envelope.as_bytes(), bytes).unwrap();
std::fs::remove_file(blob_path(tmp.path(), &crate::store::manifest_digest(bytes))).unwrap();
let err = read_all(tmp.path(), LAYER).expect_err("an orphaned statement must be an error");
let msg = err.to_string();
assert!(msg.contains(LAYER), "names the layer: {msg}");
assert!(
msg.contains("did not"),
"says the evidence failed to travel, not merely that a file is absent: {msg}"
);
}
#[test]
fn an_attestation_for_another_layer_is_reported_as_not_binding() {
let (sk, pk) = generate_root_keypair();
let tmp = layout();
let bytes = b"evidence-for-someone-else";
let other = "sha256:2222222222222222222222222222222222222222222222222222222222222222";
let st = statement(
"2026.01.0",
other,
AttestationKind::Provenance,
bytes,
"acme-ci",
);
attach(
tmp.path(),
sign(&st, &sk, "root-1").unwrap().as_bytes(),
bytes,
)
.unwrap();
let carried = read_all(tmp.path(), LAYER).unwrap();
let reports = report(&carried, LAYER_DIGEST, LAYER, &pk);
assert!(
!reports[0].binds,
"a statement for another layer must not bind"
);
assert!(
reports[0].reason.as_ref().unwrap().contains("refusing"),
"the reason must say what was refused: {:?}",
reports[0].reason
);
}
#[test]
fn an_attestation_signed_by_someone_else_does_not_bind() {
let (impostor_sk, _) = generate_root_keypair();
let (_realm_sk, realm_pk) = generate_root_keypair();
let tmp = layout();
let bytes = b"evidence";
let st = statement(
LAYER,
LAYER_DIGEST,
AttestationKind::Provenance,
bytes,
"acme-ci",
);
attach(
tmp.path(),
sign(&st, &impostor_sk, "not-the-realm").unwrap().as_bytes(),
bytes,
)
.unwrap();
let carried = read_all(tmp.path(), LAYER).unwrap();
let reports = report(&carried, LAYER_DIGEST, LAYER, &realm_pk);
assert!(!reports[0].binds);
assert_eq!(
reports[0].kind, "<unverified>",
"an unverified statement's own claims must not be echoed as fact"
);
}
#[test]
fn many_attestations_travel_together_and_reattaching_is_idempotent() {
let (sk, pk) = generate_root_keypair();
let tmp = layout();
let sbom = b"sbom-bytes";
let slsa = b"slsa-bytes";
for (kind, bytes) in [
(AttestationKind::Sbom, sbom.as_slice()),
(AttestationKind::Provenance, slsa.as_slice()),
] {
let st = statement(LAYER, LAYER_DIGEST, kind, bytes, "acme-ci");
attach(
tmp.path(),
sign(&st, &sk, "root-1").unwrap().as_bytes(),
bytes,
)
.unwrap();
}
assert_eq!(
read_all(tmp.path(), LAYER).unwrap().len(),
2,
"both carried"
);
let st = statement(LAYER, LAYER_DIGEST, AttestationKind::Sbom, sbom, "acme-ci");
attach(
tmp.path(),
sign(&st, &sk, "root-1").unwrap().as_bytes(),
sbom,
)
.unwrap();
let carried = read_all(tmp.path(), LAYER).unwrap();
assert_eq!(carried.len(), 2, "re-attaching replaces, never duplicates");
let index: serde_json::Value =
serde_json::from_slice(&std::fs::read(tmp.path().join("index.json")).unwrap()).unwrap();
assert_eq!(
index["manifests"].as_array().unwrap().len(),
4,
"two attestations are two statements and two payloads — no more, however \
many times CI re-runs: {index:#}"
);
let mut kinds: Vec<String> = report(&carried, LAYER_DIGEST, LAYER, &pk)
.into_iter()
.map(|r| r.kind)
.collect();
kinds.sort();
assert_eq!(
kinds,
vec!["provenance".to_string(), "sbom".to_string()],
"re-attaching one attestation must not evict the others — that is the one \
deliberate difference from line-status, where attaching REPLACES"
);
assert!(
report(&carried, LAYER_DIGEST, LAYER, &pk)
.iter()
.all(|r| r.binds)
);
}
#[test]
fn evidence_survives_the_store_and_can_be_re_emitted() {
let (sk, pk) = generate_root_keypair();
let src = layout();
let sbom = b"sbom-bytes";
let prov = b"provenance-bytes";
for (kind, bytes) in [
(AttestationKind::Sbom, sbom.as_slice()),
(AttestationKind::Provenance, prov.as_slice()),
] {
let st = statement(LAYER, LAYER_DIGEST, kind, bytes, "acme-ci");
attach(
src.path(),
sign(&st, &sk, "root-1").unwrap().as_bytes(),
bytes,
)
.unwrap();
}
let carried = read_all(src.path(), LAYER).unwrap();
let installed = tempfile::tempdir().unwrap();
persist(installed.path(), &carried).unwrap();
let back = read_persisted(installed.path(), LAYER).unwrap();
assert_eq!(back.len(), 2, "both attestations survive the store");
let dest = layout();
for c in &back {
attach(dest.path(), &c.statement, &c.bytes).unwrap();
}
let round_tripped = read_all(dest.path(), LAYER).unwrap();
assert_eq!(
round_tripped, carried,
"the evidence that crossed the gap is the evidence that was signed"
);
assert!(
report(&round_tripped, LAYER_DIGEST, LAYER, &pk)
.iter()
.all(|r| r.binds),
"every attestation still binds on the far side"
);
}
#[test]
fn verify_reports_each_carried_attestation_and_whether_it_still_binds() {
let (sk, pk) = generate_root_keypair();
let installed = tempfile::tempdir().unwrap();
let mine = b"sbom-for-this-layer";
let theirs = b"audit-for-another-layer";
let good = statement(LAYER, LAYER_DIGEST, AttestationKind::Sbom, mine, "acme-ci");
let other = statement(
"2026.01.0",
"sha256:2222222222222222222222222222222222222222222222222222222222222222",
AttestationKind::Audit,
theirs,
"someone-else",
);
let carried: Vec<CarriedAttestation> =
[(&good, mine.as_slice()), (&other, theirs.as_slice())]
.into_iter()
.map(|(st, bytes)| {
let envelope = sign(st, &sk, "root-1").unwrap();
CarriedAttestation {
statement_digest: crate::store::manifest_digest(envelope.as_bytes()),
statement: envelope.into_bytes(),
bytes: bytes.to_vec(),
}
})
.collect();
persist(installed.path(), &carried).unwrap();
let reports = report_installed(installed.path(), LAYER, LAYER_DIGEST, &pk).unwrap();
assert_eq!(reports.len(), 2, "verify must report EVERY attestation");
let sbom = reports.iter().find(|r| r.kind == "sbom").expect("sbom");
assert!(sbom.binds, "reason: {:?}", sbom.reason);
assert_eq!(sbom.producer, "acme-ci");
let audit = reports.iter().find(|r| r.kind == "audit").expect("audit");
assert!(
!audit.binds,
"a statement issued for another layer must not be reported as binding"
);
assert!(
audit.reason.as_ref().unwrap().contains("refusing"),
"the reason must say what was refused: {:?}",
audit.reason
);
}
#[test]
fn a_layer_carrying_nothing_reports_nothing_rather_than_failing() {
let (_sk, pk) = generate_root_keypair();
let installed = tempfile::tempdir().unwrap();
assert!(
report_installed(installed.path(), LAYER, LAYER_DIGEST, &pk)
.unwrap()
.is_empty()
);
}
#[test]
fn install_time_carriage_takes_what_the_source_holds_and_trusts_none_of_it() {
let (impostor, _) = generate_root_keypair();
let (_realm_sk, realm_pk) = generate_root_keypair();
let bytes = b"evidence";
let st = statement(LAYER, LAYER_DIGEST, AttestationKind::Vex, bytes, "acme-ci");
let source = crate::source::MemorySource::new().with_attestation(
sign(&st, &impostor, "not-the-realm").unwrap().as_bytes(),
bytes,
);
let installed = tempfile::tempdir().unwrap();
let n = carry_from_source(
&source,
&crate::source::LayerRef::Name(LAYER.parse().unwrap()),
installed.path(),
)
.unwrap();
assert_eq!(n, 1, "the evidence is carried, not judged, at fetch time");
let reports = report_installed(installed.path(), LAYER, LAYER_DIGEST, &realm_pk).unwrap();
assert!(
!reports[0].binds,
"a statement signed by anyone but the realm's root must not bind"
);
assert_eq!(
reports[0].kind, "<unverified>",
"an unverified statement's own claims must not be echoed as fact"
);
}
#[test]
fn a_layout_naming_a_blob_outside_itself_is_refused_before_anything_is_read() {
for bad in [
"sha256:../../../../../../etc/passwd",
"sha256:abc",
"sha256:",
"not-a-digest-at-all",
] {
let tmp = layout();
std::fs::write(
tmp.path().join("index.json"),
serde_json::to_vec_pretty(&serde_json::json!({
"schemaVersion": 2,
"manifests": [{
"mediaType": "application/json",
"artifactType": STATEMENT_ARTIFACT_TYPE,
"digest": bad,
"size": 1,
}]
}))
.unwrap(),
)
.unwrap();
let err = read_all(tmp.path(), LAYER).expect_err("'{bad}' is not a content address");
assert!(
matches!(err, CarryError::MalformedDigest { .. }),
"'{bad}' must be refused as a malformed digest, not resolved as a path: {err}"
);
}
}
#[test]
fn persist_names_files_after_the_bytes_not_after_what_the_source_declared() {
let (sk, _pk) = generate_root_keypair();
let installed = tempfile::tempdir().unwrap();
let bytes = b"evidence";
let st = statement(LAYER, LAYER_DIGEST, AttestationKind::Sbom, bytes, "acme");
let envelope = sign(&st, &sk, "root-1").unwrap();
persist(
installed.path(),
&[CarriedAttestation {
statement_digest: "sha256:../../../../pwned".into(),
statement: envelope.clone().into_bytes(),
bytes: bytes.to_vec(),
}],
)
.unwrap();
let hex = crate::store::manifest_digest(envelope.as_bytes())
.strip_prefix("sha256:")
.unwrap()
.to_string();
assert!(
installed
.path()
.join(STORE_DIR)
.join(format!("{hex}.statement.json"))
.is_file(),
"the file must be named after the bytes' own content address"
);
assert!(
!installed.path().parent().unwrap().join("pwned").exists()
&& !installed.path().join("pwned").exists(),
"no file may land outside the attestation store"
);
assert_eq!(read_persisted(installed.path(), LAYER).unwrap().len(), 1);
}
#[test]
fn an_absent_layout_carries_nothing_but_an_unreadable_one_is_not_silently_empty() {
let empty = tempfile::tempdir().unwrap();
assert!(
read_all(empty.path(), LAYER).unwrap().is_empty(),
"an absent index.json is an empty layout, not an error"
);
#[cfg(unix)]
{
let broken = tempfile::tempdir().unwrap();
std::fs::create_dir(broken.path().join("index.json")).unwrap();
assert!(
read_all(broken.path(), LAYER).is_err(),
"an unreadable index must be an error, never an empty answer"
);
}
}
#[test]
fn an_unreadable_attestation_store_is_an_error_not_an_empty_answer() {
let none = tempfile::tempdir().unwrap();
assert!(read_persisted(none.path(), LAYER).unwrap().is_empty());
#[cfg(unix)]
{
let broken = tempfile::tempdir().unwrap();
std::fs::write(broken.path().join(STORE_DIR), b"not a directory").unwrap();
assert!(
read_persisted(broken.path(), LAYER).is_err(),
"a store that cannot be read must not read back as 'this layer carries none'"
);
}
}
#[test]
fn a_store_that_lost_the_evidence_is_an_error_on_read_back_too() {
let (sk, _pk) = generate_root_keypair();
let installed = tempfile::tempdir().unwrap();
let bytes = b"evidence";
let st = statement(LAYER, LAYER_DIGEST, AttestationKind::Audit, bytes, "acme");
persist(
installed.path(),
&[CarriedAttestation {
statement_digest: crate::store::manifest_digest(
sign(&st, &sk, "k").unwrap().as_bytes(),
),
statement: sign(&st, &sk, "k").unwrap().into_bytes(),
bytes: bytes.to_vec(),
}],
)
.unwrap();
for e in std::fs::read_dir(installed.path().join(STORE_DIR)).unwrap() {
let p = e.unwrap().path();
if p.extension().is_some_and(|x| x == "bytes") {
std::fs::remove_file(p).unwrap();
}
}
assert!(
matches!(
read_persisted(installed.path(), LAYER),
Err(CarryError::OrphanStatement { .. })
),
"a statement whose evidence is gone must not read back as absent"
);
}
}