From edcaa2a0817f6eca16e6fa7319075a2dfbc37382 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 21 Sep 2026 14:06:49 -0400 Subject: [PATCH] aac: read the conformance pack it publishes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `aac` ships vectors under `aac/conformance/v1/` that a third-party implementation is told to certify against, and the only code naming those files was `gen_vectors.rs` — the generator that writes them. Nothing read them back, so the published bytes could drift from the implementation with no test to notice. That is the drift that left joulehook and jouledesk with stale JCR-1 envelopes after the receipt payload gained a field. The published routing table drives `AacCascade::level_for_operation` rather than being re-read, so changing a routing rule fails here instead of being silently republished. The pack's own declared invariants are also checked against its own rows. Falsified before committing: breaking a real routing rule in `aac-core` (`AacOperation::CellSelect => AacCascadeLevel::SymbolLookup`) and making the pack internally inconsistent each fail it. Pack restored and verified pristine. Workspace green. Two limits worth stating. Breaking that routing rule **also** fails the in-crate suite, so this is not that rule's only coverage. And `aac-core` exports no ordered level list, so the suite and the generator each keep a private `const LEVELS` that can still drift apart — left for a maintainer, since fixing it means adding public API. This PR began as four packs and is now one. `map` was closed on main in a800ebe3, `proof` in a4caa2e, and `sense` in a8fda66 — all three while this sat open, and all three stronger than mine, so mine were dropped rather than merged alongside. The sense one is the clearest case: it identifies that `c1_replay_is_deterministic` was `assert_eq!(f(), f())`, a tautology comparing the published digest to nothing, which is a better diagnosis than my value-comparison. Co-Authored-By: Claude Opus 5 --- .../crates/aac-conformance/tests/pack.rs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 aac/aac-rs/crates/aac-conformance/tests/pack.rs diff --git a/aac/aac-rs/crates/aac-conformance/tests/pack.rs b/aac/aac-rs/crates/aac-conformance/tests/pack.rs new file mode 100644 index 000000000..903bf8e54 --- /dev/null +++ b/aac/aac-rs/crates/aac-conformance/tests/pack.rs @@ -0,0 +1,116 @@ +//! Read the published `aac/conformance/v1/cascade.json` and hold the +//! implementation to it. +//! +//! The in-crate suite (`aac_conformance::run`) checks the same properties, but +//! against a table written **in code**. The published pack — the bytes a third +//! party downloads — was written by `gen_vectors` and read back by nothing, so +//! the two could drift apart and only the code side would be checked. +//! +//! These tests invert that: the pack is the authority, and every assertion is +//! driven from its contents. Nothing here duplicates the level table; the +//! routing entries supply the operations and the level rows supply what each +//! level must cost and whether it may run offline. + +use std::path::PathBuf; + +use aac_core::{AacCascade, AacOperation}; +use serde_json::Value; + +fn pack() -> Value { + let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../../conformance/v1/cascade.json") + .canonicalize() + .expect("published pack must exist beside the spec"); + serde_json::from_slice(&std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))) + .expect("cascade.json parses") +} + +fn level_row<'a>(pk: &'a Value, tag: &str) -> &'a Value { + pk["levels"] + .as_array() + .expect("levels array") + .iter() + .find(|l| l["tag"] == tag) + .unwrap_or_else(|| panic!("pack names level {tag} in routing but has no row for it")) +} + +/// Every routed operation lands on the level the pack publishes, and that level +/// costs and behaves exactly as the pack says. +#[test] +fn routing_energy_and_offline_match_the_published_pack() { + let pk = pack(); + let routing = pk["routing"].as_array().expect("routing array"); + assert!( + routing.len() >= 10, + "expected at least 10 routing vectors, saw {} — a wrong pack path must fail loudly", + routing.len() + ); + + for entry in routing { + let op_name = entry["operation"].as_str().expect("operation"); + let tag = entry["level"].as_str().expect("level"); + + let op: AacOperation = serde_json::from_value(entry["operation"].clone()) + .unwrap_or_else(|e| panic!("pack names operation {op_name} the crate cannot parse: {e}")); + let got = AacCascade::level_for_operation(op); + let row = level_row(&pk, tag); + + assert_eq!( + got.label(), + row["name"].as_str().expect("level name"), + "{op_name}: routed to {got:?} but the pack routes it to {tag} ({})", + row["name"] + ); + assert_eq!( + got.estimated_energy_uj(), + row["energy_uj"].as_u64().expect("energy_uj"), + "{op_name} -> {tag}: energy disagrees with the published pack" + ); + assert_eq!( + got.is_offline(), + row["offline"].as_bool().expect("offline"), + "{op_name} -> {tag}: offline capability disagrees with the published pack" + ); + } +} + +/// The pack's own declared invariants must hold over the pack's own rows. +/// +/// A pack that states `energy_strictly_monotonic` while publishing two tiers at +/// the same cost is internally inconsistent, and no amount of checking the +/// implementation would catch it. +#[test] +fn the_pack_satisfies_the_invariants_it_declares() { + let pk = pack(); + let mut levels: Vec<&Value> = pk["levels"].as_array().expect("levels").iter().collect(); + levels.sort_by_key(|l| l["index"].as_u64().expect("index")); + + if pk["invariants"]["energy_strictly_monotonic"] == true { + let mut prev = 0u64; + for l in &levels { + let e = l["energy_uj"].as_u64().expect("energy_uj"); + assert!( + e > prev, + "level {} costs {e} µJ, not more than the previous {prev} µJ, but the pack declares energy strictly monotonic", + l["tag"] + ); + prev = e; + } + } + + let declared: Vec<&str> = pk["invariants"]["offline_levels"] + .as_array() + .expect("offline_levels") + .iter() + .map(|v| v.as_str().expect("tag")) + .collect(); + let actual: Vec<&str> = levels + .iter() + .filter(|l| l["offline"] == true) + .map(|l| l["tag"].as_str().expect("tag")) + .collect(); + assert_eq!( + actual, declared, + "the levels marked offline in the pack do not match the offline_levels it declares" + ); +} -- 2.54.0 (Apple Git-157)