//! The wave-2 code-shape proof (posture-A/C shape): a codec/JIT-shaped //! fixture set rendered entirely through the public API and pinned by //! goldens. Where the wave-1 code double proved the machinery minimally, //! this proof grows the set at fuller scale: document-ordered arrays //! over the `preserve_order` load-bearing behavior, no-op escaping over //! source-escaped literals, and the determinism invariant the posture-A //! regeneration story rests on: byte-identical output across repeated //! renders, registry rebuilds, registration orders, and both sinks //! (ADR-003). On shipped code, this is the row generated-artifact //! hygiene is built on (emit.md posture A), minus the DO-NOT-EDIT //! banner, which is an emit-layer obligation with no engine-owned set //! in v0.1.0 to exercise it (recorded in the task Notes; the tests here //! assert nothing banner-related). use alkgen::{EscapePolicy, TemplateSet}; use serde::Serialize; mod fixtures { pub const SCHEMA: &str = include_str!("fixtures/code_proof/schema.json"); pub const WRITER: &str = include_str!("fixtures/code_proof/writer.hbs"); pub const VALIDATOR: &str = include_str!("fixtures/code_proof/validator.hbs"); pub const FIELD_WRITE_LINE: &str = include_str!("fixtures/code_proof/field_write_line.hbs"); } /// The registration-order variants the determinism proof rebuilds /// over: same assets, same final registry contents, changed /// registration sequence. #[derive(Clone, Copy)] enum Asset { Writer, Validator, FieldWriteLine, } const ORDER_A: [Asset; 3] = [Asset::Writer, Asset::Validator, Asset::FieldWriteLine]; const ORDER_B: [Asset; 3] = [Asset::FieldWriteLine, Asset::Validator, Asset::Writer]; #[derive(Serialize)] struct PacketContext { struct_name: String, fields: Vec, default_consts: Vec, } #[derive(Serialize)] struct CodeField { name: String, writer_call: String, check_call: String, is_string: bool, max_len_const: String, } #[derive(Serialize)] struct DefaultConst { const_name: String, literal: String, } /// The schema-kind to writer-fn mapping (ADR-004): the template renders /// whatever call name the builder decided and never interprets a schema /// kind itself. fn writer_call(schema_kind: &str) -> String { match schema_kind { "u32" => "write_u32".to_owned(), "u64" => "write_u64".to_owned(), "string" => "write_str".to_owned(), "bool" => "write_bool".to_owned(), other => panic!("fixture schema must only carry known kinds, found: {other}"), } } /// The schema-kind to check-fn mapping: the validator half of the same /// builder kind mapping, again decided in Rust (ADR-004). fn check_call(schema_kind: &str) -> String { match schema_kind { "u32" => "check_u32".to_owned(), "u64" => "check_u64".to_owned(), "string" => "check_str".to_owned(), "bool" => "check_bool".to_owned(), other => panic!("fixture schema must only carry known kinds, found: {other}"), } } /// The Rust-type-name mapping (snake_case document name to PascalCase /// type): another ADR-004 mapping living in builder code. fn rust_struct_name(doc_name: &str) -> String { doc_name .split('_') .map(|part| { let mut chars = part.chars(); match chars.next() { Some(first) => first.to_uppercase().collect::() + chars.as_str(), None => String::new(), } }) .collect() } /// Rust source-level string-literal quoting: the codegen posture /// escapes literals at the source (the builder), never by entity /// mangling, which is why the code set renders with the no-op escaper /// (ADR-005). fn rust_string_literal(raw: &str) -> String { let mut literal = String::new(); literal.push('"'); for c in raw.chars() { match c { '"' => literal.push_str("\\\""), '\\' => literal.push_str("\\\\"), _ => literal.push(c), } } literal.push('"'); literal } /// The ADR-004 context builder over the document-shaped fixture: fields /// stay in the document's array order (convention 5, the /// `preserve_order` load-bearing behavior), kind mappings and flags are /// decided here in Rust, string defaults are builder-quoted into consts, /// and the templates never see a schema kind, only dumb conditionals /// over builder-decided fields. fn build_packet_context(doc: &serde_json::Value) -> PacketContext { let struct_name = doc .get("name") .and_then(serde_json::Value::as_str) .expect("fixture schema should carry a struct name"); let fields_doc = doc .get("fields") .and_then(serde_json::Value::as_array) .expect("fixture schema should carry a fields array"); let mut fields = Vec::new(); let mut default_consts = Vec::new(); for field in fields_doc { let name = field .get("name") .and_then(serde_json::Value::as_str) .expect("fixture schema field should carry a name"); let kind = field .get("kind") .and_then(serde_json::Value::as_str) .expect("fixture schema field should carry a kind"); fields.push(CodeField { name: name.to_owned(), writer_call: writer_call(kind), check_call: check_call(kind), is_string: kind == "string", max_len_const: format!("{}_MAX", name.to_uppercase()), }); if let Some(default) = field.get("default").and_then(serde_json::Value::as_str) { default_consts.push(DefaultConst { const_name: format!("{}_DEFAULT", name.to_uppercase()), literal: rust_string_literal(default), }); } } PacketContext { struct_name: rust_struct_name(struct_name), fields, default_consts, } } fn register_asset(set: &mut TemplateSet, asset: Asset) { let result = match asset { Asset::Writer => set.register_template("writer", fixtures::WRITER), Asset::Validator => set.register_template("validator", fixtures::VALIDATOR), Asset::FieldWriteLine => { set.register_partial("field-write-line", fixtures::FIELD_WRITE_LINE) } }; result.expect("fixture asset should be well-formed"); } /// The code-shaped fixture set: two template kinds (`writer`, /// `validator`) over one composed partial, under the no-op escaper, /// registered in the caller's chosen order. fn code_proof_set(order: &[Asset]) -> TemplateSet { let mut set = TemplateSet::new(EscapePolicy::NoOp); for &asset in order { register_asset(&mut set, asset); } set } fn packet_context() -> PacketContext { let doc: serde_json::Value = serde_json::from_str(fixtures::SCHEMA).expect("fixture schema should parse as JSON"); build_packet_context(&doc) } /// Document field order (zeta, magic, alpha, length), which differs /// from both the sorted order (alpha, length, magic, zeta) and the /// reversed order (length, alpha, magic, zeta): the proof's field /// names are chosen so document order cannot be explained by sort or /// hash order. const GOLDEN_WRITER: &str = concat!( "pub const ZETA_DEFAULT: &str = \"v<1>&\\\"raw\\\"\";\n", "pub const LENGTH_DEFAULT: &str = \"a "b \";\n", "impl PacketHeader {\n", " pub fn write(&self, out: &mut W) -> std::io::Result<()> {\n", " out.write_str(&self.zeta)?;\n", " out.write_u64(&self.magic)?;\n", " out.write_bool(&self.alpha)?;\n", " out.write_str(&self.length)?;\n", " Ok(())\n", " }\n", "}\n", ); const GOLDEN_VALIDATOR: &str = concat!( "impl PacketHeader {\n", " pub fn validate(&self) -> Result<(), &'static str> {\n", " check_str_len(&self.zeta, ZETA_MAX)?;\n", " check_u64(&self.magic)?;\n", " check_bool(&self.alpha)?;\n", " check_str_len(&self.length, LENGTH_MAX)?;\n", " Ok(())\n", " }\n", "}\n", ); #[test] fn writer_and_validator_goldens_pin_mappings_literals_and_document_order() { let set = code_proof_set(&ORDER_A); let context = packet_context(); let writer = set .render("writer", &context) .expect("the writer should render through the public API"); assert_eq!(writer, GOLDEN_WRITER); let validator = set .render("validator", &context) .expect("the validator should render through the public API"); assert_eq!(validator, GOLDEN_VALIDATOR); } #[test] fn noop_escaping_passes_source_escaped_literals_verbatim() { let set = code_proof_set(&ORDER_A); let writer = set .render("writer", &packet_context()) .expect("the writer should render"); assert!( writer.contains(r#""v<1>&\"raw\"""#), "the builder-quoted literal must pass through the no-op escaper verbatim" ); assert!( writer.contains(r#""a "b ""#), "a literal already carrying escape-shaped source text must not be re-processed in either direction" ); assert!( !writer.contains("<"), "the HTML escaper would mangle the literal; the code set must never entity-escape" ); assert!( !writer.contains("&"), "entity-escaping must be absent from the code posture's output" ); } #[test] fn document_order_drives_rendered_order_not_sort_or_hash() { let set = code_proof_set(&ORDER_A); let context = packet_context(); let writer = set .render("writer", &context) .expect("the writer should render"); let validator = set .render("validator", &context) .expect("the validator should render"); for rendered in [&writer, &validator] { let zeta = rendered.find("self.zeta").expect("zeta line should render"); let magic = rendered .find("self.magic") .expect("magic line should render"); let alpha = rendered .find("self.alpha") .expect("alpha line should render"); let length = rendered .find("self.length") .expect("length line should render"); assert!(zeta < magic && magic < alpha && alpha < length); } assert!( writer.find("self.zeta").expect("zeta") < writer.find("self.alpha").expect("alpha"), "zeta before alpha already excludes the sorted order" ); assert!( writer.find("self.alpha").expect("alpha") < writer.find("self.length").expect("length"), "alpha before length excludes the reversed document order too" ); } #[test] fn reordered_document_arrays_render_in_new_array_order() { let set = code_proof_set(&ORDER_A); let mut doc: serde_json::Value = serde_json::from_str(fixtures::SCHEMA).expect("fixture schema should parse as JSON"); let fields = doc .get_mut("fields") .and_then(serde_json::Value::as_array_mut) .expect("fixture schema should carry a fields array"); fields.sort_by_key(|field| { field .get("name") .and_then(serde_json::Value::as_str) .map(str::to_owned) .unwrap_or_default() }); let writer = set .render("writer", &build_packet_context(&doc)) .expect("the reordered writer should render"); let alpha = writer.find("self.alpha").expect("alpha line should render"); let length = writer .find("self.length") .expect("length line should render"); let magic = writer.find("self.magic").expect("magic line should render"); let zeta = writer.find("self.zeta").expect("zeta line should render"); assert!( alpha < length && length < magic && magic < zeta, "the array's new order drives the render, not any stored canonical order" ); } #[test] fn repeated_renders_within_one_registry_are_byte_identical() { let set = code_proof_set(&ORDER_A); let context = packet_context(); let writer_first = set .render("writer", &context) .expect("the writer should render"); let validator_first = set .render("validator", &context) .expect("the validator should render"); for _ in 0..4 { let writer_again = set .render("writer", &context) .expect("repeated renders must succeed"); let validator_again = set .render("validator", &context) .expect("repeated renders must succeed"); assert_eq!( writer_again, writer_first, "repeated renders must be byte-identical (ADR-003)" ); assert_eq!( validator_again, validator_first, "repeated renders must be byte-identical (ADR-003)" ); } } #[test] fn registry_rebuilds_including_registration_order_are_byte_identical() { let context = packet_context(); let first = code_proof_set(&ORDER_A) .render("writer", &context) .expect("the writer should render from the first rebuild"); let validator_first = code_proof_set(&ORDER_A) .render("validator", &context) .expect("the validator should render from the first rebuild"); for order in [&ORDER_A, &ORDER_B] { let writer = code_proof_set(order) .render("writer", &context) .expect("the writer should render from the rebuilt set"); let validator = code_proof_set(order) .render("validator", &context) .expect("the validator should render from the rebuilt set"); assert_eq!( writer, first, "independent rebuilds must render byte-identical output (ADR-003, the posture-A regeneration posture)" ); assert_eq!( validator, validator_first, "independent rebuilds must render byte-identical output (ADR-003, the posture-A regeneration posture)" ); } let writer_from_reordered = code_proof_set(&ORDER_B) .render("writer", &packet_context()) .expect("the writer should render with a fresh context"); assert_eq!( writer_from_reordered, first, "fresh context builds do not perturb the output either" ); } #[test] fn both_sinks_agree_byte_for_byte_on_the_proof_set() { let set = code_proof_set(&ORDER_A); let context = packet_context(); for name in ["writer", "validator"] { let string_sink = set .render(name, &context) .unwrap_or_else(|_| panic!("{name} should render")); let mut byte_sink = Vec::new(); set.render_to_write(name, &context, &mut byte_sink) .unwrap_or_else(|_| panic!("{name} should render to the write sink")); assert_eq!( &byte_sink[..], string_sink.as_bytes(), "both sinks must agree byte for byte on {name} (ADR-003)" ); } }