//! Independent B4 native source fixture/oracle. No desktop renderer is linked. //! generate MODE DIR | check MODE ENTRY [PRE_UNDO_ENTRY] | same EXPECTED ACTUAL [PRE_UNDO_ENTRY] //! self-check | inventory; modes: baseline, oa-rename, sa-rename. All fixture identities are fixed. use oa_core::{source_project::*, *}; use std::{collections::BTreeMap, fs, path::Path}; type Disk = BTreeMap>; fn id(n: u128) -> Id { Id::from_u128(0x5ceab400000040008000000000000000 + n) } #[derive(Clone, Copy)] struct Side { layer: Layer, word: &'static str, base: u128, } const SIDES: [Side; 2] = [ Side { layer: Layer::Oa, word: "OA", base: 1000, }, Side { layer: Layer::Sa, word: "SA", base: 2000, }, ]; impl Side { fn id(self, n: u128) -> Id { id(self.base + n) } fn name(self, text: &str) -> String { format!("{} {text}", self.word) } } fn element(p: &mut Project, side: Side, n: u128, kind: Kind, name: &str, parent: Option) { let mut e = p .make_element( kind, side.layer, &side.name(name), parent.map(|n| side.id(n)), ) .unwrap(); e.id = side.id(n); e.alias = format!("life_{}_{}", side.word.to_lowercase(), n); p.elements.insert(e.id, e); } fn scenario(p: &mut Project, side: Side, n: u128, name: &str) { element(p, side, n, Kind::Scenario, name, None); let Some(SharedBody::Scenario { viewpoint, .. }) = p.elements.get_mut(&side.id(n)).unwrap().shared_mut() else { unreachable!() }; *viewpoint = if side.layer == Layer::Oa { ScenarioViewpoint::OperationalActivity } else { ScenarioViewpoint::Functional }; } fn line(p: &mut Project, side: Side, n: u128, owner: u128, work: Option, name: &str) { element(p, side, n, Kind::Lifeline, name, Some(owner)); *p.elements .get_mut(&side.id(n)) .unwrap() .shared_mut() .unwrap() = SharedBody::Lifeline { represented: work.map(|n| side.id(n)), }; } fn occurrence( p: &mut Project, side: Side, n: u128, parent: u128, name: &str, body: OccurrenceBody, ) { element(p, side, n, Kind::Occurrence, name, Some(parent)); *p.elements .get_mut(&side.id(n)) .unwrap() .shared_mut() .unwrap() = SharedBody::Occurrence { body, precondition: String::new(), postcondition: String::new(), }; } fn steps(p: &mut Project, side: Side, owner: u128, ordered: &[u128]) { *p.elements .get_mut(&side.id(owner)) .unwrap() .steps_mut() .unwrap() = ordered.iter().map(|n| side.id(*n)).collect(); } fn message( side: Side, sender: u128, receiver: u128, kind: MessageKind, resolved: bool, ) -> OccurrenceBody { OccurrenceBody::Exchange { exchange: resolved.then(|| side.id(12)), sender: Some(side.id(sender)), receiver: Some(side.id(receiver)), message_kind: kind, operation: None, reply_to: None, values: vec![], } } fn fixture() -> Project { let mut p = Project::empty(); p.id = id(1); p.alias = "lifecycle_native".into(); p.name = "Lifecycle native contract".into(); p.description = "Fixed semantic inputs; lifecycle geometry is derived and never authored.".into(); for side in SIDES { let work = if side.layer == Layer::Oa { Kind::Activity } else { Kind::Function }; element(&mut p, side, 10, work, "Controller work", None); element(&mut p, side, 11, work, "Receiver work", None); if side.layer == Layer::Oa { let relation = Relation { id: side.id(12), alias: "life_oa_service".into(), name: "OA Lifecycle service".into(), description: String::new(), body: RelationBody::new(RelationKind::Interaction, side.id(10), side.id(11)), }; p.relations.insert(relation.id, relation); } else { // The port planner derives deterministic UUIDv5 endpoints from this fixed exchange ID. let commands = ports::plan_functional_exchange( &p, &ports::FunctionalExchangeRequest { id: side.id(12), alias: "life_sa_service".into(), name: "SA Lifecycle service".into(), description: String::new(), source: side.id(10), target: side.id(11), source_port: ports::PortChoice::Create, target_port: ports::PortChoice::Create, items: vec![], }, ) .expect("typed fixed-ID functional exchange must be admitted"); p = execute_batch(&p, &commands).unwrap(); } for (n, name) in [ (100, "01 Root create action delete"), (200, "02 Preexisting empty"), (300, "03 Create only"), (400, "04 Conditional delete"), (500, "05 Unassigned receiver"), (600, "06 Self delete"), ] { scenario(&mut p, side, n, name); line(&mut p, side, n + 1, n, Some(10), "Controller lane"); if n != 600 { line( &mut p, side, n + 2, n, (n != 500).then_some(11), if n == 500 { "Unassigned receiver lane" } else { "Receiver lane" }, ); } } occurrence( &mut p, side, 110, 100, "Create receiver", message(side, 101, 102, MessageKind::Create, true), ); occurrence( &mut p, side, 111, 100, "Receiver action", OccurrenceBody::Action { action: Some(side.id(11)), lifeline: Some(side.id(102)), }, ); occurrence( &mut p, side, 112, 100, "Delete receiver", message(side, 101, 102, MessageKind::Delete, true), ); steps(&mut p, side, 100, &[110, 111, 112]); occurrence( &mut p, side, 310, 300, "Create only receiver", message(side, 301, 302, MessageKind::Create, true), ); steps(&mut p, side, 300, &[310]); occurrence( &mut p, side, 410, 400, "Open call", message(side, 401, 402, MessageKind::Call, true), ); occurrence( &mut p, side, 411, 400, "ALT delete or keep", OccurrenceBody::Fragment { operator: FragmentOperator::Alternative, branches: vec![side.id(412), side.id(413)], }, ); occurrence( &mut p, side, 412, 411, "Left delete operand", OccurrenceBody::Branch { guard: "stop_requested".into(), occurrences: vec![side.id(414)], }, ); occurrence( &mut p, side, 413, 411, "Right empty operand", OccurrenceBody::Branch { guard: "else".into(), occurrences: vec![], }, ); occurrence( &mut p, side, 414, 412, "Conditional delete receiver", message(side, 401, 402, MessageKind::Delete, true), ); steps(&mut p, side, 400, &[410, 411]); occurrence( &mut p, side, 510, 500, "Create unassigned receiver", message(side, 501, 502, MessageKind::Create, false), ); occurrence( &mut p, side, 511, 500, "Delete unassigned receiver", message(side, 501, 502, MessageKind::Delete, false), ); steps(&mut p, side, 500, &[510, 511]); occurrence( &mut p, side, 610, 600, "Self delete controller", message(side, 601, 601, MessageKind::Delete, false), ); steps(&mut p, side, 600, &[610]); } execute_batch(&p, &[]).expect("complete source fixture must satisfy ordinary core admission; never bypass self/incomplete-lane rejection") } fn baseline() -> CompiledSourceProject { let p = fixture(); let entry = language::format(&p).replacen("{\n", "{\n include \"rationale.arcadia\";\n", 1); let raw = SourceBundle { entry: "project.arcadia".into(), files: BTreeMap::from([ ("project.arcadia".into(), entry), ("rationale.arcadia".into(), "// Preserve lifecycle note: 雨 · receiver.\r\n// Geometry never changes these bytes.\r\n".into()), ]) }; let compiled = compile_bundle(&raw, ProjectLimits::default()).unwrap(); assert_eq!(compiled.decoded.project, p); assert_eq!( compiled.bundle, raw, "no identity insertion, normalization or migration" ); compiled } fn expected(mode: &str) -> CompiledSourceProject { let base = baseline(); let side = match mode { "baseline" => return base, "oa-rename" => SIDES[0], "sa-rename" => SIDES[1], _ => panic!("mode: baseline|oa-rename|sa-rename"), }; let next = execute_batch( &base.decoded.project, &[Command::Rename( side.id(112), side.name("Delete receiver · reviewed"), )], ) .unwrap(); update_bundle(&base, &next, ProjectLimits::default()).unwrap() } fn disk(root: &Path, here: &Path, out: &mut Disk) { for entry in fs::read_dir(here).unwrap() { let entry = entry.unwrap(); let kind = entry.file_type().unwrap(); assert!(!kind.is_symlink(), "checkpoint cannot contain symlinks"); if kind.is_dir() { disk(root, &entry.path(), out); } else { assert!(kind.is_file(), "checkpoint files must be ordinary files"); let path = entry.path(); let name = path .strip_prefix(root) .unwrap() .to_str() .unwrap() .replace('\\', "/"); out.insert(name, fs::read(path).unwrap()); } } } struct Snapshot { compiled: CompiledSourceProject, disk: Disk, } fn load(entry: &Path) -> Snapshot { assert_eq!(entry.file_name().unwrap(), "project.arcadia"); let raw = source_files::load_project(entry, ProjectLimits::default()) .unwrap() .reachable_bundle(); let compiled = compile_bundle(&raw, ProjectLimits::default()).unwrap(); assert_eq!(compiled.bundle, raw, "no implicit source normalization"); execute_batch(&compiled.decoded.project, &[]).unwrap(); let mut files = Disk::new(); let root = entry.parent().unwrap(); disk(root, root, &mut files); Snapshot { compiled, disk: files, } } fn source_disk(bundle: &SourceBundle) -> Disk { bundle .files .iter() .map(|(name, text)| (name.clone(), text.as_bytes().to_vec())) .collect() } fn expected_disk(bundle: &SourceBundle, origin: Option<&Disk>) -> Disk { let mut files = origin.cloned().unwrap_or_default(); files.extend(source_disk(bundle)); files } fn equal(expected: &CompiledSourceProject, actual: &Snapshot, origin: Option<&Disk>) { assert_eq!( actual.compiled.decoded.project, expected.decoded.project, "whole Project: every identity, field, order, reference and geometry" ); assert_eq!( actual.compiled.bundle, expected.bundle, "whole reachable SourceBundle including retained note bytes" ); assert_eq!( actual.disk, expected_disk(&expected.bundle, origin), "whole physical file map: only exact pre-Undo retained files are permitted" ); } fn self_check() { let base = baseline(); assert_eq!( base.decoded.project, fixture(), "fixture deterministic, including typed ports" ); assert_eq!(base.bundle, baseline().bundle); for mode in ["baseline", "oa-rename", "sa-rename"] { let prediction = expected(mode); let actual = Snapshot { disk: source_disk(&prediction.bundle), compiled: expected(mode), }; equal(&prediction, &actual, None); if mode != "baseline" { assert_ne!(prediction.decoded.project, base.decoded.project); assert_ne!(prediction.bundle, base.bundle); let side = if mode == "oa-rename" { SIDES[0] } else { SIDES[1] }; let mut independently_named = base.decoded.project.clone(); independently_named .elements .get_mut(&side.id(112)) .unwrap() .name = side.name("Delete receiver · reviewed"); assert_eq!( prediction.decoded.project, independently_named, "rename changes precisely one name; alias and identity stay fixed" ); } let mut changed_project = prediction.decoded.project.clone(); changed_project.description.push_str("unrequested"); assert_ne!(changed_project, prediction.decoded.project); let mut changed_source = prediction.bundle.clone(); changed_source .files .get_mut("rationale.arcadia") .unwrap() .push_str("// unrequested\n"); assert_ne!(changed_source, prediction.bundle); } let plain = source_disk(&base.bundle); let mut origin = plain.clone(); origin.insert( "detached.arcadia".into(), b"// exact pre-Undo bytes\r\n".to_vec(), ); let retained = expected_disk(&base.bundle, Some(&origin)); assert_eq!(retained, origin); assert_ne!( retained, expected_disk(&base.bundle, None), "origin is mandatory to authorize retained files" ); let mut missing = retained.clone(); missing.remove("detached.arcadia"); assert_ne!(missing, retained); let mut changed = retained.clone(); changed.get_mut("detached.arcadia").unwrap().push(b'!'); assert_ne!(changed, retained); let mut unknown = retained.clone(); unknown.insert("unknown.arcadia".into(), vec![]); assert_ne!(unknown, retained); let mut wrong_reachable = origin.clone(); wrong_reachable.insert("rationale.arcadia".into(), b"wrong prior contents".to_vec()); assert_eq!( expected_disk(&base.bundle, Some(&wrong_reachable)), retained, "expected reachable bytes override the origin" ); println!( "PASS self-check: fixed OA/SA fixture, exact name-only predictions, explicit retained-file origin policy" ); } fn main() { assert_eq!(SCHEMA_VERSION, 7); let args: Vec<_> = std::env::args().collect(); match args.get(1).map(String::as_str) { Some("self-check") => { assert_eq!(args.len(), 2); self_check(); } Some("inventory") => { assert_eq!(args.len(), 2); let base = baseline(); for e in base.decoded.project.elements.values().filter(|e| { matches!(e.kind(), Kind::Scenario | Kind::Lifeline | Kind::Occurrence) }) { println!( "{} | {:?} | {} | parent={:?}", e.id, e.kind(), e.name, e.parent ); } for issue in validation::validate(&base.decoded.project) { println!("DIAGNOSTIC {issue:?}"); } } Some("generate") => { assert_eq!(args.len(), 4, "generate MODE FRESH_DIR"); let prediction = expected(&args[2]); let dir = Path::new(&args[3]); assert!(!dir.exists(), "preserve existing fixture/evidence"); fs::create_dir_all(dir).unwrap(); for (name, text) in &prediction.bundle.files { let path = dir.join(name); fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path, text.as_bytes()).unwrap(); } equal(&prediction, &load(&dir.join("project.arcadia")), None); println!( "PASS generated {}: {} fixed elements, {} relations, {} exact sources", args[2], prediction.decoded.project.elements.len(), prediction.decoded.project.relations.len(), prediction.bundle.files.len() ); } Some("check") => { assert!( (4..=5).contains(&args.len()), "check MODE ENTRY [PRE_UNDO_ENTRY]" ); let actual = load(Path::new(&args[3])); let origin = args.get(4).map(|path| load(Path::new(path))); equal( &expected(&args[2]), &actual, origin.as_ref().map(|s| &s.disk), ); println!( "PASS {}: complete Project/reachable sources/physical map", args[2] ); } Some("same") => { assert!( (4..=5).contains(&args.len()), "same VERIFIED_EXPECTED_ENTRY ACTUAL_ENTRY [PRE_UNDO_ENTRY]" ); let wanted = load(Path::new(&args[2])); let actual = load(Path::new(&args[3])); let origin = args.get(4).map(|path| load(Path::new(path))); assert_eq!( actual.compiled.decoded.project, wanted.compiled.decoded.project ); assert_eq!(actual.compiled.bundle, wanted.compiled.bundle); let files = origin.as_ref().map_or_else( || wanted.disk.clone(), |s| expected_disk(&wanted.compiled.bundle, Some(&s.disk)), ); assert_eq!( actual.disk, files, "exact prior checkpoint or explicit retained-file origin" ); println!("PASS same: all identities, fields, reachable source and physical bytes"); } _ => panic!( "self-check | inventory | generate MODE DIR | check MODE ENTRY [PRE_UNDO_ENTRY] | same EXPECTED ACTUAL [PRE_UNDO_ENTRY]" ), } }