Verified against gate-house's own committed files before editing, not the inbox message: GH-DEC-2026-017 in decisions/decisions.md at gate-house@def0af2, amendments A9-A13 in docs/amendments/v0.8-section-11-declaration-amendments.md, and sections 3, 4 and 11 of net-kingdom's security-layer-model_v0.8.md. The ruling and docs/layer-declaration-precedence.md's secondary account agreed. INTENT.md's frontmatter is the declaration; layer.yaml is a derived artifact, now marked derived: true / derived_from: INTENT.md, and it does not govern. standard_version is removed from BOTH forms. The ruling's general form is that a layer declaration must not carry a standard version, and INTENT.md is the declaration, so removing it from the sidecar alone would have left the field in the only file that actually declares. INTENT.md's version-pinned `standard:` path is de-versioned for the same reason: a pinned path reads as a validity condition. The version ops-warden assented at stays with the assent, ADR-0010. NO LAYER VALUE IS CHANGED. INTENT.md still says Staff and layer.yaml still says staff. Section 3's vocabulary is closed, four tokens, and case-insensitive: the two forms were never in disagreement about a layer, and the ruling asked nobody to re-spell anything. The comment marking the divergence is rewritten from "unruled, do not touch" to "ruled, folding case is the checker's job". check_layer_conformance.py would have rejected the conforming declaration this ruling produces -- it listed standard_version as a required key. It now reads INTENT.md as the governing form, ASCII-folds before comparing, validates both values against the closed four-token vocabulary (Taxonomy included; omitting it is the defect A9 records against the estate's other validator), requires the derived marking, rejects a returning standard_version in either file, and reports a post-fold disagreement between the forms as a finding rather than resolving it away by precedence. The test asserts the fold, not equality. An equality assertion here would be this repository quietly performing the re-spelling the ruling declined to order; the fold still fails on a real layer divergence. pep-stance.yaml is untouched. A stance map is not a layer declaration, and the sidecar schema beyond the derived marking and the version is explicitly not ruled. layer.yaml is the form seven repositories copied, so the adopter change set is written out in wiki/playbooks/netkingdom-layer-declaration.md -- including the trap that an adopter which also copied the checker turns a conforming declaration into MALFORMED exit 2 by removing the field alone. No other repository is edited here. Still open: where the removed version lives. A12 says the derived conformance record "already MUST" carry it; ops-warden has a re-runnable checker that emits nothing durable. Asked of gate-house in message 4220413a, unanswered, and left open rather than answered by choosing. Nothing above depends on it. Carries WARDEN-WP-0034-T06 to done. 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
268 lines
12 KiB
Python
268 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""Check ops-warden against the NetKingdom security layer model (§5, §11).
|
|
|
|
Read-only. Two declaration forms are read, and the precedence between them is
|
|
`GH-DEC-2026-017` §1 (amendment A11): **INTENT.md's frontmatter governs** and
|
|
`layer.yaml` is a derived artifact that must be marked derived, must name
|
|
INTENT.md as its source, and must agree with it. The sidecar is read anyway,
|
|
because a disagreement between the two is itself a finding and is reported
|
|
rather than resolved away by precedence.
|
|
|
|
Comparison against §3's closed four-token vocabulary ASCII-folds case before
|
|
comparing (`GH-DEC-2026-017` §2, amendment A9): two spellings of a token do not
|
|
describe two boundaries, and a check that reports findings about capital letters
|
|
buries the one real disagreement it exists to find.
|
|
|
|
Neither form carries a `standard_version`, and their absence is enforced here
|
|
and by tests (`GH-DEC-2026-017` §5, amendment A12).
|
|
|
|
Makes §11's second mechanical check real:
|
|
|
|
every direct Tooling client in a Staff repository maps to a declared
|
|
§5.1, §5.2, or §5.3 entry
|
|
|
|
The failure this catches is a *new* direct OpenBao contact appearing in
|
|
src/warden/ without an entry in layer.yaml — an undeclared violation (§11),
|
|
which is a finding rather than a tracked gap. It deliberately does NOT check
|
|
the review dates: a date-triggered failure breaks the build on a calendar day
|
|
with no code change (the reasoning recorded in WARDEN-WP-0033-T05), so
|
|
staleness is reported and left to `--report`, never to CI.
|
|
|
|
Exit 0 clean, 1 undeclared contact found, 2 declaration malformed.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SRC = ROOT / "src" / "warden"
|
|
INTENT = ROOT / "INTENT.md"
|
|
DECL = ROOT / "layer.yaml"
|
|
|
|
VALID_SHAPES = {"5.1", "5.2", "5.3"}
|
|
|
|
# §3's vocabulary, closed, four tokens (GH-DEC-2026-017 §3, amendment A9). The
|
|
# canonical spellings are §4's catalog-column forms; comparison is ASCII
|
|
# case-insensitive, so the fold is what is stored and `Taxonomy` is in the set —
|
|
# omitting it is the defect A9 records against the estate's other validator.
|
|
LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
|
|
|
|
|
|
def _fold(value: str) -> str:
|
|
"""ASCII case-fold, per §3 as amended: two spellings of a token are one token."""
|
|
return str(value).strip().encode("ascii", "ignore").decode().lower()
|
|
|
|
|
|
def load_governing_layer() -> str:
|
|
"""The declaration is INTENT.md's frontmatter `layer:` key (§11, GH-DEC-2026-017 §1).
|
|
|
|
layer.yaml is derived and does not govern. Read without a YAML frontmatter
|
|
dependency: the file is read by humans first and a parser second.
|
|
"""
|
|
if not INTENT.exists():
|
|
print(f"MISSING: {INTENT} — §11's governing declaration form")
|
|
raise SystemExit(2)
|
|
lines = INTENT.read_text().splitlines()
|
|
if not lines or lines[0].strip() != "---":
|
|
print("MALFORMED: INTENT.md has no frontmatter to carry the declaration (§11)")
|
|
raise SystemExit(2)
|
|
end = next((i for i, ln in enumerate(lines[1:], 1) if ln.strip() == "---"), None)
|
|
if end is None:
|
|
print("MALFORMED: INTENT.md frontmatter is not terminated")
|
|
raise SystemExit(2)
|
|
front = yaml.safe_load("\n".join(lines[1:end])) or {}
|
|
if "layer" not in front:
|
|
print("MALFORMED: INTENT.md frontmatter has no 'layer' key — §11's declaration")
|
|
raise SystemExit(2)
|
|
if "standard_version" in front:
|
|
print(
|
|
"MALFORMED: INTENT.md frontmatter carries 'standard_version' — a layer "
|
|
"declaration MUST NOT carry a standard version (§11 as amended by A12)"
|
|
)
|
|
raise SystemExit(2)
|
|
layer = front["layer"]
|
|
if _fold(layer) not in LAYER_VOCABULARY:
|
|
print(
|
|
f"MALFORMED: INTENT.md declares layer {layer!r}, outside §3's closed "
|
|
f"vocabulary {sorted(LAYER_VOCABULARY)} (case-insensitive)"
|
|
)
|
|
raise SystemExit(2)
|
|
return layer
|
|
|
|
# A direct Tooling contact is an *invocation*, not a mention. Matching the word
|
|
# "bao" caught help text, a docstring, and the dev-tier doubles library that
|
|
# simulates bao rather than calling it — three false positives on first run.
|
|
# So match the two shapes that actually execute:
|
|
# 1. an HTTP request built against the OpenBao address
|
|
# 2. an argv list whose first element is the bao binary
|
|
TOOLING_PATTERNS = (
|
|
# httpx call whose URL is built from the configured OpenBao/Vault address
|
|
re.compile(r"""\bhttpx\.\w+\(|url\s*=\s*f?["'].*\{self\._cfg\.addr\}"""),
|
|
# argv construction: [bao_bin, ...] / ["bao", ...] / [bao_binary, ...]
|
|
re.compile(r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b)\s*,"""),
|
|
)
|
|
|
|
# httpx alone is not a Tooling contact — policy.py calls an Engine and worker.py
|
|
# calls the State Hub. A module matching only the httpx pattern counts as a
|
|
# contact only if it also references the OpenBao address configuration.
|
|
ADDR_HINT = re.compile(r"""_cfg\.addr|VAULT_ADDR|BAO_ADDR""")
|
|
|
|
# Modules that talk to an Engine or to something outside the §4 catalog. Listed
|
|
# in layer.yaml under non_tooling_clients and excluded from the scan with it.
|
|
def _excluded(decl: dict) -> set[str]:
|
|
return {e["module"].split("/")[-1] for e in decl.get("non_tooling_clients", [])}
|
|
|
|
|
|
def load_declaration() -> dict:
|
|
if not DECL.exists():
|
|
print(f"MISSING: {DECL} — ops-warden must declare in its own voice (§11)")
|
|
raise SystemExit(2)
|
|
decl = yaml.safe_load(DECL.read_text())
|
|
for key in ("layer", "repository", "derived", "derived_from", "tooling_contacts"):
|
|
if key not in decl:
|
|
print(f"MALFORMED: layer.yaml has no {key!r}")
|
|
raise SystemExit(2)
|
|
# §11's derived-artifact rule: marked as derived, naming what it derives from.
|
|
if decl["derived"] is not True:
|
|
print("MALFORMED: layer.yaml must be marked 'derived: true' (§11, GH-DEC-2026-017 §1)")
|
|
raise SystemExit(2)
|
|
if decl["derived_from"] != "INTENT.md":
|
|
print(
|
|
f"MALFORMED: layer.yaml derives from {decl['derived_from']!r}; §11 names "
|
|
"INTENT.md as the governing declaration"
|
|
)
|
|
raise SystemExit(2)
|
|
# A12: the version has no home in a declaration, governing or derived.
|
|
if "standard_version" in decl:
|
|
print(
|
|
"MALFORMED: layer.yaml carries 'standard_version' — a layer declaration "
|
|
"MUST NOT carry a standard version (§11 as amended by A12)"
|
|
)
|
|
raise SystemExit(2)
|
|
if _fold(decl["layer"]) not in LAYER_VOCABULARY:
|
|
print(
|
|
f"MALFORMED: layer.yaml declares layer {decl['layer']!r}, outside §3's "
|
|
f"closed vocabulary {sorted(LAYER_VOCABULARY)} (case-insensitive)"
|
|
)
|
|
raise SystemExit(2)
|
|
for c in decl["tooling_contacts"]:
|
|
if c.get("shape") not in VALID_SHAPES:
|
|
print(f"MALFORMED: {c.get('id')} has shape {c.get('shape')!r}, not one of {sorted(VALID_SHAPES)}")
|
|
raise SystemExit(2)
|
|
# §5.3 carries four fields, machine-readably. That is the whole point of
|
|
# the shape; a gap missing them is prose wearing a schema.
|
|
if c["shape"] == "5.3":
|
|
for field in ("capability", "intended_owner", "blocked_on", "review"):
|
|
if not c.get(field):
|
|
print(f"MALFORMED: §5.3 entry {c['id']!r} is missing {field!r}")
|
|
raise SystemExit(2)
|
|
# §5.2's test is the supplied-authority property.
|
|
if c["shape"] == "5.2" and c.get("supplied_authority") != "none":
|
|
print(f"MALFORMED: §5.2 conduit {c['id']!r} must declare supplied_authority: none")
|
|
raise SystemExit(2)
|
|
return decl
|
|
|
|
|
|
def scan_modules() -> dict[str, list[int]]:
|
|
"""Return {module_name: [line numbers]} for direct Tooling contacts."""
|
|
found: dict[str, list[int]] = {}
|
|
for path in sorted(SRC.rglob("*.py")):
|
|
if path.name.startswith("test_"):
|
|
continue
|
|
text = path.read_text()
|
|
hits: list[int] = []
|
|
for n, line in enumerate(text.splitlines(), 1):
|
|
stripped = line.strip()
|
|
if stripped.startswith("#") or stripped.startswith('"'):
|
|
continue
|
|
if any(p.search(line) for p in TOOLING_PATTERNS):
|
|
hits.append(n)
|
|
if hits:
|
|
# An httpx-only match needs the OpenBao address to be a Tooling
|
|
# contact; otherwise it is an Engine or non-catalogued call.
|
|
argv_shape = any(TOOLING_PATTERNS[1].search(ln) for ln in text.splitlines())
|
|
if argv_shape or ADDR_HINT.search(text):
|
|
found[path.name] = hits
|
|
return found
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--report", action="store_true", help="also print the declaration and gap review dates")
|
|
args = ap.parse_args()
|
|
|
|
governing = load_governing_layer()
|
|
decl = load_declaration()
|
|
declared = {c["module"].split("/")[-1] for c in decl["tooling_contacts"]}
|
|
excluded = _excluded(decl)
|
|
found = scan_modules()
|
|
|
|
undeclared = {m: lines for m, lines in found.items() if m not in declared and m not in excluded}
|
|
# A voluntary declaration has no fixed argv shape to detect (an
|
|
# operator-configured command). Over-declaring is safe; not reporting it as
|
|
# stale keeps the signal meaningful.
|
|
voluntary = {
|
|
c["module"].split("/")[-1]
|
|
for c in decl["tooling_contacts"]
|
|
if c.get("detection") == "voluntary"
|
|
}
|
|
stale_decls = declared - set(found) - voluntary
|
|
|
|
if args.report:
|
|
print(f"{decl['repository']} — layer: {governing} (declared in INTENT.md; §11 governing form)")
|
|
print(f" layer.yaml: derived from {decl['derived_from']}, layer: {decl['layer']}")
|
|
print(f"declared by {decl['declared_by']}\n")
|
|
for c in decl["tooling_contacts"]:
|
|
line = f" §{c['shape']} {c['id']:<28} {c['module']}"
|
|
if c["shape"] == "5.3":
|
|
overdue = str(c["review"]) < date.today().isoformat()
|
|
line += f" -> {c['intended_owner']} review {c['review']}"
|
|
if overdue:
|
|
line += " [REVIEW OVERDUE]"
|
|
print(line)
|
|
gaps = [c for c in decl["tooling_contacts"] if c["shape"] == "5.3"]
|
|
print(f"\n{len(gaps)} declared gap(s) — tracked non-conformance, not conformance (§11).")
|
|
|
|
ok = True
|
|
|
|
# §11 as amended (A11): the derived form MUST agree with the governing one,
|
|
# and a disagreement is a finding in its own right — reported, not resolved
|
|
# away by precedence. Comparison folds case (A9): `Staff` and `staff` agree.
|
|
if _fold(decl["layer"]) != _fold(governing):
|
|
ok = False
|
|
print(
|
|
"\nDECLARATION DISAGREEMENT — a finding under §11, not a precedence question:\n"
|
|
f" INTENT.md (governs): layer: {governing}\n"
|
|
f" layer.yaml (derived): layer: {decl['layer']}\n"
|
|
"Precedence says which value is ops-warden's answer. It does not say the\n"
|
|
"disagreement did not happen. Case is already folded, so this is a real\n"
|
|
"disagreement about a layer, not about orthography."
|
|
)
|
|
|
|
if undeclared:
|
|
ok = False
|
|
print("\nUNDECLARED TOOLING CONTACT — a finding under §11, not a tracked gap:")
|
|
for m, lines in sorted(undeclared.items()):
|
|
print(f" src/warden/{m}: line(s) {', '.join(map(str, lines[:6]))}")
|
|
print("\nAdd a §5.1/§5.2/§5.3 entry to layer.yaml, or route it through an engine.")
|
|
|
|
if stale_decls:
|
|
print("\nNote: declared but no contact found (module removed or refactored?):")
|
|
for m in sorted(stale_decls):
|
|
print(f" {m}")
|
|
|
|
if ok and not args.report:
|
|
print(f"PASS — {len(found)} module(s) with Tooling contact, all declared.")
|
|
elif ok:
|
|
print("\nPASS — every direct Tooling contact maps to a declared shape.")
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|