Declare layer machine-readably (§11); adopt v0.6 corrections
The standard moved v0.4 -> v0.6. All four findings from our v0.4 review were adopted in v0.5, and v0.6 went further on two of them. §11 now requires a machine-readable declaration — prose cannot distinguish a declaration from a transcribed review. We had none. Added layer.yaml (form adapted from ops-warden's reference implementation), scripts/check_layer_conformance.py, and tests/test_layer_conformance.py. The check makes our central claim mechanical rather than asserted: no direct Tooling client in src/. The test exercises the negative case on a synthetic tree, so it fails if the checker goes blind. pyyaml is added as a DEV dependency only — `dependencies = []` is load-bearing for the §5 claim and stays empty. Adopted from v0.6: - containment is no longer ours (§9.2). Actuation is an Engine concept, unowned and held at zero; kings-guard proposes containment and never performs it. The register row is now a dependency, not our gap. - observation is scoped to Staff-reachable sources, with identity and secret observation pending — our finding 1, adopted near-verbatim. - access-engine DECLINED the authentication-evidence gap; owner is now the identity layer plus audit-core, reproposed and unassented. - §11 blocked-clean recorded, with the rule that it must not rank below conforming — our finding 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UEtvmYUBP2fDtirJGWn5MW Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014379@bnt-lap001 Assistant-Session: 4af9e20f-1768-4afc-951b-b507784e382b
This commit is contained in:
parent
d99f395aa1
commit
72c2a42d67
9 changed files with 389 additions and 29 deletions
62
INTENT.md
62
INTENT.md
|
|
@ -1,35 +1,45 @@
|
|||
# INTENT
|
||||
|
||||
> **Layer: Staff.** *(NetKingdom Security Layer Model — current version
|
||||
> `net-kingdom/canon/standards/security-layer-model_v0.4.md`, §4 catalog,
|
||||
> status accepted; ratified by `gate-house/decisions/decisions.md`
|
||||
> GH-DEC-2026-001; assented here by `decisions/decisions.md` KG-DEC-2026-001
|
||||
> on 2026-08-28, against v0.1. This declaration is made in kings-guard's own
|
||||
> voice, per §11.)*
|
||||
> `net-kingdom/canon/standards/security-layer-model_v0.6.md`, §4 catalog;
|
||||
> ratified by `gate-house/decisions/decisions.md` GH-DEC-2026-001; assented here
|
||||
> by `decisions/decisions.md` KG-DEC-2026-001 on 2026-08-28, against v0.1.)*
|
||||
>
|
||||
> **Catalog entry (v0.4 §4):** adaptive defence, observation; **containment —
|
||||
> pending (§9.2)**, until an engine exposes a containment surface. The pending
|
||||
> mark exists because kings-guard raised the defect that v0.1 catalogued a
|
||||
> capability §5 forbade discharging; the general rule is now §9.1.
|
||||
> **The machine-readable declaration required by §11 is `layer.yaml`**, checked
|
||||
> by `scripts/check_layer_conformance.py` and tested in
|
||||
> `tests/test_layer_conformance.py`. Prose cannot distinguish a declaration from
|
||||
> a transcribed review, so the file is authoritative and this note is commentary.
|
||||
>
|
||||
> kings-guard is **interactive and non-deterministic**: adaptive defence,
|
||||
> observation, containment. Acting at runtime does not make a repository an
|
||||
> Engine; being agentic makes it Staff.
|
||||
> **Catalog entry (v0.6 §4):** adaptive defence and judgment; observation of
|
||||
> Staff-reachable sources — identity and secret observation **pending**;
|
||||
> **proposes** containment, which it does not own.
|
||||
>
|
||||
> kings-guard is **interactive and non-deterministic**. Acting at runtime does
|
||||
> not make a repository an Engine; being agentic makes it Staff.
|
||||
>
|
||||
> **The binding rule (§5): Staff never touches Tooling directly. It acts only
|
||||
> through Engine APIs.** kings-guard holds no direct client for a Tooling-layer
|
||||
> system — no database connection, no OpenBao client, no cluster mutation. It
|
||||
> may contain a threat only by calling an engine. Where no engine exposes a
|
||||
> capability kings-guard needs, that is raised as an **engine gap**, never
|
||||
> solved locally; the open gaps are listed under *System boundary* below.
|
||||
> system — no database connection, no OpenBao client, no cluster mutation, and
|
||||
> no §5.1 diagnostic read or §5.2 conduit either. Under §11 that is
|
||||
> **blocked-clean**: the capabilities needing such a contact sit at zero rather
|
||||
> than being taken locally, and §11 rules that this MUST NOT rank below
|
||||
> conforming.
|
||||
>
|
||||
> **Containment is not ours (§9.2).** v0.6 moved it off this repository
|
||||
> entirely: reduce authority, require step-up, and isolate a workload are
|
||||
> authority-changing operations, so they are rendered by an Engine and enforced
|
||||
> by a PEP. **kings-guard proposes containment; it never performs it.** The
|
||||
> actuation surface is unowned and held at zero estate-wide, so no argument
|
||||
> anywhere may assume containment is automatic.
|
||||
>
|
||||
> **Posture contract.** kings-guard **publishes** posture; `gate-house` defines
|
||||
> its authority meaning; `access-engine` renders it. Posture is not a privilege
|
||||
> source. The asymmetry is absolute: kings-guard may **reduce** authority,
|
||||
> **require step-up**, or **request containment**; it MUST NOT probabilistically
|
||||
> manufacture additional authority. Every effector request it emits therefore
|
||||
> carries an explicit authority boundary and is advisory unless the owning
|
||||
> system has already delegated a narrow, deterministic action lane.
|
||||
> manufacture additional authority. Every effector request carries an explicit
|
||||
> authority boundary. Under incomplete observation this asymmetry is what bounds
|
||||
> the damage: a suppressed event can only cost a tightening that should have
|
||||
> happened, never manufacture authority through us (§8, §9.6).
|
||||
|
||||
> This file captures **why this repository exists**, the **direction it is
|
||||
> moving toward**, and the **kind of system it is meant to become**.
|
||||
|
|
@ -151,16 +161,18 @@ rule these are gaps to close in the owning engine, not work to route around.
|
|||
None is a standing licence to reach into Tooling.
|
||||
|
||||
These are **unowned capabilities**, not §5.3 declared contacts: kings-guard
|
||||
makes no direct Tooling contact for any of them. Under §11 kings-guard is
|
||||
**Conforming**, not a tracked non-conformance. The fields follow §5.3's shape so
|
||||
one register can hold both, but the distinction is load-bearing — see the
|
||||
assessment sent to gate-house on 2026-08-29.
|
||||
makes no direct Tooling contact for any of them. v0.5 §11 added the
|
||||
**blocked-clean** state for exactly this case, on kings-guard's finding, and
|
||||
ruled that it MUST NOT rank below conforming.
|
||||
|
||||
`layer.yaml` is the authoritative machine-readable form; this table is the
|
||||
human-readable view of it.
|
||||
|
||||
| `capability` | Needed for | `intended_owner` | `blocked_on` | `review` |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Authentication and assurance evidence (token assurance, attestation outcomes, authentication anomalies) exposed as an engine surface | identity-drift posture | `user-engine` / `access-engine` | no engine surface exists; kings-guard consumes fixtures only | 2026-11-28 |
|
||||
| Authentication and assurance evidence (token assurance, attestation outcomes, authentication anomalies) exposed as an engine surface | identity-drift posture | identity layer + `audit-core` — **`access-engine` declined** (v0.6 §13); reproposed, not assented | no engine surface exists; kings-guard consumes fixtures only | 2026-11-28 |
|
||||
| Secret-use evidence (lease, revocation, mount and rotation metadata) exposed as an engine surface | secret-abuse posture | `secrets-engine` | no engine surface exists; kings-guard consumes fixtures only | 2026-11-28 |
|
||||
| Containment surface — reduce authority, require step-up, isolate a workload — callable as a deterministic engine API, available while an incident is in progress | bounded response | `access-engine`, runtime engines | no engine surface exists; ruled pending in v0.4 §9.2, degraded-mode fallback ruled into the engine by §9.3 | 2026-11-28 |
|
||||
| Actuation surface — reduce authority, require step-up, isolate a workload — as a deterministic engine API carrying a decision record | containment kings-guard **proposes but does not own** | `access-engine` + runtime PEPs; not reviewed (`FLEX-DEC-2026-002`) | ruled an Engine concept held at zero (v0.6 §9.2); recorded here as a dependency, not a kings-guard gap to close | 2026-11-28 |
|
||||
|
||||
Until a gap closes, the corresponding posture lane stays advisory and
|
||||
fixture-driven. kings-guard MUST NOT open a direct path to the Tooling system
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -1,6 +1,6 @@
|
|||
PYTHON ?= python3
|
||||
|
||||
.PHONY: install-dev test lint run-demo
|
||||
.PHONY: install-dev test lint run-demo check-layer
|
||||
|
||||
install-dev:
|
||||
$(PYTHON) -m pip install -e ".[dev]"
|
||||
|
|
@ -11,5 +11,10 @@ test:
|
|||
lint:
|
||||
$(PYTHON) -m ruff check src tests
|
||||
|
||||
# NetKingdom Security Layer Model §11 — makes the no-Tooling-client claim
|
||||
# checkable rather than asserted. See layer.yaml.
|
||||
check-layer:
|
||||
$(PYTHON) scripts/check_layer_conformance.py --report
|
||||
|
||||
run-demo:
|
||||
PYTHONPATH=src $(PYTHON) -m kings_guard.main --pilot qonto-assistant
|
||||
|
|
|
|||
2
SCOPE.md
2
SCOPE.md
|
|
@ -9,7 +9,7 @@
|
|||
**Staff** — interactive, non-deterministic; adaptive defence, observation,
|
||||
containment. Binding rule: kings-guard never touches Tooling directly; it acts
|
||||
only through Engine APIs. See `INTENT.md` and
|
||||
`net-kingdom/canon/standards/security-layer-model_v0.4.md`.
|
||||
`net-kingdom/canon/standards/security-layer-model_v0.6.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ The following rules apply to every integration below:
|
|||
4. `kings-guard` must preserve **tenant isolation**: any retained evidence or
|
||||
memory must stay bounded by declared confidentiality rules.
|
||||
5. `kings-guard` is a **Staff-layer** repository and is bound by §5 of the
|
||||
NetKingdom Security Layer Model v0.4: it never holds a direct client for a
|
||||
NetKingdom Security Layer Model v0.6: it never holds a direct client for a
|
||||
Tooling-layer system. Evidence from Tooling (OpenBao, key-cape components)
|
||||
is consumed **through the owning engine**. Where no engine surface exists,
|
||||
the lane stays fixture-driven and the gap is declared in `INTENT.md`.
|
||||
|
|
|
|||
108
layer.yaml
Normal file
108
layer.yaml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# kings-guard — NetKingdom security layer declaration
|
||||
#
|
||||
# Framework: net-kingdom/canon/standards/security-layer-model_v0.6.md
|
||||
# Assent: decisions/decisions.md KG-DEC-2026-001 (kings-guard's own voice, §11)
|
||||
# Validate: python3 scripts/check_layer_conformance.py
|
||||
#
|
||||
# §11 (v0.6) requires a machine-readable declaration: prose cannot distinguish a
|
||||
# declaration from a transcribed review. Form adapted from ops-warden's
|
||||
# reference implementation, offered under §11.
|
||||
#
|
||||
# kings-guard's position is unusual and this file is shaped to state it exactly:
|
||||
# there are NO Tooling contacts. Not a narrow one, not a read-only one. The
|
||||
# capabilities that would need them sit at zero instead. Under §11 that is
|
||||
# BLOCKED-CLEAN, which MUST NOT rank below conforming.
|
||||
|
||||
schema_version: "0.1"
|
||||
framework: netkingdom-security-layer-model
|
||||
standard_version: "0.6"
|
||||
repository: kings-guard
|
||||
layer: staff
|
||||
declared_by: decisions/decisions.md#KG-DEC-2026-001
|
||||
declared_at: "2026-08-29"
|
||||
|
||||
# §4 catalog entry, transcribed so drift between the catalog and this file is
|
||||
# visible. The standard is authoritative for the row; this records what we
|
||||
# understand ourselves to have been assigned.
|
||||
catalog_entry:
|
||||
owns:
|
||||
- adaptive defence and judgment
|
||||
- observation of Staff-reachable sources
|
||||
pending:
|
||||
- identity observation
|
||||
- secret observation
|
||||
proposes_but_does_not_own:
|
||||
# §9.2 — actuation is an Engine concept held at zero. kings-guard proposes
|
||||
# containment and never performs it. This is not our gap to close.
|
||||
- containment
|
||||
|
||||
# §5 / §11: every direct contact with a Tooling-layer system (a §4 Tooling row),
|
||||
# one entry each. Empty is a claim, and scripts/check_layer_conformance.py is
|
||||
# what makes it checkable rather than asserted.
|
||||
tooling_contacts: []
|
||||
|
||||
# §11 requires non-Tooling clients to be recorded "so the check is total".
|
||||
non_tooling_clients:
|
||||
- id: state-hub-work-records
|
||||
target: state-hub
|
||||
layer: not-catalogued
|
||||
operation: "HTTP to the Custodian State Hub for work records and progress events"
|
||||
write: true
|
||||
note: >-
|
||||
Outside §5 by the v0.5 scope rule: "Tooling-layer system" means a §4
|
||||
Tooling row, and state-hub is not one. Recorded, not policed. Carries no
|
||||
security authority and no secret payload.
|
||||
|
||||
# §11 blocked-clean. These are NOT §5.3 declared gaps: there is no contact to
|
||||
# declare. Fields follow the §5.3 shape so one register can hold both kinds
|
||||
# (§13 now carries the state column that keeps them distinct — raised by
|
||||
# kings-guard against v0.4).
|
||||
unowned_capabilities:
|
||||
|
||||
- id: authentication-assurance-evidence
|
||||
state: unowned-capability
|
||||
capability: >-
|
||||
Token assurance, attestation outcomes and authentication anomalies exposed
|
||||
as an engine surface, for identity-drift posture.
|
||||
intended_owner: "identity layer + audit-core"
|
||||
owner_status: "access-engine declined (v0.6 §13); reproposed, not assented"
|
||||
blocked_on: >-
|
||||
No engine exposes authentication evidence. key-cape is Tooling, so §5
|
||||
forbids the direct route, and the capability stays at zero rather than
|
||||
being taken locally.
|
||||
review: "2026-11-28"
|
||||
consequence: "identity-drift posture lane stays fixture-driven"
|
||||
|
||||
- id: secret-use-evidence
|
||||
state: unowned-capability
|
||||
capability: >-
|
||||
Lease, revocation, mount and rotation metadata exposed as an engine
|
||||
surface, for secret-abuse posture.
|
||||
intended_owner: secrets-engine
|
||||
owner_status: proposed
|
||||
blocked_on: >-
|
||||
No engine exposes secret-use evidence. OpenBao is Tooling; same reasoning
|
||||
as above.
|
||||
review: "2026-11-28"
|
||||
consequence: "secret-abuse posture lane stays fixture-driven"
|
||||
|
||||
- id: actuation-surface
|
||||
state: unowned-capability
|
||||
capability: >-
|
||||
Reduce authority, require step-up, isolate a workload — as a deterministic
|
||||
engine API carrying a decision record.
|
||||
intended_owner: "access-engine + runtime PEPs"
|
||||
owner_status: "proposed; access-engine has not reviewed (FLEX-DEC-2026-002)"
|
||||
blocked_on: >-
|
||||
Ruled in v0.6 §9.2 to be an Engine concept, unowned and held at zero.
|
||||
kings-guard proposes containment and does not own it, so this row is
|
||||
recorded here as a dependency, not as a kings-guard gap to close.
|
||||
review: "2026-11-28"
|
||||
consequence: "no containment is possible anywhere in the estate"
|
||||
|
||||
# §5.1 read-only diagnostic observation of Tooling: none declared, none taken.
|
||||
# §5.2 conduit: none. kings-guard runs no tool under a caller's identity.
|
||||
declared_shapes:
|
||||
"5.1": []
|
||||
"5.2": []
|
||||
"5.3": []
|
||||
|
|
@ -16,6 +16,10 @@ dependencies = []
|
|||
dev = [
|
||||
"pytest>=8.2,<9.0",
|
||||
"ruff>=0.6,<1.0",
|
||||
# Reads layer.yaml in scripts/check_layer_conformance.py. Deliberately a
|
||||
# DEV dependency: `dependencies = []` above is load-bearing for the §5
|
||||
# no-Tooling-client claim and must stay empty.
|
||||
"pyyaml>=6.0,<7.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
|
|
|||
143
scripts/check_layer_conformance.py
Normal file
143
scripts/check_layer_conformance.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#!/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.6) 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.
|
||||
|
||||
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, 2 declaration malformed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
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"
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
|
||||
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", "repository", "tooling_contacts", "standard_version"):
|
||||
if key not in data:
|
||||
print(f"FAIL: {DECL.name} missing required key '{key}' (§11)", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
if data["layer"] != "staff":
|
||||
print(f"FAIL: declared layer is '{data['layer']}', expected 'staff'", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
return data
|
||||
|
||||
|
||||
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 main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--report", action="store_true", help="print the declaration summary")
|
||||
args = parser.parse_args()
|
||||
|
||||
decl = load_declaration()
|
||||
declared = {c.get("id") for c in decl.get("tooling_contacts") or []}
|
||||
hits = scan()
|
||||
|
||||
undeclared = [h for h in hits if h[1] not in declared]
|
||||
|
||||
if args.report:
|
||||
print(f"kings-guard — layer {decl['layer']}, standard v{decl['standard_version']}")
|
||||
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}")
|
||||
|
||||
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 not args.report:
|
||||
print(f"OK: no direct Tooling client in {SRC.relative_to(ROOT)} (§5, §11)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -15,7 +15,7 @@ classification: Public
|
|||
# NetKingdom Immune Architecture
|
||||
|
||||
> **Layer note (2026-08-28).** kings-guard is a **Staff**-layer repository
|
||||
> under the NetKingdom Security Layer Model v0.4 (assented in
|
||||
> under the NetKingdom Security Layer Model v0.6 (assented in
|
||||
> `decisions/decisions.md` KG-DEC-2026-001). Where this document uses "control
|
||||
> plane", read it as naming an **estate-wide, Engine-layer** arrangement of
|
||||
> deterministic authorities — never as a self-description of `kings-guard`,
|
||||
|
|
|
|||
88
tests/test_layer_conformance.py
Normal file
88
tests/test_layer_conformance.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""kings-guard's layer declaration is checkable, not merely asserted (§11).
|
||||
|
||||
The claim under test is the one our whole position rests on: no direct
|
||||
Tooling-layer client. A test that only ran the checker against a clean tree
|
||||
would prove nothing — it would pass just as happily if the checker were broken.
|
||||
So the negative case is exercised too, on a synthetic tree.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
yaml = pytest.importorskip("yaml")
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / "scripts" / "check_layer_conformance.py"
|
||||
DECL = ROOT / "layer.yaml"
|
||||
|
||||
|
||||
def _run(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(SCRIPT), *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def test_declaration_exists_and_declares_staff():
|
||||
"""§11: an estate-authored repository declares its layer machine-readably."""
|
||||
assert DECL.exists(), "no layer.yaml — §11 requires a machine-readable declaration"
|
||||
data = yaml.safe_load(DECL.read_text())
|
||||
assert data["repository"] == "kings-guard"
|
||||
assert data["layer"] == "staff"
|
||||
assert data["framework"] == "netkingdom-security-layer-model"
|
||||
|
||||
|
||||
def test_no_tooling_contacts_declared():
|
||||
"""The blocked-clean position: nothing to declare, because nothing is touched."""
|
||||
data = yaml.safe_load(DECL.read_text())
|
||||
assert data["tooling_contacts"] == [], (
|
||||
"a Tooling contact appeared in the declaration; kings-guard's blocked-clean "
|
||||
"position under §11 no longer holds and KG-DEC-2026-001 needs revisiting"
|
||||
)
|
||||
for shape, entries in data["declared_shapes"].items():
|
||||
assert entries == [], f"§5.{shape} shape declared; see comment above"
|
||||
|
||||
|
||||
def test_unowned_capabilities_carry_the_gap_record_fields():
|
||||
"""§5.3 field shape, reused for §13 unowned-capability rows (§17 gap-record)."""
|
||||
data = yaml.safe_load(DECL.read_text())
|
||||
caps = data["unowned_capabilities"]
|
||||
assert caps, "the three known gaps should be declared"
|
||||
for cap in caps:
|
||||
for field in ("capability", "intended_owner", "blocked_on", "review", "state"):
|
||||
assert cap.get(field), f"{cap.get('id')} missing '{field}'"
|
||||
assert cap["state"] == "unowned-capability"
|
||||
|
||||
|
||||
def test_checker_passes_on_the_real_tree():
|
||||
result = _run()
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_checker_catches_an_undeclared_tooling_client(tmp_path, monkeypatch):
|
||||
"""The negative case: a direct OpenBao client must fail the check.
|
||||
|
||||
This is the convenience §6 warns about — it would arrive as one import.
|
||||
"""
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location("check_layer_conformance", SCRIPT)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
fake_src = tmp_path / "src" / "kings_guard"
|
||||
fake_src.mkdir(parents=True)
|
||||
(fake_src / "effector.py").write_text(
|
||||
"import hvac\n\n\ndef contain(actor):\n hvac.Client().revoke(actor)\n"
|
||||
)
|
||||
monkeypatch.setattr(module, "SRC", fake_src)
|
||||
monkeypatch.setattr(module, "ROOT", tmp_path)
|
||||
|
||||
hits = module.scan()
|
||||
assert hits, "a direct OpenBao client was not detected — the checker is blind"
|
||||
assert hits[0][1] == "hvac"
|
||||
Loading…
Add table
Add a link
Reference in a new issue