INTENT.md frontmatter governs and layer.yaml is marked derived from it. standard_version is removed from both forms (A12). The checker and its tests change in the same commit: they no longer require the field, they reject it, they fold case against the four-token vocabulary (A9) instead of comparing with plain equality, and they still fail a layer disagreement that survives the fold (A11). Neither layer value is re-spelled. The ruling answers kings-guard's "validated-against" reading of the field (§5: version-scoped state belongs in the derived conformance record), so that version now lives in the checker as VALIDATED_AGAINST and is printed in every run's output. The standard: path in INTENT.md is unversioned to match the reference form. 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
312 lines
13 KiB
Python
312 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Check kings-guard against the NetKingdom security layer model (§5, §11).
|
|
|
|
Read-only. kings-guard's whole position under the standard rests on one claim:
|
|
|
|
it holds no direct client for any Tooling-layer system, and the
|
|
capabilities that would need one sit at zero instead (§11 blocked-clean)
|
|
|
|
That claim has been asserted in prose since KG-DEC-2026-001. §11 (v0.7) requires
|
|
a machine-readable declaration because prose cannot distinguish a declaration
|
|
from a transcribed review. This script is what makes the claim checkable: it
|
|
fails if a Tooling client appears in src/ without a matching layer.yaml entry.
|
|
|
|
The failure it exists to catch is a *convenience* — someone reaching for an
|
|
OpenBao or cluster client during an incident because the engine surface still
|
|
does not exist (§9.2). That is precisely the "small convenience" §6 warns about,
|
|
and it would arrive as a one-line import.
|
|
|
|
Declaration form (GH-DEC-2026-017, amendments A9, A11, A12): INTENT.md's
|
|
frontmatter `layer:` key governs; layer.yaml is a derived artifact that must be
|
|
marked derived, name INTENT.md, and agree with it. Layer comparison ASCII-folds
|
|
case against §3's closed four-token vocabulary, so `Staff` and `staff` agree and
|
|
neither file is re-spelled. A disagreement that survives the fold is reported as
|
|
a finding, not resolved by precedence. Neither form may carry a
|
|
`standard_version`; the version this check was built and validated against is
|
|
the checker's own, VALIDATED_AGAINST below, and is printed in every run's output
|
|
— this script's output is the derived conformance record A12 points to.
|
|
|
|
Review dates are reported, never enforced: a date-triggered failure breaks the
|
|
build on a calendar day with no code change.
|
|
|
|
Exit 0 clean, 1 undeclared contact found or the two declaration forms disagree,
|
|
2 declaration malformed.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import ast
|
|
import re
|
|
import sys
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SRC = ROOT / "src" / "kings_guard"
|
|
DECL = ROOT / "layer.yaml"
|
|
INTENT = ROOT / "INTENT.md"
|
|
|
|
# The standard text this checker was built and validated against. This is the
|
|
# derived conformance record's version (GH-DEC-2026-017 §5 / A12), moved here
|
|
# from layer.yaml's former `standard_version: "0.7"`, which recorded the same
|
|
# fact in a place the ruling says a declaration must not carry it. Bump it when
|
|
# the checker is re-validated against a newer accepted text.
|
|
VALIDATED_AGAINST = "net-kingdom/canon/standards/security-layer-model_v0.7.md"
|
|
|
|
# §3's closed layer vocabulary (A9): four tokens, compared ASCII case-insensitively.
|
|
LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
|
|
EXPECTED_LAYER = "staff" # kings-guard's own §4 row, folded
|
|
|
|
# Import roots that would constitute a direct Tooling-layer client under §4.
|
|
# Matched against the top-level module of every import in src/.
|
|
TOOLING_IMPORTS = {
|
|
"hvac": "OpenBao / Vault client",
|
|
"bao": "OpenBao client",
|
|
"kubernetes": "cluster client",
|
|
"kubernetes_asyncio": "cluster client",
|
|
"psycopg": "direct database connection",
|
|
"psycopg2": "direct database connection",
|
|
"asyncpg": "direct database connection",
|
|
"sqlalchemy": "direct database connection",
|
|
"pymysql": "direct database connection",
|
|
"redis": "direct datastore connection",
|
|
"ldap3": "direct LDAP client (key-cape tooling)",
|
|
"python_ldap": "direct LDAP client (key-cape tooling)",
|
|
"docker": "container runtime client",
|
|
}
|
|
|
|
# §3.4 rule 1 — no standing credential held in the repository or its
|
|
# configuration. Filenames that would be a standing secret, and assignments
|
|
# of well-known secret env vars to string literals in src/.
|
|
CREDENTIAL_FILENAMES = {
|
|
".env",
|
|
".env.local",
|
|
".env.production",
|
|
"credentials.json",
|
|
"secrets.yaml",
|
|
"secrets.yml",
|
|
"id_rsa",
|
|
"id_ed25519",
|
|
"id_ecdsa",
|
|
}
|
|
CREDENTIAL_LITERAL = re.compile(
|
|
r"""(?x)
|
|
\b(?:VAULT_TOKEN|OPENBAO_TOKEN|BAO_TOKEN|AWS_SECRET_ACCESS_KEY|
|
|
PRIVATE_KEY|BEGIN\ (?:RSA\ )?PRIVATE\ KEY)
|
|
"""
|
|
)
|
|
SKIP_CREDENTIAL_SCAN_DIRS = {".git", ".venv", "__pycache__", ".pytest_cache", ".ruff_cache"}
|
|
|
|
|
|
def fold(value: object) -> str:
|
|
"""ASCII case-fold, per §3 as amended (A9): two spellings are one token."""
|
|
return str(value).encode("ascii", "replace").decode("ascii").lower()
|
|
|
|
|
|
def _malformed(message: str) -> None:
|
|
print(f"FAIL: {message}", file=sys.stderr)
|
|
raise SystemExit(2)
|
|
|
|
|
|
def load_governing_layer() -> str:
|
|
"""The declaration: INTENT.md frontmatter `layer:` (§11, GH-DEC-2026-017 §1)."""
|
|
if not INTENT.exists():
|
|
_malformed(f"no {INTENT.name} — §11's governing declaration form")
|
|
lines = INTENT.read_text().splitlines()
|
|
if not lines or lines[0].strip() != "---":
|
|
_malformed(f"{INTENT.name} has no frontmatter to carry the declaration (§11)")
|
|
try:
|
|
end = lines[1:].index("---") + 1
|
|
except ValueError:
|
|
_malformed(f"{INTENT.name} frontmatter is not terminated")
|
|
try:
|
|
front = yaml.safe_load("\n".join(lines[1:end])) or {}
|
|
except yaml.YAMLError as exc:
|
|
_malformed(f"{INTENT.name} frontmatter is not parseable: {exc}")
|
|
if "layer" not in front:
|
|
_malformed(f"{INTENT.name} frontmatter has no 'layer' key — §11's declaration")
|
|
if "standard_version" in front:
|
|
_malformed(
|
|
f"{INTENT.name} frontmatter carries 'standard_version' — a layer "
|
|
"declaration MUST NOT carry a standard version (§11 as amended by A12)"
|
|
)
|
|
layer = front["layer"]
|
|
if fold(layer) not in LAYER_VOCABULARY:
|
|
_malformed(
|
|
f"{INTENT.name} declares layer {layer!r}, outside §3's closed vocabulary "
|
|
f"{sorted(LAYER_VOCABULARY)} (case-insensitive)"
|
|
)
|
|
if fold(layer) != EXPECTED_LAYER:
|
|
_malformed(f"{INTENT.name} declares layer {layer!r}, expected Staff (§4 row)")
|
|
return str(layer)
|
|
|
|
|
|
def load_declaration() -> dict:
|
|
"""The derived form, layer.yaml (§11 derived-artifact rule)."""
|
|
if not DECL.exists():
|
|
_malformed(f"no declaration at {DECL.relative_to(ROOT)} (§11)")
|
|
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", "repository", "tooling_contacts", "derived", "derived_from"):
|
|
if key not in data:
|
|
_malformed(f"{DECL.name} missing required key '{key}' (§11)")
|
|
if data["derived"] is not True:
|
|
_malformed(f"{DECL.name} must be marked 'derived: true' (§11, GH-DEC-2026-017 §1)")
|
|
if data["derived_from"] != "INTENT.md":
|
|
_malformed(
|
|
f"{DECL.name} derives from {data['derived_from']!r}; §11 names INTENT.md "
|
|
"as the governing declaration"
|
|
)
|
|
if "standard_version" in data:
|
|
_malformed(
|
|
f"{DECL.name} carries 'standard_version' — a layer declaration MUST NOT "
|
|
"carry a standard version (§11 as amended by A12)"
|
|
)
|
|
if fold(data["layer"]) not in LAYER_VOCABULARY:
|
|
_malformed(
|
|
f"{DECL.name} declares layer {data['layer']!r}, outside §3's closed "
|
|
f"vocabulary {sorted(LAYER_VOCABULARY)} (case-insensitive)"
|
|
)
|
|
return data
|
|
|
|
|
|
def forms_disagree(governing: object, derived: object) -> bool:
|
|
"""A11: the derived form must agree with INTENT.md, after the A9 case fold."""
|
|
return fold(governing) != fold(derived)
|
|
|
|
|
|
def imported_modules(path: Path) -> set[str]:
|
|
"""Top-level module name of every import in one file."""
|
|
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 scan_standing_credentials() -> list[tuple[Path, str]]:
|
|
"""§3.4 rule 1: no standing credential in the repository or its config."""
|
|
hits: list[tuple[Path, str]] = []
|
|
for path in ROOT.rglob("*"):
|
|
if not path.is_file():
|
|
continue
|
|
if any(part in SKIP_CREDENTIAL_SCAN_DIRS for part in path.parts):
|
|
continue
|
|
if path.name in CREDENTIAL_FILENAMES:
|
|
hits.append((path, f"credential-shaped file {path.name}"))
|
|
continue
|
|
if path.suffix in {".pem", ".key"} and "test" not in path.parts:
|
|
hits.append((path, f"key material file {path.name}"))
|
|
if SRC.is_dir():
|
|
for path in sorted(SRC.rglob("*.py")):
|
|
text = path.read_text(encoding="utf-8")
|
|
if CREDENTIAL_LITERAL.search(text):
|
|
hits.append((path, "standing-credential literal or private-key block"))
|
|
return hits
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--report", action="store_true", help="print the declaration summary")
|
|
args = parser.parse_args()
|
|
|
|
governing = load_governing_layer()
|
|
decl = load_declaration()
|
|
declared = {c.get("id") for c in decl.get("tooling_contacts") or []}
|
|
hits = scan()
|
|
credential_hits = scan_standing_credentials()
|
|
|
|
undeclared = [h for h in hits if h[1] not in declared]
|
|
rules = decl.get("agent_principal_rules") or {}
|
|
checks = decl.get("agent_principal_rule_checks") or {}
|
|
|
|
if args.report:
|
|
print(f"kings-guard — layer: {governing} (declared in INTENT.md; §11 governing form)")
|
|
print(f" layer.yaml: derived from {decl['derived_from']}, layer: {decl['layer']}")
|
|
print(f" checker validated against: {VALIDATED_AGAINST}")
|
|
print(f" tooling contacts declared: {len(declared)}")
|
|
print(f" unowned capabilities (§11 blocked-clean): "
|
|
f"{len(decl.get('unowned_capabilities') or [])}")
|
|
today = date.today()
|
|
for cap in decl.get("unowned_capabilities") or []:
|
|
review = cap.get("review")
|
|
stale = ""
|
|
if review and date.fromisoformat(str(review)) < today:
|
|
stale = " [REVIEW OVERDUE]"
|
|
print(f" - {cap['id']}: {cap.get('owner_status', '?')}{stale}")
|
|
print(" agent-principal rule checks (§3.4):")
|
|
for name, meta in checks.items():
|
|
form = meta.get("form", "unspecified") if isinstance(meta, dict) else "unspecified"
|
|
print(f" - {name}: {form} (claimed={rules.get(name)})")
|
|
|
|
if forms_disagree(governing, decl["layer"]):
|
|
print("", file=sys.stderr)
|
|
print("FAIL: the two declaration forms disagree (§11, A11)", file=sys.stderr)
|
|
print(f" INTENT.md (governs): layer: {governing}", file=sys.stderr)
|
|
print(f" layer.yaml (derived): layer: {decl['layer']}", file=sys.stderr)
|
|
print(" Case is already folded, so this is a real layer disagreement.", file=sys.stderr)
|
|
print(" It is a finding in its own right; precedence does not erase it.", file=sys.stderr)
|
|
return 1
|
|
|
|
if undeclared:
|
|
print("", file=sys.stderr)
|
|
print("FAIL: undeclared Tooling-layer client (§11 undeclared violation)", file=sys.stderr)
|
|
for path, module, what in undeclared:
|
|
rel = path.relative_to(ROOT)
|
|
print(f" {rel}: imports '{module}' — {what}", file=sys.stderr)
|
|
print("", file=sys.stderr)
|
|
print(" A Staff repository may not hold a direct Tooling client (§5).", file=sys.stderr)
|
|
print(" Route it through the owning engine, or if none exists, raise an", file=sys.stderr)
|
|
print(" engine gap — do not declare this to make the check pass.", file=sys.stderr)
|
|
return 1
|
|
|
|
if credential_hits:
|
|
print("", file=sys.stderr)
|
|
print("FAIL: standing credential material (§3.4 rule 1)", file=sys.stderr)
|
|
for path, what in credential_hits:
|
|
try:
|
|
rel = path.relative_to(ROOT)
|
|
except ValueError:
|
|
rel = path
|
|
print(f" {rel}: {what}", file=sys.stderr)
|
|
print("", file=sys.stderr)
|
|
print(" Authority is per task, time-bounded, and attributable to the", file=sys.stderr)
|
|
print(" principal acted for. Do not hold a standing secret here.", file=sys.stderr)
|
|
return 1
|
|
|
|
if rules.get("no_standing_credential") is not True:
|
|
print("FAIL: layer.yaml does not claim no_standing_credential (§3.4 rule 1)", file=sys.stderr)
|
|
return 2
|
|
|
|
if not args.report:
|
|
print(
|
|
f"OK: no direct Tooling client in {SRC.relative_to(ROOT)}; "
|
|
"no standing credential (§5, §11, §3.4 rule 1); "
|
|
f"declaration forms agree; validated against {VALIDATED_AGAINST}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|