Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
407 lines
17 KiB
Python
407 lines
17 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 version of the
|
||
standard or its companion in any key or value (A12 r2, GH-DEC-2026-020 §1–§2):
|
||
not only a key named `standard_version`, but `companion_version`, another
|
||
key naming a standard/companion version, and a version-bearing path or file name such as a `standard:` of
|
||
`…security-layer-model_v0.7.md`. Comments are not read (YAML drops them) and
|
||
`schema_version` is the sidecar's own schema, so neither is reached. Only
|
||
INTENT.md frontmatter and layer.yaml are read for A12: stance, claims and
|
||
classification maps are not declarations and this run never applies A12 to them
|
||
(GH-DEC-2026-020 §3).
|
||
|
||
A bare revision citation in a prose value — `layer.yaml` cites "v0.6 §13" as
|
||
provenance in a gap record — is reported on every run, not failed. A12 r3
|
||
excludes prose provenance from declaration pins.
|
||
|
||
Every run prints, first, the standard version this check was built and validated
|
||
against (VALIDATED_AGAINST) and the scope it ranges over (SCOPE), including runs
|
||
that fail (GH-DEC-2026-020 §4). A run is sufficient; nothing durable is emitted.
|
||
|
||
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.8.md "
|
||
"(accepted-1 sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)"
|
||
)
|
||
|
||
# The scope every run ranges over, printed on every run (GH-DEC-2026-020 §4, A11).
|
||
# This is a single-repository run, not an estate run: it grades kings-guard, a §4
|
||
# row, and nothing else.
|
||
SCOPE = (
|
||
"kings-guard only (§4 row, Staff): INTENT.md frontmatter, layer.yaml, "
|
||
"src/kings_guard, repository tree for credential files"
|
||
)
|
||
|
||
# A12 r3 detection. A key naming a standard/companion version is a pin; the sidecar's own
|
||
# schema_version. A value naming the standard or companion with a version, or any
|
||
# versioned file name, is a pin. In identity-bearing keys any version token is a
|
||
# pin. A bare "vN.N" elsewhere in a value is a citation: reported, not failed.
|
||
A12_EXEMPT_KEYS = {"schema_version"}
|
||
A12_IDENTITY_KEYS = {"standard", "companion"}
|
||
A12_VERSION_KEY = re.compile(r"(standard|companion).*version|version.*(standard|companion)", re.I)
|
||
A12_PIN_VALUE = re.compile(r"[_\-.]v\d+(\.\d+)*(\.md)?\b|@v?\d+\.\d+", re.I)
|
||
A12_VERSION_TOKEN = re.compile(r"\bv?\d+\.\d+(?:\.\d+)*\b")
|
||
A12_CITATION = re.compile(r"\bv\d+\.\d+\b")
|
||
|
||
# §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 a12_findings(node: object, where: str) -> tuple[list[str], list[str]]:
|
||
"""Every key and value of a declaration form, walked (A12 r2).
|
||
|
||
Returns (pins, citations). Pins fail the run; citations are reported.
|
||
"""
|
||
pins: list[str] = []
|
||
citations: list[str] = []
|
||
|
||
def walk(value: object, path: str, key: str | None) -> None:
|
||
if isinstance(value, dict):
|
||
for k, v in value.items():
|
||
k_str = str(k)
|
||
sub = f"{path}.{k_str}" if path else k_str
|
||
if A12_VERSION_KEY.search(k_str) and k_str not in A12_EXEMPT_KEYS:
|
||
pins.append(f"{where}: key '{sub}' carries a version")
|
||
if k_str in A12_EXEMPT_KEYS:
|
||
continue
|
||
walk(v, sub, k_str)
|
||
return
|
||
if isinstance(value, list):
|
||
for i, v in enumerate(value):
|
||
walk(v, f"{path}[{i}]", key)
|
||
return
|
||
if not isinstance(value, str):
|
||
return
|
||
text = str(value)
|
||
if A12_PIN_VALUE.search(text) or (
|
||
key and key.lower() in A12_IDENTITY_KEYS and A12_VERSION_TOKEN.search(text)
|
||
):
|
||
pins.append(f"{where}: '{path}' = {text!r} names a versioned standard")
|
||
elif A12_CITATION.search(text):
|
||
match = A12_CITATION.search(text).group(0)
|
||
citations.append(f"{where}: '{path}' cites {match} in prose")
|
||
|
||
walk(node, "", None)
|
||
return pins, citations
|
||
|
||
|
||
def _refuse_pins(pins: list[str]) -> None:
|
||
if pins:
|
||
_malformed(
|
||
"a layer declaration MUST NOT carry a standard or companion version "
|
||
"in any key or value (§11 as amended by A12 r2, GH-DEC-2026-020 §1–§2):\n "
|
||
+ "\n ".join(pins)
|
||
)
|
||
|
||
|
||
def _malformed(message: str) -> None:
|
||
print(f"FAIL: {message}", file=sys.stderr)
|
||
raise SystemExit(2)
|
||
|
||
|
||
def load_intent_frontmatter() -> dict:
|
||
"""INTENT.md's frontmatter, parsed; the governing declaration form (§11)."""
|
||
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 not isinstance(front, dict):
|
||
_malformed(f"{INTENT.name} frontmatter is not a mapping")
|
||
return front
|
||
|
||
|
||
def load_governing_layer() -> str:
|
||
"""The declaration: INTENT.md frontmatter `layer:` (§11, GH-DEC-2026-017 §1)."""
|
||
front = load_intent_frontmatter()
|
||
if "layer" not in front:
|
||
_malformed(f"{INTENT.name} frontmatter has no 'layer' key — §11's declaration")
|
||
_refuse_pins(a12_findings(front, f"{INTENT.name} frontmatter")[0])
|
||
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"
|
||
)
|
||
_refuse_pins(a12_findings(data, DECL.name)[0])
|
||
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()
|
||
|
||
# Every run, pass or fail, states what it checks against and over what
|
||
# (GH-DEC-2026-020 §4). Printed before anything can exit.
|
||
print(f"checking against: {VALIDATED_AGAINST}")
|
||
print(f"scope: {SCOPE}")
|
||
|
||
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)})")
|
||
|
||
citations = (
|
||
a12_findings(load_intent_frontmatter(), f"{INTENT.name} frontmatter")[1]
|
||
+ a12_findings(decl, DECL.name)[1]
|
||
)
|
||
for citation in citations:
|
||
print(f"NOTE (A12 r3 provenance, reported not failed): {citation}")
|
||
|
||
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); "
|
||
"declaration forms agree; no standard or companion version in "
|
||
f"either form; validated against {VALIDATED_AGAINST}; scope: {SCOPE}"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|