//! C28 structural fitness: the node crates obey the ladder import direction. //! The compiler only rejects import cycles; an acyclic-but-outward dependency //! (e.g. `aura-std` -> `aura-market`) would compile silently. This test reads //! each node crate's `[dependencies]` table and asserts its intra-workspace edges //! stay within the inner set C28 permits for that layer. `[dev-dependencies]` are //! C28-exempt (tests may cross layers) and are deliberately not inspected. use std::path::PathBuf; fn workspace_root() -> PathBuf { // aura-vocabulary/ + /../.. = workspace root (the repo idiom for reaching it). PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..") } /// Every `crates/*` directory that has its own `Cargo.toml` — the actual workspace /// membership, read from disk rather than duplicated by hand, so the `allowed` table /// below can be checked for completeness instead of silently omitting a crate. fn workspace_crate_names() -> Vec { let crates_dir = workspace_root().join("crates"); let mut names: Vec = std::fs::read_dir(&crates_dir) .unwrap_or_else(|e| panic!("read {}: {e}", crates_dir.display())) .filter_map(|entry| { let entry = entry.expect("dir entry"); let path = entry.path(); if path.join("Cargo.toml").is_file() { Some(entry.file_name().into_string().expect("utf8 dir name")) } else { None } }) .collect(); names.sort(); names } /// The `aura-*` keys under `[dependencies]` (NOT `[dev-dependencies]`) of a crate. fn prod_intra_workspace_deps(crate_name: &str) -> Vec { let manifest = workspace_root() .join("crates") .join(crate_name) .join("Cargo.toml"); let text = std::fs::read_to_string(&manifest) .unwrap_or_else(|e| panic!("read {}: {e}", manifest.display())); let doc: toml::Value = toml::from_str(&text).expect("Cargo.toml parses as TOML"); match doc.get("dependencies").and_then(|d| d.as_table()) { Some(deps) => deps .keys() .filter(|k| k.starts_with("aura-")) .cloned() .collect(), None => Vec::new(), } } #[test] fn node_crates_obey_the_c28_import_direction() { // (crate, the intra-workspace [dependencies] C28 permits for its layer) let allowed: &[(&str, &[&str])] = &[ ("aura-core", &[]), // foundation: no aura-* deps ("aura-analysis", &[]), // foundation: no aura-* deps ("aura-measurement", &["aura-core", "aura-analysis"]), ("aura-std", &["aura-core"]), ("aura-market", &["aura-core"]), ("aura-strategy", &["aura-core"]), ("aura-engine", &["aura-core", "aura-analysis"]), ("aura-backtest", &["aura-core", "aura-engine"]), ( "aura-vocabulary", &["aura-core", "aura-std", "aura-market", "aura-strategy", "aura-backtest"], ), ( "aura-composites", &["aura-core", "aura-engine", "aura-std", "aura-strategy", "aura-backtest"], ), ("aura-ingest", &["aura-core", "aura-engine"]), ("aura-research", &["aura-core"]), ( "aura-registry", &["aura-core", "aura-engine", "aura-backtest", "aura-analysis", "aura-research"], ), ( "aura-campaign", &[ "aura-core", "aura-engine", "aura-backtest", "aura-analysis", "aura-registry", "aura-research", ], ), ( "aura-runner", &[ "aura-core", "aura-engine", "aura-std", "aura-strategy", "aura-backtest", "aura-composites", "aura-ingest", "aura-registry", "aura-research", "aura-campaign", "aura-vocabulary", ], ), ("aura-bench", &["aura-core", "aura-engine", "aura-std", "aura-ingest"]), ( "aura-cli", &[ "aura-core", "aura-engine", "aura-std", "aura-strategy", "aura-backtest", "aura-registry", "aura-research", "aura-campaign", "aura-vocabulary", "aura-ingest", "aura-measurement", "aura-runner", ], ), ]; let mut table_names: Vec<&str> = allowed.iter().map(|(name, _)| *name).collect(); table_names.sort(); let disk_names = workspace_crate_names(); assert_eq!( table_names, disk_names.iter().map(String::as_str).collect::>(), "C28 full-workspace: `allowed` must enumerate exactly the crates under `crates/` \ (add the new crate's permitted set here, even a foundation crate with no aura-* \ deps) — otherwise it escapes the C28 guard silently" ); for (crate_name, permitted) in allowed { for dep in prod_intra_workspace_deps(crate_name) { assert!( permitted.contains(&dep.as_str()), "C28 import-direction violation: `{crate_name}` [dependencies] on `{dep}`, \ outside its permitted inner set {permitted:?}" ); } } for (crate_name, permitted) in allowed { for shell_or_assembly in ["aura-cli", "aura-runner"] { let exempt = *crate_name == "aura-cli" && shell_or_assembly == "aura-runner"; assert!( exempt || !permitted.contains(&shell_or_assembly), "C28 assembly violation: `{crate_name}`'s permitted set names \ `{shell_or_assembly}`, but the shell/assembly crates are imported \ by nothing except the shell itself" ); } } } #[test] fn the_shell_defines_no_domain_modules_and_no_lib_target() { let root = workspace_root(); let manifest_text = std::fs::read_to_string(root.join("crates/aura-cli/Cargo.toml")).unwrap(); let manifest: toml::Value = toml::from_str(&manifest_text).expect("Cargo.toml parses as TOML"); assert!( manifest.get("lib").is_none(), "C28: the shell exports nothing — aura-cli must stay a pure binary crate" ); let mut modules: Vec = std::fs::read_dir(root.join("crates/aura-cli/src")) .unwrap() .map(|e| e.unwrap().file_name().into_string().unwrap()) .collect(); modules.sort(); assert_eq!( modules, [ "campaign_run.rs", "graph_construct.rs", "main.rs", "render.rs", "research_docs.rs", "scaffold.rs", "verb_sugar.rs", ], "C28 (#295): the shell holds argv/dispatch, argv->document translation, \ and presentation only — a new module needs a library home" ); }