Files
alkgen/tests/code_proof.rs
T
glm-5.3-flash 3b0483e50f test(code-proof): wave-2 code-shape proof through the public API (posture A/C)
- code-shaped fixture set: writer + validator template kinds over one
  composed partial, no-op escaper, strict mode, all public API
- ADR-004 mappings in builder Rust (kind->call names, struct name,
  string-kind flag, source-quoted literals); templates stay dumb
- document order (not sort/hash/reverse) drives rendered order;
  reordered arrays render in the array's new order
- determinism: byte-identical across repeated renders, registry
  rebuilds, differing registration orders, fresh contexts, and both
  sinks (render vs render_to_write)
- banner stays an emit-layer obligation; no banner assertions in the
  proof (gap recorded in task Notes, honestly)

Verification: cargo test (38 passed), cargo clippy --all-targets --
-D warnings, cargo fmt --check, taskgraph validate
2026-11-06 20:34:42 +00:00

423 lines
15 KiB
Rust

//! 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<CodeField>,
default_consts: Vec<DefaultConst>,
}
#[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::<String>() + 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 &quot;b <i>\";\n",
"impl PacketHeader {\n",
" pub fn write<W: std::io::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 &quot;b <i>""#),
"a literal already carrying escape-shaped source text must not be re-processed in either direction"
);
assert!(
!writer.contains("&lt;"),
"the HTML escaper would mangle the literal; the code set must never entity-escape"
);
assert!(
!writer.contains("&amp;"),
"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)"
);
}
}