From 2ef97c87a0f818a906aa42942c32d6c69f9ec899 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 21 Sep 2026 09:37:43 +0200 Subject: [PATCH] Apply GH-DEC-2026-020: the checker prints its version and scope, and detects a version anywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check_layer_conformance.py now carries VALIDATED_AGAINST and SCOPE and prints both on every run, the OK line included (kings-guard's pattern, ruling §4). A12 detection widens from the key `standard_version` to any version in any key or value of the INTENT.md frontmatter and layer.yaml: a *version* key, a version-bearing path, or a version in standard/companion/framework (ruling §1-§2). schema_version, comments and stance/claims files are not reached (§1, §3). The declaration itself was already conforming; unchanged. Tests fail if a versioned `standard:` path or a companion_version comes back. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703 --- scripts/check_layer_conformance.py | 103 ++++++++++++++++++++++++----- tests/test_layer_conformance.py | 48 ++++++++++++++ 2 files changed, 135 insertions(+), 16 deletions(-) diff --git a/scripts/check_layer_conformance.py b/scripts/check_layer_conformance.py index 8014665..e12b674 100644 --- a/scripts/check_layer_conformance.py +++ b/scripts/check_layer_conformance.py @@ -9,11 +9,17 @@ This is an Engine (PIP). The checkable claims: - both layer values are in §3's closed four-token vocabulary and are compared after an ASCII case fold (A9); nothing is re-spelled. A disagreement that survives the fold is reported as a finding, not resolved by precedence (A11) -- neither form carries a standard_version (A12) +- neither form carries a standard or companion version in any key or value, + including a version-bearing `standard:` or `companion:` path (A12 r2, + GH-DEC-2026-020 §1-§2). Comments and `schema_version` are not reached, and + stance/claims/classification files are out of scope (GH-DEC-2026-020 §3) - no pep_stance path - no catalogued Tooling client (OpenBao, key-cape, cluster) - sqlite3 is this PIP's own store and is allowed +Every run prints the standard text it checks against (VALIDATED_AGAINST) and +the scope it ranged over (GH-DEC-2026-020 §4: the version belongs to the run). + Exit 0 clean, 1 undeclared Tooling contact, 2 declaration malformed. """ from __future__ import annotations @@ -26,6 +32,15 @@ from pathlib import Path import yaml +# The standard text this checker was built and validated against, printed on +# every run (GH-DEC-2026-020 §4; kings-guard's pattern). A declaration MUST NOT +# carry this (A12); the run does. Bump it when re-validated against a newer +# accepted text. +VALIDATED_AGAINST = ( + "net-kingdom/canon/standards/security-layer-model_v0.7.md" + " (+ GH-DEC-2026-017, GH-DEC-2026-020 / A12 r2)" +) + # §3 as amended (A9): closed, four tokens, compared after an ASCII case fold. LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"} @@ -40,6 +55,62 @@ SRC = ROOT / "src" / "maturity_engine" DECL = ROOT / "layer.yaml" INTENT = ROOT / "INTENT.md" +# What this run ranges over. Declaration files only: stance, claims and +# evidence-classification maps are deliberately NOT in scope (GH-DEC-2026-020 §3). +SCOPE = "INTENT.md frontmatter, layer.yaml, imports under src/maturity_engine" + +# A12 r2: a version anywhere in the declaration, not only a key literally named +# `standard_version`. `schema_version` is the file format's own version, not a +# standard version, and is not reached (GH-DEC-2026-020 §1). +EXEMPT_VERSION_KEYS = {"schema_version"} +# A version-bearing path or file name, e.g. `security-layer-model_v0.7.md`, +# `.../v0.2/companion.md`. Matched only against whitespace-free values, so prose +# notes citing a rule's history are not mistaken for a pin. +VERSIONED_PATH = re.compile(r"(?i)(?:^|[_\-/.])v\d+(?:\.\d+)*(?:$|[_\-/.])") +# Keys that name a standard or companion: any version-like token in their value counts. +STANDARD_KEYS = {"standard", "companion", "framework"} +BARE_VERSION = re.compile(r"\d+\.\d+") + + +def version_findings(data: object, where: str) -> list[str]: + """Every place a standard or companion version appears in a declaration.""" + found: list[str] = [] + + def walk(node: object, path: str, key: str | None) -> None: + if isinstance(node, dict): + for k, v in node.items(): + ks = str(k) + if ks in EXEMPT_VERSION_KEYS: + continue + if "version" in ks.lower(): + found.append(f"{where}: key '{path}{ks}' carries a version") + walk(v, f"{path}{ks}.", ks) + elif isinstance(node, list): + for i, v in enumerate(node): + walk(v, f"{path}{i}.", key) + elif isinstance(node, str): + value = node.strip() + label = path.rstrip(".") + if key and key.lower() in STANDARD_KEYS and BARE_VERSION.search(value): + found.append(f"{where}: '{label}' value {value!r} carries a version") + elif value and not any(c.isspace() for c in value) and VERSIONED_PATH.search(value): + found.append(f"{where}: '{label}' value {value!r} is a version-bearing path") + + walk(data, "", None) + return found + + +def _fail_versions(findings: list[str]) -> None: + print( + "FAIL: a layer declaration MUST NOT carry a standard or companion version, " + "in any key or value (GH-DEC-2026-017 §5, GH-DEC-2026-020, A12 r2)", + file=sys.stderr, + ) + for line in findings: + print(f" {line}", file=sys.stderr) + raise SystemExit(2) + + TOOLING_IMPORTS = { "hvac": "OpenBao / Vault client", "bao": "OpenBao client", @@ -75,13 +146,9 @@ def load_declaration() -> dict: file=sys.stderr, ) raise SystemExit(2) - if "standard_version" in data: - print( - f"FAIL: {DECL.name} carries 'standard_version' — a layer declaration MUST NOT " - "carry a standard version (GH-DEC-2026-017 §5, A12)", - file=sys.stderr, - ) - raise SystemExit(2) + findings = version_findings(data, DECL.name) + if findings: + _fail_versions(findings) if _fold(data["layer"]) not in LAYER_VOCABULARY: print( f"FAIL: {DECL.name} layer {data['layer']!r} is outside §3's closed vocabulary " @@ -117,13 +184,9 @@ def intent_frontmatter() -> dict: file=sys.stderr, ) raise SystemExit(2) - if "standard_version" in meta: - print( - "FAIL: INTENT.md frontmatter carries 'standard_version' — a layer declaration " - "MUST NOT carry a standard version (GH-DEC-2026-017 §5, A12)", - file=sys.stderr, - ) - raise SystemExit(2) + findings = version_findings(meta, "INTENT.md frontmatter") + if findings: + _fail_versions(findings) if _fold(meta["layer"]) not in LAYER_VOCABULARY: print( f"FAIL: INTENT.md layer {meta['layer']!r} is outside §3's closed vocabulary " @@ -162,6 +225,9 @@ def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--report", action="store_true") args = parser.parse_args() + # GH-DEC-2026-020 §4: every run states what it checks against and its scope, + # on stdout, before any verdict — so a failing run carries it too. + print(f"check_layer_conformance: validated against {VALIDATED_AGAINST}; scope: {SCOPE}") decl = load_declaration() intent = intent_frontmatter() @@ -197,8 +263,13 @@ def main() -> int: print(f" tooling contacts: {len(decl.get('tooling_contacts') or [])}") print(f" non-tooling clients: {len(decl.get('non_tooling_clients') or [])}") print(" pep_stance: none") + print(f" checker validated against: {VALIDATED_AGAINST}") + print(f" scope: {SCOPE}") else: - print(f"OK: Engine/PIP declaration holds; no catalogued Tooling client in {SRC.relative_to(ROOT)}") + print( + f"OK: Engine/PIP declaration holds; no catalogued Tooling client in " + f"{SRC.relative_to(ROOT)}; validated against {VALIDATED_AGAINST}; scope: {SCOPE}" + ) return 0 diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py index 73bec83..0346b36 100644 --- a/tests/test_layer_conformance.py +++ b/tests/test_layer_conformance.py @@ -112,3 +112,51 @@ def test_checker_catches_an_openbao_client(tmp_path, monkeypatch): hits = module.scan() assert hits assert hits[0][1] == "hvac" + + +def _checker(): + import importlib.util + + spec = importlib.util.spec_from_file_location("check_layer_conformance_v", SCRIPT) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_every_run_prints_the_version_and_scope(): + """GH-DEC-2026-020 §4: the version belongs to the run, OK line included.""" + module = _checker() + for args in ((), ("--report",)): + out = _run(*args).stdout + assert module.VALIDATED_AGAINST in out + assert module.SCOPE in out + ok = [line for line in _run().stdout.splitlines() if line.startswith("OK:")] + assert ok and module.VALIDATED_AGAINST in ok[0] and module.SCOPE in ok[0] + + +def test_no_version_anywhere_in_either_declaration(): + """A12 r2: no standard or companion version in any key or value.""" + module = _checker() + assert module.version_findings(_intent_frontmatter(), "INTENT.md") == [] + assert module.version_findings(yaml.safe_load(DECL.read_text()), "layer.yaml") == [] + + +@pytest.mark.parametrize( + "declaration", + [ + {"standard": "net-kingdom/canon/standards/security-layer-model_v0.7.md"}, + {"companion": "net-kingdom/v0.2/SECURITY-COMPANION.md"}, + {"companion_version": "0.2"}, + {"standard_version": "0.7"}, + {"framework": "netkingdom-security-layer-model 0.8"}, + ], +) +def test_checker_catches_a_version_beyond_the_key_name(declaration): + """Fails if a versioned `standard:` path or a companion_version comes back.""" + assert _checker().version_findings(declaration, "x") + + +def test_schema_version_and_prose_are_not_reached(): + module = _checker() + assert module.version_findings({"schema_version": "0.1"}, "x") == [] + assert module.version_findings({"note": "Outside §5 by the v0.5 scope rule"}, "x") == []