maturity-engine/scripts/check_layer_conformance.py
tegwick 2ef97c87a0 Apply GH-DEC-2026-020: the checker prints its version and scope, and detects a version anywhere
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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
2026-09-21 09:37:43 +02:00

277 lines
11 KiB
Python

#!/usr/bin/env python3
"""Check maturity-engine against the NetKingdom security layer model (§5, §11).
This is an Engine (PIP). The checkable claims:
- INTENT.md frontmatter declares the layer and governs (GH-DEC-2026-017 §1)
- layer.yaml is derived: marked `derived: true`, `derived_from: INTENT.md`,
and declares layer=engine, role=pip
- 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 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
import argparse
import ast
import re
import sys
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"}
def _fold(value: object) -> str:
"""ASCII case-fold: two spellings of a token are one token."""
return str(value).strip().encode("ascii", "ignore").decode().lower()
ROOT = Path(__file__).resolve().parents[1]
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",
"kubernetes": "cluster client",
"kubernetes_asyncio": "cluster client",
"ldap3": "direct LDAP client (key-cape tooling)",
"python_ldap": "direct LDAP client (key-cape tooling)",
}
def load_declaration() -> dict:
if not DECL.exists():
print(f"FAIL: no declaration at {DECL.relative_to(ROOT)} (§11)", file=sys.stderr)
raise SystemExit(2)
try:
data = yaml.safe_load(DECL.read_text())
except yaml.YAMLError as exc:
print(f"FAIL: {DECL.name} is not parseable: {exc}", file=sys.stderr)
raise SystemExit(2) from exc
for key in ("layer", "role", "repository", "tooling_contacts", "derived", "derived_from"):
if key not in data:
print(f"FAIL: {DECL.name} missing required key '{key}' (§11)", file=sys.stderr)
raise SystemExit(2)
if data["derived"] is not True:
print(
f"FAIL: {DECL.name} must be marked 'derived: true' (§11, GH-DEC-2026-017 §1)",
file=sys.stderr,
)
raise SystemExit(2)
if data["derived_from"] != "INTENT.md":
print(
f"FAIL: {DECL.name} derives from {data['derived_from']!r}; §11 names INTENT.md",
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 "
f"{sorted(LAYER_VOCABULARY)} (case-insensitive)",
file=sys.stderr,
)
raise SystemExit(2)
if _fold(data["layer"]) != "engine":
print(f"FAIL: declared layer is {data['layer']!r}, expected 'engine'", file=sys.stderr)
raise SystemExit(2)
if _fold(data["role"]) != "pip":
print(f"FAIL: declared role is {data['role']!r}, expected 'pip'", file=sys.stderr)
raise SystemExit(2)
if data.get("pep_stance"):
print("FAIL: pep_stance is set; this engine is not PEP-shaped", file=sys.stderr)
raise SystemExit(2)
return data
def intent_frontmatter() -> dict:
text = INTENT.read_text()
match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
if not match:
print("FAIL: INTENT.md has no YAML frontmatter (§11)", file=sys.stderr)
raise SystemExit(2)
meta = yaml.safe_load(match.group(1))
if not isinstance(meta, dict):
print("FAIL: INTENT.md frontmatter is not a mapping", file=sys.stderr)
raise SystemExit(2)
if "layer" not in meta:
print(
"FAIL: INTENT.md frontmatter has no 'layer:' key — that is the declaration (§11)",
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 "
f"{sorted(LAYER_VOCABULARY)} (case-insensitive)",
file=sys.stderr,
)
raise SystemExit(2)
return meta
def imported_modules(path: Path) -> set[str]:
try:
tree = ast.parse(path.read_text())
except SyntaxError:
return set()
found: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
found.update(alias.name.split(".")[0] for alias in node.names)
elif isinstance(node, ast.ImportFrom):
if node.level == 0 and node.module:
found.add(node.module.split(".")[0])
return found
def scan() -> list[tuple[Path, str, str]]:
hits: list[tuple[Path, str, str]] = []
for path in sorted(SRC.rglob("*.py")):
for module in sorted(imported_modules(path)):
if module in TOOLING_IMPORTS:
hits.append((path, module, TOOLING_IMPORTS[module]))
return hits
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()
# A11: the derived form MUST agree with the governing one. Case is folded
# first (A9), so a mismatch here is a real layer disagreement — a finding
# in its own right, reported rather than resolved away by precedence.
if _fold(intent["layer"]) != _fold(decl["layer"]):
print(
f"FAIL: layer disagreement after case fold — INTENT.md (governs) "
f"{intent['layer']!r} vs layer.yaml (derived) {decl['layer']!r}",
file=sys.stderr,
)
return 2
if _fold(intent.get("role", "")) != _fold(decl["role"]):
print(
f"FAIL: INTENT.md role {intent.get('role')!r} != layer.yaml {decl['role']!r}",
file=sys.stderr,
)
return 2
hits = scan()
if hits:
print("FAIL: catalogued Tooling-layer client in an Engine that does not own it", file=sys.stderr)
for path, module, what in hits:
print(f" {path.relative_to(ROOT)}: imports {module!r} — {what}", file=sys.stderr)
return 1
if args.report:
print(
f"maturity-engine — layer {intent['layer']} (INTENT.md, governs), role {decl['role']}"
)
print(f" layer.yaml: derived from {decl['derived_from']}, layer: {decl['layer']}")
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 "
f"{SRC.relative_to(ROOT)}; validated against {VALIDATED_AGAINST}; scope: {SCOPE}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())