Apply GH-DEC-2026-020: the checker prints its version and scope, and detects a version anywhere
check_layer_conformance.py now carries VALIDATED_AGAINST and SCOPE and prints both on every run, the OK line included (kings-guard's pattern, ruling §4). A12 detection widens from the key `standard_version` to any version in any key or value of the INTENT.md frontmatter and layer.yaml: a *version* key, a version-bearing path, or a version in standard/companion/framework (ruling §1-§2). schema_version, comments and stance/claims files are not reached (§1, §3). The declaration itself was already conforming; unchanged. Tests fail if a versioned `standard:` path or a companion_version comes back. 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
This commit is contained in:
parent
3d71ceecd4
commit
2ef97c87a0
2 changed files with 135 additions and 16 deletions
|
|
@ -9,11 +9,17 @@ This is an Engine (PIP). The checkable claims:
|
||||||
- both layer values are in §3's closed four-token vocabulary and are compared
|
- both layer values are in §3's closed four-token vocabulary and are compared
|
||||||
after an ASCII case fold (A9); nothing is re-spelled. A disagreement that
|
after an ASCII case fold (A9); nothing is re-spelled. A disagreement that
|
||||||
survives the fold is reported as a finding, not resolved by precedence (A11)
|
survives the fold is reported as a finding, not resolved by precedence (A11)
|
||||||
- neither form carries a standard_version (A12)
|
- neither form carries a standard or companion version in any key or value,
|
||||||
|
including a version-bearing `standard:` or `companion:` path (A12 r2,
|
||||||
|
GH-DEC-2026-020 §1-§2). Comments and `schema_version` are not reached, and
|
||||||
|
stance/claims/classification files are out of scope (GH-DEC-2026-020 §3)
|
||||||
- no pep_stance path
|
- no pep_stance path
|
||||||
- no catalogued Tooling client (OpenBao, key-cape, cluster)
|
- no catalogued Tooling client (OpenBao, key-cape, cluster)
|
||||||
- sqlite3 is this PIP's own store and is allowed
|
- sqlite3 is this PIP's own store and is allowed
|
||||||
|
|
||||||
|
Every run prints the standard text it checks against (VALIDATED_AGAINST) and
|
||||||
|
the scope it ranged over (GH-DEC-2026-020 §4: the version belongs to the run).
|
||||||
|
|
||||||
Exit 0 clean, 1 undeclared Tooling contact, 2 declaration malformed.
|
Exit 0 clean, 1 undeclared Tooling contact, 2 declaration malformed.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -26,6 +32,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
# The standard text this checker was built and validated against, printed on
|
||||||
|
# every run (GH-DEC-2026-020 §4; kings-guard's pattern). A declaration MUST NOT
|
||||||
|
# carry this (A12); the run does. Bump it when re-validated against a newer
|
||||||
|
# accepted text.
|
||||||
|
VALIDATED_AGAINST = (
|
||||||
|
"net-kingdom/canon/standards/security-layer-model_v0.7.md"
|
||||||
|
" (+ GH-DEC-2026-017, GH-DEC-2026-020 / A12 r2)"
|
||||||
|
)
|
||||||
|
|
||||||
# §3 as amended (A9): closed, four tokens, compared after an ASCII case fold.
|
# §3 as amended (A9): closed, four tokens, compared after an ASCII case fold.
|
||||||
LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
|
LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
|
||||||
|
|
||||||
|
|
@ -40,6 +55,62 @@ SRC = ROOT / "src" / "maturity_engine"
|
||||||
DECL = ROOT / "layer.yaml"
|
DECL = ROOT / "layer.yaml"
|
||||||
INTENT = ROOT / "INTENT.md"
|
INTENT = ROOT / "INTENT.md"
|
||||||
|
|
||||||
|
# What this run ranges over. Declaration files only: stance, claims and
|
||||||
|
# evidence-classification maps are deliberately NOT in scope (GH-DEC-2026-020 §3).
|
||||||
|
SCOPE = "INTENT.md frontmatter, layer.yaml, imports under src/maturity_engine"
|
||||||
|
|
||||||
|
# A12 r2: a version anywhere in the declaration, not only a key literally named
|
||||||
|
# `standard_version`. `schema_version` is the file format's own version, not a
|
||||||
|
# standard version, and is not reached (GH-DEC-2026-020 §1).
|
||||||
|
EXEMPT_VERSION_KEYS = {"schema_version"}
|
||||||
|
# A version-bearing path or file name, e.g. `security-layer-model_v0.7.md`,
|
||||||
|
# `.../v0.2/companion.md`. Matched only against whitespace-free values, so prose
|
||||||
|
# notes citing a rule's history are not mistaken for a pin.
|
||||||
|
VERSIONED_PATH = re.compile(r"(?i)(?:^|[_\-/.])v\d+(?:\.\d+)*(?:$|[_\-/.])")
|
||||||
|
# Keys that name a standard or companion: any version-like token in their value counts.
|
||||||
|
STANDARD_KEYS = {"standard", "companion", "framework"}
|
||||||
|
BARE_VERSION = re.compile(r"\d+\.\d+")
|
||||||
|
|
||||||
|
|
||||||
|
def version_findings(data: object, where: str) -> list[str]:
|
||||||
|
"""Every place a standard or companion version appears in a declaration."""
|
||||||
|
found: list[str] = []
|
||||||
|
|
||||||
|
def walk(node: object, path: str, key: str | None) -> None:
|
||||||
|
if isinstance(node, dict):
|
||||||
|
for k, v in node.items():
|
||||||
|
ks = str(k)
|
||||||
|
if ks in EXEMPT_VERSION_KEYS:
|
||||||
|
continue
|
||||||
|
if "version" in ks.lower():
|
||||||
|
found.append(f"{where}: key '{path}{ks}' carries a version")
|
||||||
|
walk(v, f"{path}{ks}.", ks)
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for i, v in enumerate(node):
|
||||||
|
walk(v, f"{path}{i}.", key)
|
||||||
|
elif isinstance(node, str):
|
||||||
|
value = node.strip()
|
||||||
|
label = path.rstrip(".")
|
||||||
|
if key and key.lower() in STANDARD_KEYS and BARE_VERSION.search(value):
|
||||||
|
found.append(f"{where}: '{label}' value {value!r} carries a version")
|
||||||
|
elif value and not any(c.isspace() for c in value) and VERSIONED_PATH.search(value):
|
||||||
|
found.append(f"{where}: '{label}' value {value!r} is a version-bearing path")
|
||||||
|
|
||||||
|
walk(data, "", None)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _fail_versions(findings: list[str]) -> None:
|
||||||
|
print(
|
||||||
|
"FAIL: a layer declaration MUST NOT carry a standard or companion version, "
|
||||||
|
"in any key or value (GH-DEC-2026-017 §5, GH-DEC-2026-020, A12 r2)",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
for line in findings:
|
||||||
|
print(f" {line}", file=sys.stderr)
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
|
||||||
TOOLING_IMPORTS = {
|
TOOLING_IMPORTS = {
|
||||||
"hvac": "OpenBao / Vault client",
|
"hvac": "OpenBao / Vault client",
|
||||||
"bao": "OpenBao client",
|
"bao": "OpenBao client",
|
||||||
|
|
@ -75,13 +146,9 @@ def load_declaration() -> dict:
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
if "standard_version" in data:
|
findings = version_findings(data, DECL.name)
|
||||||
print(
|
if findings:
|
||||||
f"FAIL: {DECL.name} carries 'standard_version' — a layer declaration MUST NOT "
|
_fail_versions(findings)
|
||||||
"carry a standard version (GH-DEC-2026-017 §5, A12)",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise SystemExit(2)
|
|
||||||
if _fold(data["layer"]) not in LAYER_VOCABULARY:
|
if _fold(data["layer"]) not in LAYER_VOCABULARY:
|
||||||
print(
|
print(
|
||||||
f"FAIL: {DECL.name} layer {data['layer']!r} is outside §3's closed vocabulary "
|
f"FAIL: {DECL.name} layer {data['layer']!r} is outside §3's closed vocabulary "
|
||||||
|
|
@ -117,13 +184,9 @@ def intent_frontmatter() -> dict:
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
if "standard_version" in meta:
|
findings = version_findings(meta, "INTENT.md frontmatter")
|
||||||
print(
|
if findings:
|
||||||
"FAIL: INTENT.md frontmatter carries 'standard_version' — a layer declaration "
|
_fail_versions(findings)
|
||||||
"MUST NOT carry a standard version (GH-DEC-2026-017 §5, A12)",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise SystemExit(2)
|
|
||||||
if _fold(meta["layer"]) not in LAYER_VOCABULARY:
|
if _fold(meta["layer"]) not in LAYER_VOCABULARY:
|
||||||
print(
|
print(
|
||||||
f"FAIL: INTENT.md layer {meta['layer']!r} is outside §3's closed vocabulary "
|
f"FAIL: INTENT.md layer {meta['layer']!r} is outside §3's closed vocabulary "
|
||||||
|
|
@ -162,6 +225,9 @@ def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--report", action="store_true")
|
parser.add_argument("--report", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
# GH-DEC-2026-020 §4: every run states what it checks against and its scope,
|
||||||
|
# on stdout, before any verdict — so a failing run carries it too.
|
||||||
|
print(f"check_layer_conformance: validated against {VALIDATED_AGAINST}; scope: {SCOPE}")
|
||||||
|
|
||||||
decl = load_declaration()
|
decl = load_declaration()
|
||||||
intent = intent_frontmatter()
|
intent = intent_frontmatter()
|
||||||
|
|
@ -197,8 +263,13 @@ def main() -> int:
|
||||||
print(f" tooling contacts: {len(decl.get('tooling_contacts') or [])}")
|
print(f" tooling contacts: {len(decl.get('tooling_contacts') or [])}")
|
||||||
print(f" non-tooling clients: {len(decl.get('non_tooling_clients') or [])}")
|
print(f" non-tooling clients: {len(decl.get('non_tooling_clients') or [])}")
|
||||||
print(" pep_stance: none")
|
print(" pep_stance: none")
|
||||||
|
print(f" checker validated against: {VALIDATED_AGAINST}")
|
||||||
|
print(f" scope: {SCOPE}")
|
||||||
else:
|
else:
|
||||||
print(f"OK: Engine/PIP declaration holds; no catalogued Tooling client in {SRC.relative_to(ROOT)}")
|
print(
|
||||||
|
f"OK: Engine/PIP declaration holds; no catalogued Tooling client in "
|
||||||
|
f"{SRC.relative_to(ROOT)}; validated against {VALIDATED_AGAINST}; scope: {SCOPE}"
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,51 @@ def test_checker_catches_an_openbao_client(tmp_path, monkeypatch):
|
||||||
hits = module.scan()
|
hits = module.scan()
|
||||||
assert hits
|
assert hits
|
||||||
assert hits[0][1] == "hvac"
|
assert hits[0][1] == "hvac"
|
||||||
|
|
||||||
|
|
||||||
|
def _checker():
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("check_layer_conformance_v", SCRIPT)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_run_prints_the_version_and_scope():
|
||||||
|
"""GH-DEC-2026-020 §4: the version belongs to the run, OK line included."""
|
||||||
|
module = _checker()
|
||||||
|
for args in ((), ("--report",)):
|
||||||
|
out = _run(*args).stdout
|
||||||
|
assert module.VALIDATED_AGAINST in out
|
||||||
|
assert module.SCOPE in out
|
||||||
|
ok = [line for line in _run().stdout.splitlines() if line.startswith("OK:")]
|
||||||
|
assert ok and module.VALIDATED_AGAINST in ok[0] and module.SCOPE in ok[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_version_anywhere_in_either_declaration():
|
||||||
|
"""A12 r2: no standard or companion version in any key or value."""
|
||||||
|
module = _checker()
|
||||||
|
assert module.version_findings(_intent_frontmatter(), "INTENT.md") == []
|
||||||
|
assert module.version_findings(yaml.safe_load(DECL.read_text()), "layer.yaml") == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"declaration",
|
||||||
|
[
|
||||||
|
{"standard": "net-kingdom/canon/standards/security-layer-model_v0.7.md"},
|
||||||
|
{"companion": "net-kingdom/v0.2/SECURITY-COMPANION.md"},
|
||||||
|
{"companion_version": "0.2"},
|
||||||
|
{"standard_version": "0.7"},
|
||||||
|
{"framework": "netkingdom-security-layer-model 0.8"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_checker_catches_a_version_beyond_the_key_name(declaration):
|
||||||
|
"""Fails if a versioned `standard:` path or a companion_version comes back."""
|
||||||
|
assert _checker().version_findings(declaration, "x")
|
||||||
|
|
||||||
|
|
||||||
|
def test_schema_version_and_prose_are_not_reached():
|
||||||
|
module = _checker()
|
||||||
|
assert module.version_findings({"schema_version": "0.1"}, "x") == []
|
||||||
|
assert module.version_findings({"note": "Outside §5 by the v0.5 scope rule"}, "x") == []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue