tenant-engine/scripts/check_layer_conformance.py
tegwick d846eba65c Apply GH-DEC-2026-017: INTENT.md governs, layer.yaml is derived, no version
Remove standard_version from layer.yaml and the INTENT.md frontmatter (A12),
mark layer.yaml derived: true / derived_from: INTENT.md (A11), and change the
conformance checker and its tests in the same commit so the conforming
declaration does not fail as malformed.

The checker now requires the INTENT.md layer: key, rejects standard_version
in either form, checks the layer against the closed four-token vocabulary
(A9), and reports a layer/role disagreement between the two forms after an
ASCII case-fold. Nothing is re-spelled: INTENT.md keeps Engine/PIP and
layer.yaml keeps engine/pip. pep-stance.yaml and pip-claims.yaml keep their
standard_version; neither is a layer declaration.

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 07:35:01 +02:00

195 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""Check tenant-engine against the NetKingdom security layer model (§5, §11).
Read-only. Makes two mechanical checks:
1. INTENT.md frontmatter declares the layer and governs; layer.yaml is a
derived artifact (derived: true, derived_from: INTENT.md) that must agree
with it after ASCII case-folding. The layer is one of §3's closed four
tokens (Taxonomy, Tooling, Engine, Staff). Neither form carries a
standard_version (GH-DEC-2026-017 §1-§5, v0.8 amendments A9, A11, A12).
2. No catalogued Tooling client (OpenBao, key-cape) appears in src/
unless it maps to a declared §5.1 / §5.2 / §5.3 entry.
PostgreSQL / SQLite / httpx-to-flex-auth / httpx-to-audit-core are not
Tooling contacts. They are listed in layer.yaml non_tooling_clients so
the inventory is total.
Exit 0 clean, 1 undeclared contact, 2 declaration malformed.
"""
from __future__ import annotations
import argparse
import ast
import re
import sys
from pathlib import Path
try:
import yaml
except ImportError: # pragma: no cover - dev extra
print("FAIL: PyYAML is required (pip install pyyaml)", file=sys.stderr)
raise SystemExit(2)
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src" / "tenant_engine"
DECL = ROOT / "layer.yaml"
INTENT = ROOT / "INTENT.md"
# Catalogued Tooling in statute §4 today: key-cape and OpenBao.
# Import roots that would constitute a direct client of those.
TOOLING_IMPORTS = {
"hvac": "OpenBao / Vault client",
"bao": "OpenBao client",
"keycloak": "key-cape / Keycloak client",
"ldap3": "direct LDAP client (key-cape tooling)",
"python_ldap": "direct LDAP client (key-cape tooling)",
}
TOOLING_ARGV = re.compile(r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b)\s*,""")
OPENBAO_ADDR = re.compile(r"\b(?:VAULT_ADDR|BAO_ADDR|X-Vault-Token)\b")
# §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 only; two spellings of a token are one token."""
return str(value).translate(str.maketrans("ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz"))
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", "derived", "derived_from", "tooling_contacts"):
if key not in data:
print(f"FAIL: {DECL.name} missing required key '{key}'", file=sys.stderr)
raise SystemExit(2)
if data["derived"] is not True or data["derived_from"] != "INTENT.md":
print(f"FAIL: {DECL.name} must be marked derived: true, derived_from: INTENT.md "
"(§11, GH-DEC-2026-017 §1)", file=sys.stderr)
raise SystemExit(2)
if "standard_version" in data:
print(f"FAIL: {DECL.name} carries standard_version; a layer declaration "
"must not (GH-DEC-2026-017, A12)", file=sys.stderr)
raise SystemExit(2)
if _fold(data["layer"]) not in LAYER_VOCABULARY:
print(f"FAIL: {DECL.name} layer {data['layer']!r} is outside the closed "
f"vocabulary {sorted(LAYER_VOCABULARY)}", 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["repository"] != "tenant-engine":
print(f"FAIL: repository is {data['repository']!r}", file=sys.stderr)
raise SystemExit(2)
return data
def intent_frontmatter() -> dict:
text = INTENT.read_text()
if not text.startswith("---"):
print("FAIL: INTENT.md has no YAML frontmatter", file=sys.stderr)
raise SystemExit(2)
block = text.split("---", 2)[1]
data = yaml.safe_load(block) or {}
if "layer" not in data:
print("FAIL: INTENT.md frontmatter has no layer: key (§11)", file=sys.stderr)
raise SystemExit(2)
if "standard_version" in data:
print("FAIL: INTENT.md frontmatter carries standard_version; a layer "
"declaration must not (GH-DEC-2026-017, A12)", file=sys.stderr)
raise SystemExit(2)
if _fold(data["layer"]) not in LAYER_VOCABULARY:
print(f"FAIL: INTENT.md layer {data['layer']!r} is outside the closed "
f"vocabulary {sorted(LAYER_VOCABULARY)}", file=sys.stderr)
raise SystemExit(2)
if _fold(data["layer"]) != "engine":
print(f"FAIL: INTENT.md layer is {data.get('layer')!r}, expected Engine", file=sys.stderr)
raise SystemExit(2)
if _fold(data.get("role", "")) != "pip":
print(f"FAIL: INTENT.md role is {data.get('role')!r}, expected PIP", file=sys.stderr)
raise SystemExit(2)
return data
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) and 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")):
text = path.read_text()
for module in sorted(imported_modules(path)):
if module in TOOLING_IMPORTS:
hits.append((path, module, TOOLING_IMPORTS[module]))
if TOOLING_ARGV.search(text) or OPENBAO_ADDR.search(text):
hits.append((path, "openbao-invocation", "OpenBao argv or address"))
return hits
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--report", action="store_true")
args = parser.parse_args()
decl = load_declaration()
intent = intent_frontmatter()
# A11: a disagreement between the governing and derived forms is a finding
# in its own right, reported rather than resolved by precedence. Case is
# folded first (A9), so what survives is a real disagreement.
for key in ("layer", "role"):
if _fold(decl[key]) != _fold(intent.get(key, "")):
print(f"FAIL: {key} disagrees after case-fold — INTENT.md (governing): "
f"{intent.get(key)!r}, layer.yaml (derived): {decl[key]!r}",
file=sys.stderr)
return 2
hits = scan()
if args.report:
print(f"tenant-engine — layer {intent['layer']}, role {intent['role']} "
f"(INTENT.md governs; layer.yaml derived from {decl['derived_from']})")
print(f" tooling contacts declared: {len(decl.get('tooling_contacts') or [])}")
print(f" non-tooling clients: {len(decl.get('non_tooling_clients') or [])}")
if hits:
print("", file=sys.stderr)
print("FAIL: undeclared Tooling-layer client (§11 undeclared violation)", file=sys.stderr)
for path, module, what in hits:
print(f" {path.relative_to(ROOT)}: {module}{what}", file=sys.stderr)
return 1
if decl.get("tooling_contacts"):
print("FAIL: tooling_contacts is not empty; this engine claimed none", file=sys.stderr)
return 1
if not args.report:
print(f"OK: Engine/PIP declaration matches INTENT.md; no Tooling client in "
f"{SRC.relative_to(ROOT)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())