#![cfg(feature = "json")]
use shepherd_core::candidate::{
CandidateCustody, CandidateRecord, CandidateState, CompilerDigests, FileReference,
ProductArtifacts, ProductFile, ProductManifest, ProductSource, evidence_only_path,
excluded_product_path, product_symlink_target,
};
fn file() -> FileReference {
FileReference {
path: "/external/component.wasm".into(),
sha256: "b".repeat(64),
bytes: 8,
}
}
fn manifest() -> ProductManifest {
ProductManifest {
schema: "shepherd.product-manifest/1".into(),
run: "v657".into(),
source: ProductSource {
root: "/source".into(),
commit: "a".repeat(40),
tree: "b".repeat(40),
},
files: vec![ProductFile {
path: "Cargo.toml".into(),
mode: "100644".into(),
sha256: "c".repeat(64),
}],
compiler: CompilerDigests {
claude: "d".repeat(64),
codex: "e".repeat(64),
pi: "f".repeat(64),
},
artifacts: ProductArtifacts {
native_cli: file(),
component_wasm: file(),
},
}
}
fn frozen() -> CandidateRecord {
CandidateRecord::freeze(
"d".repeat(64),
"run-incarnation".into(),
manifest(),
"e".repeat(64),
)
.expect("measured native freeze")
}
#[test]
fn candidate_stage_order_and_pack_once_are_not_replayable() {
let mut candidate = frozen();
assert!(
candidate.reserve_pack().is_err(),
"pack requires source verification"
);
assert!(candidate.attest_packages(file()).is_err());
assert!(candidate.attest_lifecycle(file()).is_err());
candidate.verify_source().expect("source stage");
candidate.reserve_pack().expect("one native reservation");
assert!(candidate.reserve_pack().is_err(), "second pack must fail");
candidate
.attest_packages(file())
.expect("packages measured");
assert!(
candidate.attest_packages(file()).is_err(),
"package replay must fail"
);
candidate
.attest_lifecycle(file())
.expect("lifecycle measured");
assert!(
candidate.attest_lifecycle(file()).is_err(),
"lifecycle replay must fail"
);
assert_eq!(candidate.state, CandidateState::Attested);
candidate.validate().expect("complete typed record");
}
#[test]
fn revocation_is_terminal_and_retains_measured_proofs() {
let mut candidate = frozen();
candidate.verify_source().expect("verified");
candidate.reserve_pack().expect("reserved");
candidate.attest_packages(file()).expect("packages");
candidate
.revoke("product bytes changed".into())
.expect("revoke");
assert!(candidate.packages_manifest.is_some());
assert_eq!(
candidate.revocation_reason.as_deref(),
Some("product bytes changed")
);
assert!(candidate.verify_source().is_err());
assert!(candidate.reserve_pack().is_err());
assert!(candidate.attest_lifecycle(file()).is_err());
assert!(candidate.revoke("replay".into()).is_err());
}
#[test]
fn refreeze_requires_explicit_revocation_and_preserves_history() {
let mut custody = CandidateCustody::new(frozen()).expect("new custody");
assert!(custody.refreeze(frozen()).is_err());
custody.current.revoke("new source".into()).expect("revoke");
assert!(
custody.refreeze(frozen()).is_err(),
"candidate identity cannot replay"
);
let mut next = frozen();
next.candidate_id = "f".repeat(64);
custody.refreeze(next).expect("new identity after revoke");
assert_eq!(custody.history.len(), 1);
assert_eq!(custody.history[0].state, CandidateState::Revoked);
custody.validate().expect("history retained");
}
#[test]
fn manifest_rejects_noncanonical_incomplete_or_excluded_shapes() {
manifest().validate().expect("canonical manifest");
for path in [
"../source",
"/source",
"a//b",
"a/./b",
"a\\b",
".git/config",
".shepherd/runs/v657/run.json",
".shepherd/runs/v657/evidence/proof.json",
] {
let mut input = manifest();
input.files[0].path = path.into();
assert!(input.validate().is_err(), "unsafe product path: {path}");
}
for mode in ["100600", "160000", "120000"] {
let mut input = manifest();
input.files[0].mode = mode.into();
assert!(input.validate().is_err(), "unsupported mode: {mode}");
}
let mut input = manifest();
input.files.push(input.files[0].clone());
assert!(input.validate().is_err(), "duplicate product path");
input.files.clear();
assert!(input.validate().is_err(), "empty inventory");
let mut input = manifest();
input.source.commit = "origin/v6.5.7".into();
assert!(input.validate().is_err(), "moving ref is not a commit id");
input.source.commit = "A".repeat(40);
assert!(input.validate().is_err(), "uppercase id");
}
#[test]
fn only_five_canonical_symlinks_have_product_authority() {
for (path, target) in [
("CLAUDE.md", "AGENTS.md"),
("plugins/shepherd/agents", "../../agents"),
(
"plugins/shepherd/hooks/hooks.json",
"../../../hooks/hooks.json",
),
("plugins/shepherd/hooks/scripts", "../../../hooks/scripts"),
("plugins/shepherd/skills", "../../skills"),
] {
assert_eq!(product_symlink_target(path), Some(target));
let mut input = manifest();
input.files[0].path = path.into();
input.files[0].mode = "120000".into();
input.validate().expect("explicit product symlink");
}
assert_eq!(product_symlink_target("plugins/shepherd/other"), None);
}
#[test]
fn attestation_allowlist_never_excludes_another_runs_product() {
for path in [
".shepherd/runs/v657/evidence/one.json",
".shepherd/runs/v657/lanes/lane-a/evidence/two.json",
".shepherd/runs/v657/lanes/lane-a/handoff.md",
".shepherd/runs/v657/close.md",
".shepherd/runs/v657/handoff.md",
] {
assert!(evidence_only_path("v657", path), "{path}");
assert!(excluded_product_path(path), "{path}");
}
for path in [
".shepherd/runs/v656/evidence/one.json",
".shepherd/runs/v657/seed.md",
".shepherd/runs/v657/plan.md",
".shepherd/runs/v657/lanes/lane-a/plan.md",
".shepherd/runs/v657/reports/review.md",
".shepherd/runs/v657/evidence/../seed.md",
"src/evidence/fake.json",
".shepherd/runs/v657/run.json",
] {
assert!(!evidence_only_path("v657", path), "{path}");
}
assert!(!excluded_product_path(".shepherd/runs/v656/seed.md"));
assert!(!excluded_product_path(".shepherd/shepherd.toml"));
}
#[test]
fn generated_artifacts_are_never_product_and_attestations_stay_run_scoped() {
use shepherd_core::candidate::{evidence_only_path, excluded_product_path};
for generated in [
".artifacts/docs/index.html",
".artifacts/attestations/v660/attestation.json",
] {
assert!(
excluded_product_path(generated),
"{generated} is generated, not product"
);
}
assert!(evidence_only_path(
"v660",
".shepherd/runs/v660/evidence/attestation.json"
));
assert!(
!evidence_only_path("v660", ".shepherd/runs/v659/evidence/attestation.json"),
"another run's attestation must stay detectable as foreign"
);
assert!(!evidence_only_path(
"v660",
".artifacts/attestations/v660/attestation.json"
));
assert!(evidence_only_path(
"v660",
".shepherd/runs/v660/attestation.json"
));
assert!(
!evidence_only_path("v660", ".shepherd/runs/v659/attestation.json"),
"the run id in the path is what makes a foreign attestation detectable"
);
assert!(!evidence_only_path("v660", ".shepherd/runs/v660/run.json"));
assert!(!evidence_only_path("v660", ".shepherd/runs/v660/plan.md"));
}