"""Governance semver: the classifier labels amendments major / minor / patch. Rules under test (docs/semver.md): - any change to ``constitution/kernel/`` or ``constitution/invariants.yaml`` is **major** (breaking change to the meta-rules); - adding a userland module is **minor** (new capability, no meta-rule change); - changing only parameter values inside an existing userland module is **patch**. """ from __future__ import annotations import shutil import pytest import yaml from tests.helpers import PROJECT_ROOT, classify, impact_of, tweak_first_numeric def _tree(tmp_path, name): dest = tmp_path / name shutil.copytree(PROJECT_ROOT / "constitution", dest / "constitution") return dest def _kernel_file(tree): files = sorted((tree / "constitution" / "kernel").glob("*.yaml")) assert files, "no kernel articles found" return files[0] def _userland_file(tree): files = sorted((tree / "constitution" / "userland").glob("*.yaml")) assert files, "no userland modules found" return files[0] def test_kernel_edit_is_major(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") article = _kernel_file(head) article.write_text( article.read_text(encoding="utf-8") + "\ntest_amendment_marker: kernel-change\n", encoding="utf-8", ) assert impact_of(classify(base, head)) == "major" def test_invariants_edit_is_major(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") invariants = head / "constitution" / "invariants.yaml" invariants.write_text( invariants.read_text(encoding="utf-8") + "\ntest_amendment_marker: invariant-change\n", encoding="utf-8", ) assert impact_of(classify(base, head)) == "major" def test_new_userland_module_is_minor(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") module = head / "constitution" / "userland" / "pet-policy.yaml" module.write_text( "id: pet-policy\n" "title: Household Pet Policy\n" "version: 0.1.0\n" "params:\n" " max_pets: 3\n" "rules:\n" " - id: pp-1\n" " text: No citizen may keep more than max_pets pets.\n", encoding="utf-8", ) assert impact_of(classify(base, head)) == "minor" def test_userland_parameter_tweak_is_patch(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") module = _userland_file(head) doc = yaml.safe_load(module.read_text(encoding="utf-8")) new_doc, changed = tweak_first_numeric(doc) if not changed: pytest.skip("no numeric parameter found in the userland module to tweak") module.write_text(yaml.safe_dump(new_doc, sort_keys=False), encoding="utf-8") assert impact_of(classify(base, head)) == "patch" def test_mixed_kernel_and_userland_is_major(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") article = _kernel_file(head) article.write_text( article.read_text(encoding="utf-8") + "\ntest_amendment_marker: kernel-change\n", encoding="utf-8", ) module = head / "constitution" / "userland" / "extra-module.yaml" module.write_text("id: extra\ntitle: Extra module\n", encoding="utf-8") assert impact_of(classify(base, head)) == "major" def test_no_change_is_not_major(tmp_path): base = _tree(tmp_path, "base") head = _tree(tmp_path, "head") result = classify(base, head) assert impact_of(result) in {"patch", "none", "noop", "no_change"}, ( "an empty diff must never be classified as a breaking change" )