//! Consume the published `aac` conformance pack. //! //! `gen_vectors` writes `aac/conformance/v1/cascade.json` and nothing read it back, //! so the published cascade — the table a third-party implementation certifies //! against — was never checked against the engine that produces it. //! //! The pack also **declares its own invariants**, and that block is written as //! literals by the generator rather than derived from the levels beside it: //! //! ```text //! "invariants": { "energy_strictly_monotonic": true, "offline_levels": ["L0","L1","L2"] } //! ``` //! //! So the pack asserts those properties; nothing made them true. If `is_offline()` //! moved a level, the generator would emit the new `levels` array next to the old //! claim and the file would contradict itself. These tests check the declared //! invariants against the published data, which is what turns the claim into a fact. use aac_core::{AacCascade, AacCascadeLevel, AacOperation}; use serde_json::Value; use std::path::PathBuf; /// The level order and tags the generator publishes. const LEVELS: [(AacCascadeLevel, &str); 10] = [ (AacCascadeLevel::CacheHit, "L0"), (AacCascadeLevel::SymbolLookup, "L1"), (AacCascadeLevel::LocalPredict, "L2"), (AacCascadeLevel::ContextPredict, "L3"), (AacCascadeLevel::SemanticExpand, "L4"), (AacCascadeLevel::ConversationAi, "L5"), (AacCascadeLevel::GenerativeComm, "L6"), (AacCascadeLevel::VoiceClone, "L7"), (AacCascadeLevel::MultimodalGen, "L8"), (AacCascadeLevel::AdaptiveModel, "L9"), ]; const OPS: [(AacOperation, &str); 10] = [ (AacOperation::CellSelect, "CellSelect"), (AacOperation::SymbolSearch, "SymbolSearch"), (AacOperation::WordPrediction, "WordPrediction"), (AacOperation::SocialScript, "SocialScript"), (AacOperation::SentenceConstruction, "SentenceConstruction"), (AacOperation::ConversationRepair, "ConversationRepair"), (AacOperation::ComposeLong, "ComposeLong"), (AacOperation::VoiceBanking, "VoiceBanking"), (AacOperation::SymbolGeneration, "SymbolGeneration"), (AacOperation::ModelRetrain, "ModelRetrain"), ]; fn pack() -> Value { let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../../conformance/v1/cascade.json") .canonicalize() .expect("the conformance pack must exist next to the spec"); serde_json::from_slice(&std::fs::read(&p).expect("read cascade.json")) .expect("cascade.json must parse") } fn levels(doc: &Value) -> &Vec { doc["levels"].as_array().expect("the pack publishes a level table") } /// The engine still produces exactly the published cascade. /// /// Rebuilt in process rather than by running the generator: `gen_vectors` writes to /// a hardcoded relative path with no output argument, so running it to diff would /// overwrite the published file. #[test] fn the_engine_still_produces_the_published_cascade() { let doc = pack(); assert_eq!(doc["standard"], "aac"); assert_eq!(doc["vector"], "cascade"); let rebuilt_levels: Vec = LEVELS .iter() .enumerate() .map(|(i, (lvl, tag))| { serde_json::json!({ "index": i, "tag": tag, "name": lvl.label(), "energy_uj": lvl.estimated_energy_uj(), "offline": lvl.is_offline(), }) }) .collect(); assert_eq!(&rebuilt_levels, levels(&doc), "the level table has drifted from the engine"); let rebuilt_routes: Vec = OPS .iter() .map(|(op, name)| { let lvl = AacCascade::level_for_operation(*op); let tag = LEVELS.iter().find(|(l, _)| l == &lvl).map(|(_, t)| *t).expect("known level"); serde_json::json!({ "operation": name, "level": tag }) }) .collect(); assert_eq!(&rebuilt_routes, doc["routing"].as_array().unwrap(), "routing has drifted"); } /// The invariants the pack declares, checked against the data it ships beside them. /// /// Both are literals in the generator. Until something compares them to the levels, /// a pack that contradicts itself would publish cleanly. #[test] fn the_declared_invariants_hold_over_the_published_levels() { let doc = pack(); let inv = &doc["invariants"]; let levels = levels(&doc); assert_eq!(levels.len(), 10, "a truncated table must not look like a pass"); // Indices and tags are consistent and contiguous. for (i, l) in levels.iter().enumerate() { assert_eq!(l["index"].as_u64().unwrap() as usize, i, "level {i} index disagrees"); assert_eq!(l["tag"].as_str().unwrap(), format!("L{i}"), "level {i} tag disagrees"); } if inv["energy_strictly_monotonic"].as_bool().expect("declared monotonicity") { for w in levels.windows(2) { let (a, b) = (w[0]["energy_uj"].as_u64().unwrap(), w[1]["energy_uj"].as_u64().unwrap()); assert!( b > a, "the pack declares strictly monotonic energy but {} ({a} uJ) is not below {} ({b} uJ)", w[0]["tag"], w[1]["tag"] ); } } let declared: Vec<&str> = inv["offline_levels"] .as_array() .expect("declared offline levels") .iter() .map(|v| v.as_str().unwrap()) .collect(); let actual: Vec<&str> = levels .iter() .filter(|l| l["offline"].as_bool().unwrap()) .map(|l| l["tag"].as_str().unwrap()) .collect(); assert_eq!( actual, declared, "the levels marked offline do not match the offline_levels the pack declares" ); } /// The accessibility property the cascade exists to protect: the operations a person /// needs to say something at all must route to a level that works offline. /// /// Selecting a cell, searching for a symbol and predicting a word are how an AAC user /// forms basic utterances. If any of those routed above the offline boundary, losing /// connectivity would cost someone their voice, not merely a feature. #[test] fn basic_communication_routes_below_the_offline_boundary() { let doc = pack(); let offline: Vec<&str> = levels(&doc) .iter() .filter(|l| l["offline"].as_bool().unwrap()) .map(|l| l["tag"].as_str().unwrap()) .collect(); let route_of = |op: &str| -> String { doc["routing"] .as_array() .unwrap() .iter() .find(|r| r["operation"] == op) .unwrap_or_else(|| panic!("the pack routes {op}"))["level"] .as_str() .unwrap() .to_string() }; for op in ["CellSelect", "SymbolSearch", "WordPrediction"] { let lvl = route_of(op); assert!( offline.contains(&lvl.as_str()), "{op} routes to {lvl}, which the pack marks online — basic communication \ must not depend on connectivity" ); } // And the converse, so the offline set is not trivially everything: the expensive // generative operations are online, which is why they are allowed to cost more. for op in ["ComposeLong", "VoiceBanking", "ModelRetrain"] { let lvl = route_of(op); assert!(!offline.contains(&lvl.as_str()), "{op} unexpectedly routes offline ({lvl})"); } } /// Every published operation routes to a level the pack also publishes, and every /// level is reachable — a routing table naming a level that does not exist, or a /// level nothing can reach, is a table nobody can implement against. #[test] fn routing_and_levels_are_mutually_complete() { let doc = pack(); let tags: Vec<&str> = levels(&doc).iter().map(|l| l["tag"].as_str().unwrap()).collect(); let routes = doc["routing"].as_array().expect("the pack publishes routing"); assert_eq!(routes.len(), OPS.len(), "routing must cover every operation"); for r in routes { let lvl = r["level"].as_str().unwrap(); assert!(tags.contains(&lvl), "{} routes to unknown level {lvl}", r["operation"]); } for tag in &tags { assert!( routes.iter().any(|r| r["level"] == *tag), "level {tag} is published but no operation reaches it" ); } }