Apply GH-DEC-2026-020 to the layer conformance checker
The checker now prints VALIDATED_AGAINST and its scope on every run, including the OK line (kings-guard pattern). A12 detection widens from the key name standard_version to every key and value of INTENT.md frontmatter and layer.yaml: versioned standard:/companion: paths, companion_version, and any *_version key except schema_version. Comments and non-declaration files are not reached. Declaration unchanged; gate-house confirmed it conforms. 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
cd54d8e716
commit
9fd05d09fe
2 changed files with 147 additions and 12 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
import contextlib
|
||||||
|
import io
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
import unittest
|
import unittest
|
||||||
|
|
@ -66,6 +68,25 @@ class LayerDeclarationTest(unittest.TestCase):
|
||||||
self.assertNotIn("standard_version", decl)
|
self.assertNotIn("standard_version", decl)
|
||||||
self.assertNotIn("standard_version", layer.load_intent())
|
self.assertNotIn("standard_version", layer.load_intent())
|
||||||
|
|
||||||
|
def test_no_version_anywhere_in_either_form(self):
|
||||||
|
"""GH-DEC-2026-020 / A12 r2: no version in any key or value, not only the key name."""
|
||||||
|
decl = yaml.safe_load(layer.DECL.read_text())
|
||||||
|
self.assertEqual(layer.declaration_versions(decl), [])
|
||||||
|
self.assertEqual(layer.declaration_versions(layer.load_intent()), [])
|
||||||
|
self.assertNotIn("companion_version", decl)
|
||||||
|
self.assertNotIn("companion_version", layer.load_intent())
|
||||||
|
|
||||||
|
def test_every_run_prints_version_and_scope(self):
|
||||||
|
"""GH-DEC-2026-020 §4: the version lives in the run, including the OK line."""
|
||||||
|
out = io.StringIO()
|
||||||
|
with contextlib.redirect_stdout(out):
|
||||||
|
self.assertEqual(layer.main([]), 0)
|
||||||
|
text = out.getvalue()
|
||||||
|
self.assertIn(f"validated against: {layer.VALIDATED_AGAINST}", text)
|
||||||
|
self.assertIn(f"scope: {layer.SCOPE}", text)
|
||||||
|
ok = [line for line in text.splitlines() if line.startswith("OK:")]
|
||||||
|
self.assertTrue(ok and layer.VALIDATED_AGAINST in ok[0])
|
||||||
|
|
||||||
def test_vocabulary_is_the_closed_four_tokens(self):
|
def test_vocabulary_is_the_closed_four_tokens(self):
|
||||||
self.assertEqual(layer.LAYER_VOCABULARY, {"taxonomy", "tooling", "engine", "staff"})
|
self.assertEqual(layer.LAYER_VOCABULARY, {"taxonomy", "tooling", "engine", "staff"})
|
||||||
|
|
||||||
|
|
@ -158,3 +179,52 @@ class LayerCheckerFailureTest(unittest.TestCase):
|
||||||
joined = "\n".join(errors)
|
joined = "\n".join(errors)
|
||||||
self.assertIn("fastapi", joined)
|
self.assertIn("fastapi", joined)
|
||||||
self.assertIn("/v1/check", joined)
|
self.assertIn("/v1/check", joined)
|
||||||
|
|
||||||
|
def test_versioned_standard_path_is_exit_2(self):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
_write_tree(root, intent=INTENT.replace(
|
||||||
|
"standard: netkingdom-security-layer-model",
|
||||||
|
"standard: net-kingdom/canon/standards/security-layer-model_v0.7.md",
|
||||||
|
))
|
||||||
|
code, errors, _ = layer.evaluate(root=root)
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
self.assertTrue(any("standard" in item and "v0.7" in item for item in errors))
|
||||||
|
|
||||||
|
def test_companion_version_is_exit_2(self):
|
||||||
|
for decl, intent in (
|
||||||
|
({**DECL, "companion_version": "0.2"}, INTENT),
|
||||||
|
(DECL, INTENT.replace("role: PIP\n", 'role: PIP\ncompanion_version: "0.2"\n')),
|
||||||
|
(DECL, INTENT.replace("role: PIP\n", "role: PIP\ncompanion: net-kingdom/SECURITY-COMPANION.md v0.2\n")),
|
||||||
|
):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
_write_tree(root, decl=decl, intent=intent)
|
||||||
|
code, errors, _ = layer.evaluate(root=root)
|
||||||
|
self.assertEqual(code, 2, errors)
|
||||||
|
|
||||||
|
def test_versioned_path_nested_in_layer_yaml_is_exit_2(self):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
_write_tree(root, decl={**DECL, "catalog_entry": {"source": "canon/standards/security-layer-model_v0.8.md"}})
|
||||||
|
code, errors, _ = layer.evaluate(root=root)
|
||||||
|
self.assertEqual(code, 2)
|
||||||
|
|
||||||
|
def test_schema_version_and_comments_are_not_reached(self):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
_write_tree(root, decl={**DECL, "schema_version": "0.1"})
|
||||||
|
path = root / "layer.yaml"
|
||||||
|
path.write_text("# Framework: security-layer-model_v0.7.md\n" + path.read_text())
|
||||||
|
code, errors, _ = layer.evaluate(root=root)
|
||||||
|
self.assertEqual(code, 0, errors)
|
||||||
|
|
||||||
|
def test_non_declaration_files_are_not_read(self):
|
||||||
|
"""GH-DEC-2026-020 §3: stance/claims/classification maps keep their versions."""
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
_write_tree(root)
|
||||||
|
(root / "pep-stance.yaml").write_text('standard_version: "0.7"\n')
|
||||||
|
(root / "pip-claims.yaml").write_text('standard_version: "0.7"\n')
|
||||||
|
code, errors, _ = layer.evaluate(root=root)
|
||||||
|
self.assertEqual(code, 0, errors)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,16 @@ Per GH-DEC-2026-017 (amendments A9, A11, A12): INTENT.md governs and
|
||||||
layer.yaml is a derived artifact that must be marked derived and name
|
layer.yaml is a derived artifact that must be marked derived and name
|
||||||
INTENT.md as its source; layer values are compared against §3's closed
|
INTENT.md as its source; layer values are compared against §3's closed
|
||||||
four-token vocabulary after an ASCII case-fold, and neither form is
|
four-token vocabulary after an ASCII case-fold, and neither form is
|
||||||
re-spelled; neither form carries a standard_version.
|
re-spelled; neither form carries a standard version.
|
||||||
|
|
||||||
|
Per GH-DEC-2026-020 (A12 r2), "carries a standard version" is a property of
|
||||||
|
the content, not of a key name. The declaration is every key and value of the
|
||||||
|
INTENT.md frontmatter and of layer.yaml; a version-bearing `standard:` or
|
||||||
|
`companion:` path, a `companion_version`, or any `*_version` key other than
|
||||||
|
`schema_version` is a pin and fails. Comments and `schema_version` are not
|
||||||
|
reached. Stance, claims, and evidence-classification maps are not
|
||||||
|
declarations and are never read here. The version belongs to the run: every
|
||||||
|
run prints VALIDATED_AGAINST and the scope it ranged over.
|
||||||
|
|
||||||
The failure it exists to catch is a *convenience* — a live lookup, an
|
The failure it exists to catch is a *convenience* — a live lookup, an
|
||||||
OpenBao client, or an /authorize helper "just for this consumer". That is
|
OpenBao client, or an /authorize helper "just for this consumer". That is
|
||||||
|
|
@ -32,6 +41,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ast
|
import ast
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -44,6 +54,25 @@ TOOLS = ROOT / "tools"
|
||||||
DECL = ROOT / "layer.yaml"
|
DECL = ROOT / "layer.yaml"
|
||||||
INTENT = ROOT / "INTENT.md"
|
INTENT = ROOT / "INTENT.md"
|
||||||
|
|
||||||
|
# The standard text this checker was built and validated against. A
|
||||||
|
# declaration carries no version (A12 r2); the run states it instead
|
||||||
|
# (GH-DEC-2026-020 §4). Bump when re-validated against a newer accepted text.
|
||||||
|
VALIDATED_AGAINST = "net-kingdom/canon/standards/security-layer-model_v0.7.md"
|
||||||
|
|
||||||
|
# What a run ranges over. Nothing else in the tree is read; in particular no
|
||||||
|
# stance, claims, or evidence-classification map (GH-DEC-2026-020 §3).
|
||||||
|
SCOPE = "INTENT.md frontmatter, layer.yaml (A12 across all keys and values), tools/**/*.py"
|
||||||
|
|
||||||
|
# A12 r2: keys that are version pins. schema_version is the file format's own
|
||||||
|
# version and is not reached.
|
||||||
|
VERSION_KEY = re.compile(r"(^|_)version$", re.IGNORECASE)
|
||||||
|
NOT_REACHED_KEYS = {"schema_version"}
|
||||||
|
# A version in a path (`..._v0.7.md`, `...-v1.md`) anywhere in a value.
|
||||||
|
VERSIONED_PATH = re.compile(r"[_-]v\d+(\.\d+)*(\.[A-Za-z]+)?\b", re.IGNORECASE)
|
||||||
|
# A bare version token (`v0.2`, `0.7`) in the value of a standard/companion pin.
|
||||||
|
PIN_KEYS = {"standard", "companion", "framework"}
|
||||||
|
VERSION_TOKEN = re.compile(r"\bv?\d+\.\d+(\.\d+)*\b", re.IGNORECASE)
|
||||||
|
|
||||||
TOOLING_IMPORTS = {
|
TOOLING_IMPORTS = {
|
||||||
"hvac": "OpenBao / Vault client",
|
"hvac": "OpenBao / Vault client",
|
||||||
"bao": "OpenBao client",
|
"bao": "OpenBao client",
|
||||||
|
|
@ -117,11 +146,7 @@ def load_declaration(path: Path = DECL) -> dict[str, Any]:
|
||||||
raise ConformanceError(
|
raise ConformanceError(
|
||||||
f"{path.name} derives from {data['derived_from']!r}; §11 names INTENT.md"
|
f"{path.name} derives from {data['derived_from']!r}; §11 names INTENT.md"
|
||||||
)
|
)
|
||||||
if "standard_version" in data:
|
_reject_versions(path.name, data)
|
||||||
raise ConformanceError(
|
|
||||||
f"{path.name} carries 'standard_version'; a layer declaration MUST NOT "
|
|
||||||
"(GH-DEC-2026-017 §5, A12)"
|
|
||||||
)
|
|
||||||
return dict(data)
|
return dict(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -131,14 +156,48 @@ def load_intent(path: Path = INTENT) -> dict[str, Any]:
|
||||||
front = _frontmatter(path)
|
front = _frontmatter(path)
|
||||||
if "layer" not in front:
|
if "layer" not in front:
|
||||||
raise ConformanceError("INTENT.md frontmatter has no 'layer:' key (§11)")
|
raise ConformanceError("INTENT.md frontmatter has no 'layer:' key (§11)")
|
||||||
if "standard_version" in front:
|
_reject_versions("INTENT.md frontmatter", front)
|
||||||
raise ConformanceError(
|
|
||||||
"INTENT.md frontmatter carries 'standard_version'; a layer declaration "
|
|
||||||
"MUST NOT (GH-DEC-2026-017 §5, A12)"
|
|
||||||
)
|
|
||||||
return front
|
return front
|
||||||
|
|
||||||
|
|
||||||
|
def declaration_versions(data: Any, where: str = "") -> list[str]:
|
||||||
|
"""Every standard/companion version pin in a declaration (A12 r2).
|
||||||
|
|
||||||
|
Walks all keys and values. Comments never reach here (YAML drops them);
|
||||||
|
schema_version is skipped by name.
|
||||||
|
"""
|
||||||
|
found: list[str] = []
|
||||||
|
if isinstance(data, Mapping):
|
||||||
|
for key, value in data.items():
|
||||||
|
name = str(key)
|
||||||
|
here = f"{where}.{name}" if where else name
|
||||||
|
if name.lower() in NOT_REACHED_KEYS:
|
||||||
|
continue
|
||||||
|
if VERSION_KEY.search(name):
|
||||||
|
found.append(f"key '{here}' is a version pin")
|
||||||
|
continue
|
||||||
|
if name.lower() in PIN_KEYS and isinstance(value, (str, int, float)):
|
||||||
|
if VERSION_TOKEN.search(str(value)) or VERSIONED_PATH.search(str(value)):
|
||||||
|
found.append(f"'{here}: {value}' carries a version")
|
||||||
|
continue
|
||||||
|
found.extend(declaration_versions(value, here))
|
||||||
|
elif isinstance(data, list):
|
||||||
|
for index, item in enumerate(data):
|
||||||
|
found.extend(declaration_versions(item, f"{where}[{index}]"))
|
||||||
|
elif isinstance(data, str) and VERSIONED_PATH.search(data):
|
||||||
|
found.append(f"'{where}: {data}' is a version-bearing path")
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _reject_versions(source: str, data: Any) -> None:
|
||||||
|
hits = declaration_versions(data)
|
||||||
|
if hits:
|
||||||
|
raise ConformanceError(
|
||||||
|
f"{source} carries a standard version; a layer declaration MUST NOT, in any "
|
||||||
|
f"key or value (GH-DEC-2026-017 §5, GH-DEC-2026-020, A12 r2): " + "; ".join(hits)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _norm(value: Any) -> str:
|
def _norm(value: Any) -> str:
|
||||||
"""ASCII case-fold (§3 as amended by A9). Only A-Z are folded."""
|
"""ASCII case-fold (§3 as amended by A9). Only A-Z are folded."""
|
||||||
text = str(value or "").strip()
|
text = str(value or "").strip()
|
||||||
|
|
@ -294,6 +353,8 @@ def main(argv: Iterable[str] | None = None) -> int:
|
||||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||||
|
|
||||||
code, errors, reports = evaluate()
|
code, errors, reports = evaluate()
|
||||||
|
print(f"checker validated against: {VALIDATED_AGAINST}")
|
||||||
|
print(f"scope: {SCOPE}")
|
||||||
if args.report:
|
if args.report:
|
||||||
try:
|
try:
|
||||||
decl = load_declaration()
|
decl = load_declaration()
|
||||||
|
|
@ -323,7 +384,11 @@ def main(argv: Iterable[str] | None = None) -> int:
|
||||||
print(" Do not declare it to make the check pass.", file=sys.stderr)
|
print(" Do not declare it to make the check pass.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
if not args.report:
|
if not args.report:
|
||||||
print(f"OK: Engine/PIP declaration agrees with INTENT.md; no Tooling client or decision surface in {TOOLS.relative_to(ROOT)}")
|
print(
|
||||||
|
"OK: Engine/PIP declaration agrees with INTENT.md and carries no standard "
|
||||||
|
f"version; no Tooling client or decision surface in {TOOLS.relative_to(ROOT)}; "
|
||||||
|
f"validated against {VALIDATED_AGAINST}"
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue