diff --git a/sandbox/sandbox-rs/crates/sandbox-attest/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-attest/src/lib.rs index b1b21c507..9cc2780b4 100644 --- a/sandbox/sandbox-rs/crates/sandbox-attest/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-attest/src/lib.rs @@ -258,7 +258,9 @@ pub struct Attestation { pub audit_prev: Option, /// How confinement was established (SPEC §7.9). Meaningful at S1–S5, where /// the tier is otherwise host-asserted with no in-band evidence; MUST be - /// absent at S0, which is unconfined by definition. + /// absent at S0, which is unconfined by definition. Omitted from the + /// serialized form when absent, as from the signed core (§7.9). + #[serde(default, skip_serializing_if = "Option::is_none")] pub mechanism: Option, /// Evidence bounding where the run could **not** have executed (SPEC §7.7). /// diff --git a/sandbox/sandbox-rs/crates/sandbox-conformance/src/bin/gen_vectors.rs b/sandbox/sandbox-rs/crates/sandbox-conformance/src/bin/gen_vectors.rs index c5f94db58..a72d122a3 100644 --- a/sandbox/sandbox-rs/crates/sandbox-conformance/src/bin/gen_vectors.rs +++ b/sandbox/sandbox-rs/crates/sandbox-conformance/src/bin/gen_vectors.rs @@ -12,51 +12,11 @@ use sandbox_conformance::vectors; fn main() -> std::io::Result<()> { let dir = vectors::vectors_dir(); std::fs::create_dir_all(&dir)?; - - let write = |name: &str, json: String| -> std::io::Result<()> { + for (name, bytes) in vectors::rendered() { let path = dir.join(name); - std::fs::write(&path, json + "\n")?; + std::fs::write(&path, bytes)?; println!("wrote {}", path.display()); - Ok(()) - }; - - write( - "content_addressing.json", - serde_json::to_string_pretty(&vectors::content_vectors()).unwrap(), - )?; - write( - "capability.json", - serde_json::to_string_pretty(&vectors::capability_vectors()).unwrap(), - )?; - write( - "receipt.json", - serde_json::to_string_pretty(&vectors::receipt_vector()).unwrap(), - )?; - write( - "attestation.json", - serde_json::to_string_pretty(&vectors::attestation_vector()).unwrap(), - )?; - write( - "tier.json", - serde_json::to_string_pretty(&vectors::tier_vectors()).unwrap(), - )?; - write( - "durable.json", - serde_json::to_string_pretty(&vectors::durable_vector()).unwrap(), - )?; - write( - "negative.json", - serde_json::to_string_pretty(&vectors::negative_pack()).unwrap(), - )?; - write( - "confidential.json", - serde_json::to_string_pretty(&vectors::confidential_pack()).unwrap(), - )?; - write( - "flow.json", - serde_json::to_string_pretty(&vectors::flow_vectors()).unwrap(), - )?; - + } println!("\nRegenerated conformance vectors. Verify with `cargo test -p sandbox-conformance`."); Ok(()) } diff --git a/sandbox/sandbox-rs/crates/sandbox-conformance/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-conformance/src/lib.rs index a56ebb02f..7f0cd372f 100644 --- a/sandbox/sandbox-rs/crates/sandbox-conformance/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-conformance/src/lib.rs @@ -714,6 +714,25 @@ mod tests { assert_eq!(report.results.len(), 13); } + /// The committed pack is exactly what the builders emit, file for file + /// and byte for byte. The file-backed checks prove each committed file is + /// valid; this proves it is the current one. Without it, a change to a + /// builder or to a type's serialized shape left the published pack behind, + /// and nothing noticed: `"mechanism": null` sat unpublished in the + /// attestation and negative packs for a release. + #[test] + fn the_committed_pack_is_what_the_builders_emit() { + use vectors::*; + let rendered = rendered(); + let names: Vec<&str> = rendered.iter().map(|(n, _)| *n).collect(); + let listed: Vec<&str> = FILES.iter().map(|(n, _)| *n).collect(); + assert_eq!(names, listed, "the rendered set is the listed set"); + for (name, bytes) in rendered { + let committed = std::fs::read_to_string(vectors_dir().join(name)).unwrap(); + assert!(committed == bytes, "{name} differs from what the builders emit: run gen_vectors and commit the result"); + } + } + #[test] fn vector_builders_round_trip_in_memory() { // The reference's own vector builders must satisfy their own checkers diff --git a/sandbox/sandbox-rs/crates/sandbox-conformance/src/vectors.rs b/sandbox/sandbox-rs/crates/sandbox-conformance/src/vectors.rs index ac0e383b9..95ce0b1dd 100644 --- a/sandbox/sandbox-rs/crates/sandbox-conformance/src/vectors.rs +++ b/sandbox/sandbox-rs/crates/sandbox-conformance/src/vectors.rs @@ -997,6 +997,25 @@ pub fn vectors_dir() -> std::path::PathBuf { .join("../../../conformance/v1") } +/// Every committed vector file, by name, as the `gen_vectors` binary writes +/// it: the pretty JSON of its builder and a final newline. A test holds the +/// committed pack to this, so the builders and the published files cannot +/// part. +pub fn rendered() -> Vec<(&'static str, String)> { + let pretty = |v: serde_json::Result| v.expect("a vector serializes") + "\n"; + vec![ + ("content_addressing.json", pretty(serde_json::to_string_pretty(&content_vectors()))), + ("capability.json", pretty(serde_json::to_string_pretty(&capability_vectors()))), + ("receipt.json", pretty(serde_json::to_string_pretty(&receipt_vector()))), + ("attestation.json", pretty(serde_json::to_string_pretty(&attestation_vector()))), + ("tier.json", pretty(serde_json::to_string_pretty(&tier_vectors()))), + ("durable.json", pretty(serde_json::to_string_pretty(&durable_vector()))), + ("negative.json", pretty(serde_json::to_string_pretty(&negative_pack()))), + ("confidential.json", pretty(serde_json::to_string_pretty(&confidential_pack()))), + ("flow.json", pretty(serde_json::to_string_pretty(&flow_vectors()))), + ] +} + pub const FILES: [(&str, &str); 9] = [ ("content_addressing.json", "C1"), ("capability.json", "C2"),