#!/usr/bin/env python3 """Hermetic tests for the FEAT-015 command migration manifest gate. Covers the Deep-Dive schema and frontier rules: - valid root frontier; valid parent-to-all-children replacement; valid removal - rejected partial split; rejected arbitrary addition - duplicate/unsorted entry; unknown schema/tag/field; unsupported syntax - missing/overlapping selector; const normalization - integer/character/byte/path const atoms; out-of-range byte diagnostics """ from __future__ import annotations import importlib.util import json import sys import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "check-command-migration-manifest.py" SPEC = importlib.util.spec_from_file_location("command_migration", SCRIPT) assert SPEC and SPEC.loader mod = importlib.util.module_from_spec(SPEC) sys.modules[SPEC.name] = mod SPEC.loader.exec_module(mod) def sample_topology() -> dict: """A minimal two-group topology with one documented slice per group.""" return { "schema_version": 1, "topology": { "utility": { "kind": "group", "scope": ["crates/tui/src/commands/groups/utility/mod.rs"], "slices": [], }, "session": { "kind": "group", "scope": ["crates/tui/src/commands/groups/session/mod.rs"], "slices": [ { "name": "session::lifecycle", "kind": "slice", "scope": ["crates/tui/src/commands/groups/session/branch.rs"], }, { "name": "session::control", "kind": "slice", "scope": ["crates/tui/src/commands/groups/session/relay.rs"], }, ], }, }, "frontier": ["session", "utility"], } class SchemaTests(unittest.TestCase): def test_valid_document_passes(self) -> None: self.assertEqual(mod.validate_topology_document(sample_topology()), []) def test_unknown_schema_version_fails(self) -> None: doc = sample_topology() doc["schema_version"] = 2 violations = mod.validate_topology_document(doc) self.assertEqual(len(violations), 1) self.assertIn("schema_version", str(violations[0])) def test_missing_schema_version_fails(self) -> None: doc = sample_topology() del doc["schema_version"] self.assertTrue(mod.validate_topology_document(doc)) def test_unknown_group_field_fails(self) -> None: doc = sample_topology() doc["topology"]["utility"]["extra"] = True violations = mod.validate_topology_document(doc) self.assertTrue(any("extra" in str(v) for v in violations)) def test_slice_without_group_prefix_fails(self) -> None: doc = sample_topology() doc["topology"]["session"]["slices"][0]["name"] = "other::slice" violations = mod.validate_topology_document(doc) self.assertTrue(any("must start with" in str(v) for v in violations)) def test_duplicate_slice_name_fails(self) -> None: doc = sample_topology() doc["topology"]["session"]["slices"].append( doc["topology"]["session"]["slices"][0] ) violations = mod.validate_topology_document(doc) self.assertTrue(any("duplicate slice" in str(v) for v in violations)) class FrontierTests(unittest.TestCase): def test_valid_root_frontier_passes(self) -> None: doc = sample_topology() self.assertEqual(mod.validate_frontier(doc["topology"], doc["frontier"]), []) def test_unsorted_frontier_fails(self) -> None: doc = sample_topology() violations = mod.validate_frontier(doc["topology"], ["utility", "session"]) self.assertTrue(any("sorted" in str(v) for v in violations)) def test_duplicate_frontier_entry_fails(self) -> None: doc = sample_topology() violations = mod.validate_frontier(doc["topology"], ["session", "session"]) self.assertTrue(any("duplicates" in str(v) for v in violations)) def test_unknown_frontier_leaf_fails(self) -> None: doc = sample_topology() violations = mod.validate_frontier(doc["topology"], ["session", "ghost"]) self.assertTrue(any("not a declared topology leaf" in str(v) for v in violations)) def test_removal_is_valid_transition(self) -> None: doc = sample_topology() old = ["session", "utility"] new = ["utility"] self.assertEqual(mod.is_valid_frontier_transition(doc["topology"], old, new), []) def test_parent_to_all_children_is_valid_transition(self) -> None: doc = sample_topology() old = ["session", "utility"] new = ["session::control", "session::lifecycle", "utility"] self.assertEqual(mod.is_valid_frontier_transition(doc["topology"], old, new), []) def test_partial_split_is_rejected(self) -> None: doc = sample_topology() old = ["session", "utility"] new = ["session::lifecycle", "utility"] violations = mod.is_valid_frontier_transition(doc["topology"], old, new) self.assertTrue(violations, "partial split must be rejected") def test_arbitrary_addition_is_rejected(self) -> None: doc = sample_topology() old = ["session", "utility"] new = ["ghost", "session", "utility"] violations = mod.is_valid_frontier_transition(doc["topology"], old, new) self.assertTrue(violations, "arbitrary growth must be rejected") class LiveTransitionTests(unittest.TestCase): def test_invalid_current_manifest_fails_closed_before_transition(self) -> None: violations = mod.validate_baseline_transition({}, None) self.assertTrue(any("current topology is invalid" in str(v) for v in violations)) def test_initial_manifest_must_start_at_all_roots(self) -> None: doc = sample_topology() self.assertEqual(mod.validate_baseline_transition(doc, None), []) doc["frontier"] = ["utility"] violations = mod.validate_baseline_transition(doc, None) self.assertTrue(any("first manifest revision" in str(v) for v in violations)) def test_live_transition_rejects_topology_mutation(self) -> None: previous = sample_topology() current = json.loads(json.dumps(previous)) current["topology"]["utility"]["scope"].append("new.rs") violations = mod.validate_baseline_transition(current, previous) self.assertTrue(any("topology is immutable" in str(v) for v in violations)) def test_live_transition_accepts_documented_split(self) -> None: previous = sample_topology() current = json.loads(json.dumps(previous)) current["frontier"] = ["session::control", "session::lifecycle", "utility"] self.assertEqual(mod.validate_baseline_transition(current, previous), []) def test_live_transition_rejects_arbitrary_growth(self) -> None: previous = sample_topology() previous["frontier"] = ["utility"] current = json.loads(json.dumps(previous)) current["frontier"] = ["session", "utility"] violations = mod.validate_baseline_transition(current, previous) self.assertTrue(any("arbitrary growth" in str(v) for v in violations)) def test_pending_projection_must_equal_json_frontier(self) -> None: doc = sample_topology() self.assertEqual( mod.validate_pending_projection(doc, ["session", "utility"]), [] ) violations = mod.validate_pending_projection(doc, ["utility"]) self.assertTrue(any("does not equal JSON frontier" in str(v) for v in violations)) def test_pending_groups_parser_is_string_only_and_order_preserving(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "contract.rs" path.write_text( 'pub(crate) const PENDING_GROUPS: &[&str] = &["session", "utility"];\n', encoding="utf-8", ) self.assertEqual(mod.load_pending_groups(path), ["session", "utility"]) class SelectorTests(unittest.TestCase): def test_free_selector_passes(self) -> None: selector = {"kind": "free", "item": ["crate", "commands", "groups", "session", "run_save"]} self.assertEqual(mod.validate_selector(selector, "s"), []) def test_inherent_selector_passes(self) -> None: selector = { "kind": "inherent", "self_type": { "tag": "path", "absolute": True, "segments": [ {"name": "crate"}, {"name": "commands", "args": []}, {"name": "groups", "args": []}, {"name": "session", "args": []}, {"name": "branch", "args": []}, {"name": "BranchCmd", "args": []}, ], }, "method": "execute", } self.assertEqual(mod.validate_selector(selector, "s"), []) def test_trait_impl_selector_passes(self) -> None: selector = { "kind": "trait_impl", "self_type": {"tag": "path", "absolute": True, "segments": [{"name": "BranchCmd"}]}, "trait_path": {"tag": "path", "absolute": True, "segments": [{"name": "RegisterCommand"}]}, "method": "execute", } self.assertEqual(mod.validate_selector(selector, "s"), []) def test_unknown_selector_kind_fails(self) -> None: selector = {"kind": "static", "item": ["a", "b"]} violations = mod.validate_selector(selector, "s") self.assertTrue(any("unknown selector kind" in str(v) for v in violations)) def test_free_selector_missing_function_fails(self) -> None: selector = {"kind": "free", "item": ["crate"]} violations = mod.validate_selector(selector, "s") self.assertTrue(any("module path array" in str(v) for v in violations)) def test_inherent_selector_missing_method_fails(self) -> None: selector = {"kind": "inherent", "self_type": {"tag": "never"}} violations = mod.validate_selector(selector, "s") self.assertTrue(any("inherent.method" in str(v) for v in violations)) def test_unknown_self_type_tag_fails(self) -> None: selector = {"kind": "inherent", "self_type": {"tag": "fn_ptr"}, "method": "execute"} violations = mod.validate_selector(selector, "s") self.assertTrue(any("unknown type node tag" in str(v) for v in violations)) class TypeAlgebraTests(unittest.TestCase): def test_primitive_and_never_pass(self) -> None: self.assertEqual(mod.validate_type_node({"tag": "primitive", "name": "u8"}, "t"), []) self.assertEqual(mod.validate_type_node({"tag": "never"}, "t"), []) def test_unknown_primitive_fails(self) -> None: violations = mod.validate_type_node({"tag": "primitive", "name": "u24"}, "t") self.assertTrue(violations) def test_tuple_and_slice_pass(self) -> None: self.assertEqual( mod.validate_type_node({"tag": "tuple", "elems": [{"tag": "never"}]}, "t"), [] ) self.assertEqual( mod.validate_type_node({"tag": "slice", "inner": {"tag": "primitive", "name": "u8"}}, "t"), [], ) def test_reference_with_mut_passes(self) -> None: self.assertEqual( mod.validate_type_node( {"tag": "reference", "mut": True, "inner": {"tag": "primitive", "name": "str"}}, "t", ), [], ) def test_array_with_byte_len_passes(self) -> None: node = { "tag": "array", "inner": {"tag": "primitive", "name": "u8"}, "len": {"tag": "int", "negative": False, "magnitude": "8", "suffix": None}, } self.assertEqual(mod.validate_type_node(node, "t"), []) def test_generic_const_argument_normalization(self) -> None: # byte literal 255 with u8 suffix passes; 256 fails with field diagnostic ok = {"tag": "const", "atom": {"tag": "int", "negative": False, "magnitude": "255", "suffix": "u8"}} self.assertEqual(mod.validate_generic_arg(ok, "g"), []) bad = {"tag": "const", "atom": {"tag": "int", "negative": False, "magnitude": "256", "suffix": "u8"}} violations = mod.validate_generic_arg(bad, "g") self.assertTrue(any("byte value 256 exceeds" in str(v) for v in violations)) class ConstAtomTests(unittest.TestCase): def test_bool_passes(self) -> None: self.assertEqual(mod.validate_const_atom({"tag": "bool", "value": True}, "c"), []) def test_bool_non_bool_value_fails(self) -> None: violations = mod.validate_const_atom({"tag": "bool", "value": 1}, "c") self.assertTrue(violations) def test_integer_canonical_magnitude(self) -> None: self.assertEqual( mod.validate_const_atom( {"tag": "int", "negative": False, "magnitude": "0", "suffix": None}, "c" ), [], ) self.assertEqual( mod.validate_const_atom( {"tag": "int", "negative": False, "magnitude": "42", "suffix": "u32"}, "c" ), [], ) violations = mod.validate_const_atom( {"tag": "int", "negative": False, "magnitude": "042", "suffix": None}, "c" ) self.assertTrue(any("leading zeros" in str(v) for v in violations)) def test_negative_zero_fails(self) -> None: violations = mod.validate_const_atom( {"tag": "int", "negative": True, "magnitude": "0", "suffix": None}, "c" ) self.assertTrue(any("negative zero" in str(v) for v in violations)) def test_unknown_suffix_fails(self) -> None: violations = mod.validate_const_atom( {"tag": "int", "negative": False, "magnitude": "1", "suffix": "u33"}, "c" ) self.assertTrue(any("suffix" in str(v) for v in violations)) def test_char_scalar_passes(self) -> None: self.assertEqual(mod.validate_const_atom({"tag": "char", "scalar": "x"}, "c"), []) def test_char_multi_scalar_fails(self) -> None: violations = mod.validate_const_atom({"tag": "char", "scalar": "xy"}, "c") self.assertTrue(any("exactly one" in str(v) for v in violations)) def test_path_const_passes(self) -> None: atom = {"tag": "path", "absolute": True, "segments": ["SIZE"]} self.assertEqual(mod.validate_const_atom(atom, "c"), []) def test_unknown_tag_fails(self) -> None: violations = mod.validate_const_atom({"tag": "float", "value": 1.0}, "c") self.assertTrue(any("unknown const atom tag" in str(v) for v in violations)) def test_extra_field_fails(self) -> None: violations = mod.validate_const_atom( {"tag": "int", "negative": False, "magnitude": "1", "suffix": None, "extra": True}, "c" ) self.assertTrue(any("exactly" in str(v) for v in violations)) class LiveGateTests(unittest.TestCase): def test_real_topology_passes_live_gate(self) -> None: doc = mod.load_topology() self.assertEqual(mod.validate_topology_document(doc), []) def test_topology_artifact_is_sorted_unique(self) -> None: doc = mod.load_topology() frontier = doc["frontier"] self.assertEqual(frontier, sorted(frontier)) self.assertEqual(len(frontier), len(set(frontier))) self.assertEqual(set(frontier), {"utility", "memory", "plugins", "project", "skills", "session", "config", "debug", "core"}) class SourceScanTests(unittest.TestCase): """Hermetic fixtures for the AST-resolved source scan (Task 3.5/3.6).""" def _write_group(self, tmpdir: Path, group: str, files: dict[str, str]) -> Path: """Write a synthetic group tree under a temp groups root.""" base = tmpdir / group base.mkdir(parents=True, exist_ok=True) for name, content in files.items(): (base / name).write_text(content, encoding="utf-8") return tmpdir def test_parse_finds_concrete_app_free_fn(self) -> None: source = ( "use crate::tui::app::App;\n" "pub fn run_config(app: &mut App, arg: Option<&str>) -> CommandResult {\n" " CommandResult::ok()\n" "}\n" ) import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "config").mkdir() (root / "config" / "mod.rs").write_text(source, encoding="utf-8") items = mod.parse_rust_file(root / "config" / "mod.rs", root) apps = [it for it in items if it.is_concrete_app] self.assertEqual(len(apps), 1) self.assertEqual(apps[0].kind, "free") self.assertEqual(apps[0].qual_path, "crate::commands::groups::config::run_config") def test_parse_ignores_non_app_fns(self) -> None: source = ( "fn helper(value: u32) -> u32 { value }\n" "fn run(app: &mut crate::tui::app::App, arg: Option<&str>) -> CommandResult {\n" " CommandResult::ok()\n" "}\n" ) import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "core").mkdir() (root / "core" / "mod.rs").write_text(source, encoding="utf-8") items = mod.parse_rust_file(root / "core" / "mod.rs", root) self.assertEqual(sum(1 for it in items if it.is_concrete_app), 1) def test_parse_finds_trait_impl_and_inherent_methods(self) -> None: source = ( "pub struct BranchCmd;\n" "impl RegisterCommand for BranchCmd {\n" " fn info() -> &'static CommandInfo { &INFO }\n" " fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {\n" " branch(app, arg)\n" " }\n" "}\n" "impl BranchCmd {\n" " pub fn helper(&self) -> u32 { 1 }\n" "}\n" ) import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "session").mkdir() (root / "session" / "mod.rs").write_text(source, encoding="utf-8") items = mod.parse_rust_file(root / "session" / "mod.rs", root) trait_exec = [it for it in items if it.kind == "trait_impl" and it.name == "execute"] self.assertEqual(len(trait_exec), 1) self.assertTrue(trait_exec[0].is_concrete_app) inherent = [it for it in items if it.kind == "inherent"] self.assertEqual(len(inherent), 1) self.assertEqual(inherent[0].name, "helper") self.assertFalse(inherent[0].is_concrete_app) def test_scope_file_missing_fails(self) -> None: violations = mod.scan_leaf_handlers(["crates/tui/src/commands/groups/core/ghost.rs"], Path("/nonexistent"))[1] self.assertTrue(any("missing" in str(v) for v in violations)) def test_frontier_matches_group_source(self) -> None: doc = sample_topology() # utility scope has one concrete-App handler; session scope has one. doc["topology"]["utility"]["scope"] = ["utility/mod.rs"] doc["topology"]["session"]["scope"] = ["session/mod.rs"] import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "utility").mkdir(parents=True) (root / "session").mkdir(parents=True) (root / "utility" / "mod.rs").write_text( "use crate::tui::app::App;\n" "fn run_util(app: &mut App, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) (root / "session" / "mod.rs").write_text( "use crate::tui::app::App;\n" "fn run_save(app: &mut App, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) violations = mod.check_source_frontier(doc["topology"], doc["frontier"], root) self.assertEqual(violations, []) def test_cheating_removal_fails(self) -> None: doc = sample_topology() doc["topology"]["utility"]["scope"] = ["utility/mod.rs"] doc["topology"]["session"]["scope"] = ["session/mod.rs"] import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "utility").mkdir(parents=True) (root / "session").mkdir(parents=True) (root / "utility" / "mod.rs").write_text( "use crate::tui::app::App;\n" "fn run_util(app: &mut App, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) (root / "session" / "mod.rs").write_text( "use crate::tui::app::App;\n" "fn run_save(app: &mut App, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) # Cheat: remove utility from the frontier while its handler remains. violations = mod.check_source_frontier(doc["topology"], ["session"], root) self.assertTrue(any("stale-removal" in str(v) for v in violations)) def test_stale_frontier_entry_fails(self) -> None: doc = sample_topology() doc["topology"]["utility"]["scope"] = ["utility/mod.rs"] doc["topology"]["session"]["scope"] = ["session/mod.rs"] import tempfile with tempfile.TemporaryDirectory() as d: root = Path(d) (root / "utility").mkdir(parents=True) (root / "session").mkdir(parents=True) # utility has NO concrete-App handler (migrated: uses a context) (root / "utility" / "mod.rs").write_text( "fn run_util(contexts: CommandContexts<'_>, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) (root / "session" / "mod.rs").write_text( "use crate::tui::app::App;\n" "fn run_save(app: &mut App, arg: Option<&str>) -> CommandResult { CommandResult::ok() }\n", encoding="utf-8", ) violations = mod.check_source_frontier(doc["topology"], ["session", "utility"], root) self.assertTrue(any("stale-entry" in str(v) for v in violations)) def test_live_source_gate_passes(self) -> None: doc = mod.load_topology() violations = mod.check_source_frontier(doc["topology"], doc["frontier"]) self.assertEqual(violations, []) if __name__ == "__main__": unittest.main()