use super::*;
#[test]
fn manifest_gate_uses_consumed_sources_and_reports_each_invalid_entry() {
use canic_core::cdk::utils::hash::sha256_hex;
for required in [false, true] {
let root = temp_dir("canic-policy-manifest-invalid-entries");
fs::create_dir_all(&root).unwrap();
let policy_path = root.join("policy.toml");
let manifest_path = root.join("evidence.toml");
let mut manifest_source = sample_manifest_source("invalid.json", required);
for path in ["unreadable.json", "valid.json"] {
let entry_source = sample_manifest_source(path, required);
manifest_source.push_str(&entry_source[entry_source.find("[[evidence]]").unwrap()..]);
}
let invalid_bytes = b"{\"truncated\":";
fs::write(root.join("invalid.json"), invalid_bytes).unwrap();
fs::create_dir(root.join("unreadable.json")).unwrap();
let valid_bytes = serde_json::to_vec_pretty(&sample_envelope()).unwrap();
fs::write(root.join("valid.json"), &valid_bytes).unwrap();
fs::write(&policy_path, "[replaced").unwrap();
fs::write(&manifest_path, "[replaced").unwrap();
let report =
evaluate_workspace_evidence_manifest_gate(WorkspaceEvidenceManifestGateRequest {
policy_source: BUILD_PROVENANCE_POLICY,
policy_path: &policy_path,
manifest_source: &manifest_source,
manifest_path: &manifest_path,
fingerprint_root: &root,
})
.expect("invalid entries must retain the complete report");
let expected_status = if required {
PolicyEvaluationStatusV1::Failed
} else {
PolicyEvaluationStatusV1::Passed
};
let expected_class = if required {
ExitClassV1::InvalidInput
} else {
ExitClassV1::SuccessWithWarnings
};
let expected_severity = if required {
PolicyFindingSeverityV1::Error
} else {
PolicyFindingSeverityV1::Warning
};
assert_eq!(report.policy_status, expected_status);
assert_eq!(report.gate_exit_class, expected_class);
assert_eq!(report.evidence.len(), 3);
for entry in &report.evidence[..2] {
assert_eq!(entry.status, expected_status);
assert_eq!(entry.gate_exit_class, expected_class);
assert_eq!(entry.findings[0].code, "policy.manifest.invalid_input");
assert_eq!(entry.findings[0].severity, expected_severity);
assert_eq!(entry.findings[0].subject_exit_class(), Some(expected_class));
assert!(entry.policy_report.is_none());
}
assert!(report.evidence[1].evaluated_envelope_fingerprint.is_none());
assert_eq!(report.evidence[2].status, PolicyEvaluationStatusV1::Passed);
assert!(report.evidence[2].policy_report.is_some());
for (fingerprint, bytes) in [
(
&report.policy_file_fingerprint,
BUILD_PROVENANCE_POLICY.as_bytes(),
),
(
&report.manifest_file_fingerprint,
manifest_source.as_bytes(),
),
(
report.evidence[0]
.evaluated_envelope_fingerprint
.as_ref()
.unwrap(),
invalid_bytes.as_slice(),
),
(
report.evidence[2]
.evaluated_envelope_fingerprint
.as_ref()
.unwrap(),
valid_bytes.as_slice(),
),
] {
assert_eq!(fingerprint.sha256, Some(sha256_hex(bytes)));
assert_eq!(fingerprint.size_bytes, Some(bytes.len() as u64));
}
fs::remove_dir_all(root).expect("clean invalid entry fixture");
}
}
#[test]
fn workspace_evidence_manifest_gate_evaluates_required_envelope() {
let root = temp_dir("canic-policy-manifest-pass");
fs::create_dir_all(&root).expect("create root");
let policy_path = root.join("policy.toml");
let manifest_path = root.join("evidence.toml");
let envelope_path = root.join("build.json");
fs::write(&policy_path, BUILD_PROVENANCE_POLICY).expect("write policy");
fs::write(
&envelope_path,
serde_json::to_vec(&sample_envelope()).expect("encode envelope"),
)
.expect("write envelope");
let manifest_source = sample_manifest_source("build.json", true);
fs::write(&manifest_path, &manifest_source).expect("write manifest");
let report = evaluate_workspace_evidence_manifest_gate(WorkspaceEvidenceManifestGateRequest {
policy_source: BUILD_PROVENANCE_POLICY,
policy_path: &policy_path,
manifest_source: &manifest_source,
manifest_path: &manifest_path,
fingerprint_root: &root,
})
.expect("evaluate manifest gate");
fs::remove_dir_all(root).expect("clean");
assert_eq!(report.policy_status, PolicyEvaluationStatusV1::Passed);
assert_eq!(report.gate_exit_class, ExitClassV1::Success);
assert_eq!(report.evidence.len(), 1);
assert_eq!(report.evidence[0].status, PolicyEvaluationStatusV1::Passed);
assert!(report.evidence[0].policy_report.is_some());
}
#[test]
fn workspace_evidence_manifest_gate_reports_missing_required_and_optional_evidence() {
let required_report = evaluate_manifest_gate_for_test(
&sample_manifest_source("missing.json", true),
BUILD_PROVENANCE_POLICY,
);
assert_eq!(
required_report.gate_exit_class,
ExitClassV1::MissingRequiredEvidence
);
assert_eq!(
required_report.evidence[0].status,
PolicyEvaluationStatusV1::Failed
);
assert_eq!(
required_report.evidence[0].findings[0].code,
"policy.manifest.required_evidence_missing"
);
let optional_report = evaluate_manifest_gate_for_test(
&sample_manifest_source("missing.json", false),
BUILD_PROVENANCE_POLICY,
);
assert_eq!(
optional_report.gate_exit_class,
ExitClassV1::SuccessWithWarnings
);
assert_eq!(
optional_report.evidence[0].status,
PolicyEvaluationStatusV1::Passed
);
assert_eq!(
optional_report.evidence[0].findings[0].code,
"policy.manifest.optional_evidence_missing"
);
}
#[test]
fn workspace_evidence_manifest_gate_checks_target_and_payload_schema_expectations() {
let mut wrong_schema = sample_envelope();
wrong_schema.payload_schema = PayloadSchemaRefV1::stable("canic.other.v1", "1");
let wrong_schema_report = evaluate_manifest_gate_with_envelope(
&sample_manifest_source("build.json", true),
wrong_schema,
);
assert_eq!(
wrong_schema_report.gate_exit_class,
ExitClassV1::BlockedByPolicy
);
assert!(
wrong_schema_report.evidence[0]
.findings
.iter()
.any(|finding| finding.code == "policy.manifest.payload_schema_mismatch")
);
let mut wrong_target = sample_envelope();
wrong_target.target.role = Some("other".to_string());
let wrong_target_report = evaluate_manifest_gate_with_envelope(
&sample_manifest_source("build.json", true),
wrong_target,
);
assert_eq!(
wrong_target_report.gate_exit_class,
ExitClassV1::BlockedByPolicy
);
assert!(
wrong_target_report.evidence[0]
.findings
.iter()
.any(|finding| finding.code == "policy.manifest.target_mismatch")
);
}
#[test]
fn workspace_evidence_manifest_rejects_duplicate_evidence_paths() {
let manifest_source = r#"
schema_version = 1
[workspace]
name = "demo"
root = "."
[[evidence]]
kind = "build_provenance"
path = "build.json"
required = true
payload_schema = "canic.build_provenance.v1"
[evidence.target]
app = "demo"
role = "app"
[[evidence]]
kind = "policy_gate"
path = " ./build.json "
required = true
payload_schema = "canic.policy_gate_report.v1"
[evidence.target]
fleet = "demo-staging"
"#;
let error = parse_workspace_evidence_manifest_v1(manifest_source)
.expect_err("duplicate evidence path should fail");
assert!(matches!(error, PolicyGateError::InvalidPolicy(_)));
assert!(
error
.to_string()
.contains("duplicates an earlier evidence path")
);
}