use crate::format::v2::AprV2Metadata;
#[test]
fn falsify_ship_022_apr_metadata_provenance_round_trip() {
let meta = AprV2Metadata {
license: Some("Apache-2.0".to_string()),
data_source: Some("teacher-only".to_string()),
data_license: Some("Apache-2.0".to_string()),
..Default::default()
};
let json = meta.to_json().expect("serialize AprV2Metadata");
let reparsed = AprV2Metadata::from_json(&json).expect("deserialize AprV2Metadata");
assert_eq!(
reparsed.license,
Some("Apache-2.0".to_string()),
"license must round-trip byte-identically"
);
assert_eq!(
reparsed.data_source,
Some("teacher-only".to_string()),
"data_source must round-trip byte-identically"
);
assert_eq!(
reparsed.data_license,
Some("Apache-2.0".to_string()),
"data_license must round-trip byte-identically"
);
assert!(
!reparsed.custom.contains_key("license"),
"license must be a named field, not in custom"
);
assert!(
!reparsed.custom.contains_key("data_source"),
"data_source must be a named field, not in custom"
);
assert!(
!reparsed.custom.contains_key("data_license"),
"data_license must be a named field, not in custom"
);
}
#[test]
fn falsify_ship_022_inspect_emits_provenance_keys() {
let meta = AprV2Metadata {
license: None,
data_source: None,
data_license: None,
..Default::default()
};
let json_bytes = meta.to_json().expect("serialize AprV2Metadata");
let json_str = std::str::from_utf8(&json_bytes).expect("utf-8 JSON");
let parsed: serde_json::Value = serde_json::from_str(json_str).expect("parse JSON");
let obj = parsed.as_object().expect("JSON object at top level");
for key in ["license", "data_source", "data_license"] {
assert!(
obj.contains_key(key),
"AprV2Metadata JSON must emit key `{key}` even when None (no skip_serializing_if); \
violating this silently hides provenance from auditors (FM-APR-PROV-SILENT-SKIP)"
);
assert!(
obj[key].is_null(),
"key `{key}` must serialize as null when None, got {:?}",
obj[key]
);
}
}
#[test]
fn falsify_ship_022_partial_provenance_round_trip() {
let meta = AprV2Metadata {
license: Some("CC-BY-4.0".to_string()),
data_source: None,
data_license: Some("CC-BY-4.0".to_string()),
..Default::default()
};
let json = meta.to_json().expect("serialize AprV2Metadata");
let reparsed = AprV2Metadata::from_json(&json).expect("deserialize AprV2Metadata");
assert_eq!(reparsed.license, Some("CC-BY-4.0".to_string()));
assert_eq!(
reparsed.data_source, None,
"None must survive round-trip as None, not coerced to Some(\"\")"
);
assert_eq!(reparsed.data_license, Some("CC-BY-4.0".to_string()));
}
#[test]
fn falsify_ship_009_apr_metadata_applies_to_model_1_teacher() {
let teacher_meta = AprV2Metadata {
license: Some("apache-2.0".to_string()),
data_source: Some("qwen2.5-coder-7b-instruct".to_string()),
data_license: Some("apache-2.0".to_string()),
..Default::default()
};
let json = teacher_meta
.to_json()
.expect("teacher AprV2Metadata must serialize");
let reparsed = AprV2Metadata::from_json(&json).expect("teacher AprV2Metadata must deserialize");
assert_eq!(
reparsed.license,
Some("apache-2.0".to_string()),
"AC-SHIP1-009: teacher license must round-trip byte-identically"
);
assert_eq!(
reparsed.data_source,
Some("qwen2.5-coder-7b-instruct".to_string()),
"AC-SHIP1-009: teacher data_source must round-trip byte-identically"
);
assert_eq!(
reparsed.data_license,
Some("apache-2.0".to_string()),
"AC-SHIP1-009: teacher data_license must round-trip byte-identically"
);
assert!(
!reparsed.custom.contains_key("license"),
"license must remain a named field for MODEL-1 teacher too"
);
assert!(
!reparsed.custom.contains_key("data_source"),
"data_source must remain a named field for MODEL-1 teacher too"
);
assert!(
!reparsed.custom.contains_key("data_license"),
"data_license must remain a named field for MODEL-1 teacher too"
);
}
#[test]
fn falsify_ship_009_gate_apr_prov_004_has_partial_discharge_marker() {
const CONTRACT_YAML: &str = include_str!("../../../../../contracts/apr-provenance-v1.yaml");
let doc: serde_yaml::Value =
serde_yaml::from_str(CONTRACT_YAML).expect("apr-provenance-v1.yaml must parse as YAML");
let gates = doc["gates"]
.as_sequence()
.expect("gates must be a sequence in apr-provenance-v1");
let gate = gates
.iter()
.find(|g| g["id"].as_str() == Some("GATE-APR-PROV-004"))
.expect("GATE-APR-PROV-004 must exist in apr-provenance-v1");
assert_eq!(
gate["falsification_id"].as_str(),
Some("FALSIFY-SHIP-009"),
"GATE-APR-PROV-004 must bind FALSIFY-SHIP-009",
);
assert_eq!(
gate["binds_to"].as_str(),
Some("AC-SHIP1-009"),
"GATE-APR-PROV-004 must bind AC-SHIP1-009 (MODEL-1 teacher license/provenance)",
);
assert_eq!(
gate["discharge_status"].as_str(),
Some("DISCHARGED"),
"GATE-APR-PROV-004 must advertise DISCHARGED \
(live `apr stamp` fixture-swap on canonical lambda-labs staging \
artifact at v1.2.0; previous PARTIAL_ALGORITHM_LEVEL at v1.1.0)",
);
assert!(
gate["discharged_evidence"].is_mapping(),
"GATE-APR-PROV-004 DISCHARGED status requires a discharged_evidence \
block documenting the host, pre/post sha256, and tooling chain",
);
assert_eq!(
gate["discharged_evidence"]["post_stamp"]["provenance_state"]["license"].as_str(),
Some("Apache-2.0"),
"discharged_evidence.post_stamp.provenance_state.license must equal Apache-2.0",
);
assert_eq!(
gate["discharged_evidence"]["host"].as_str(),
Some("noah-Lambda-Vector"),
"discharged_evidence.host must pin to the lambda-labs RTX 4090 host",
);
assert_eq!(
gate["ship_blocking"].as_bool(),
Some(true),
"GATE-APR-PROV-004 must be ship_blocking=true",
);
let evidence = gate["evidence_discharged_by"]
.as_sequence()
.expect("GATE-APR-PROV-004 must have evidence_discharged_by");
assert!(
!evidence.is_empty(),
"GATE-APR-PROV-004 evidence_discharged_by must list at least one test",
);
let live_evidence = gate["discharged_evidence"]["evidence_discharged_by_live"]
.as_sequence()
.expect(
"GATE-APR-PROV-004 DISCHARGED requires \
discharged_evidence.evidence_discharged_by_live (live RTX 4090 evidence list)",
);
assert!(
!live_evidence.is_empty(),
"GATE-APR-PROV-004 evidence_discharged_by_live must list at least one live observation",
);
}