From 71ae2598776008f9964bf584bf7f642dbb1b8414 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 21 Sep 2026 09:37:12 +0200 Subject: [PATCH] Apply GH-DEC-2026-020: remove companion_version from layer.yaml A12 r2 reaches every key and value of the declaration, including the companion's version. The standard: path in INTENT.md was already unversioned and is unchanged. tests/test_layer_declaration.py fails if a version key, a versioned standard path, or companion_version returns to the INTENT.md frontmatter or layer.yaml; schema_version and comments are not reached. One prose citation ("the v0.5 scope rule") in a rationale value is allowlisted by exact text and reported to gate-house as open. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703 --- layer.yaml | 8 +-- tests/test_layer_declaration.py | 94 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 tests/test_layer_declaration.py diff --git a/layer.yaml b/layer.yaml index 830776a..c22453c 100644 --- a/layer.yaml +++ b/layer.yaml @@ -28,9 +28,11 @@ derived_from: INTENT.md # No `standard_version` key, here or in INTENT.md: GH-DEC-2026-017 §5 / A12 — # a layer declaration MUST NOT carry a standard version. The declared layer is -# a standing property; version-scoped state belongs in the derived conformance -# record. Do not add it back. -companion_version: "0.2" +# a standing property. Do not add it back. +# +# No `companion_version` either: GH-DEC-2026-020 §2 / A12 r2 — the companion's +# version in a declaration is the same pin. tests/test_layer_declaration.py +# fails if it, or a versioned standard path, comes back. repository: approval-engine layer: engine role: pip # §3.3 engine typing; §4 catalog diff --git a/tests/test_layer_declaration.py b/tests/test_layer_declaration.py new file mode 100644 index 0000000..c18fcef --- /dev/null +++ b/tests/test_layer_declaration.py @@ -0,0 +1,94 @@ +"""A12 r2 (GH-DEC-2026-017 §5, GH-DEC-2026-020): the layer declaration carries +no version of the standard or of its companion, in any key or value. + +The declaration is every key of the INTENT.md frontmatter and every key of the +derived layer.yaml. Comments and `schema_version` are not reached. Stance, +claims and classification maps are not declarations and are not checked here. + +Parsed as text, not YAML: pyyaml is not a dependency of this repository, and a +skipped test would enforce nothing. +""" + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + +# A version-bearing key (any key ending in `version`, except the file's own +# schema version) or a version in a value, including one carried in a path or +# file name such as `security-layer-model_v0.7.md`. +VERSION_KEY = re.compile(r"^\s*-?\s*([A-Za-z0-9_]*version)\s*:", re.IGNORECASE) +VERSION_VALUE = re.compile(r"_v\d+(\.\d+)*|\bv\d+\.\d+") + +# OPEN, reported to gate-house with this repository's A12 r2 findings: a prose +# citation of a past rule inside a rationale value ("the v0.5 scope rule") is +# not a pin, but A12 r2's "no key or value" reads literally as reaching it. +# Listed here, exactly, so it stays visible and nothing else slips past. +CITATION_ALLOWLIST = { + "Progress events. Outside §5 by the v0.5 scope rule. Recorded, not", +} + + +def _declaration_lines(text: str) -> list[str]: + lines = [] + for raw in text.splitlines(): + line = raw.split(" #", 1)[0] + if line.lstrip().startswith("#") or not line.strip(): + continue + lines.append(line) + return lines + + +def _intent_frontmatter() -> str: + text = (ROOT / "INTENT.md").read_text() + match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL) + assert match, "INTENT.md has no frontmatter; the declaration lives there" + return match.group(1) + + +def _findings(name: str, text: str) -> list[str]: + found = [] + for line in _declaration_lines(text): + key = VERSION_KEY.match(line) + if key and key.group(1).lower() != "schema_version": + found.append(f"{name}: version key: {line.strip()}") + elif ( + not key + and VERSION_VALUE.search(line) + and line.strip() not in CITATION_ALLOWLIST + ): + found.append(f"{name}: versioned value: {line.strip()}") + return found + + +def test_declaration_carries_no_version(): + findings = _findings("INTENT.md frontmatter", _intent_frontmatter()) + findings += _findings("layer.yaml", (ROOT / "layer.yaml").read_text()) + assert not findings, "\n".join(findings) + + +def test_companion_version_absent(): + assert "companion_version" not in "\n".join( + _declaration_lines((ROOT / "layer.yaml").read_text()) + ) + assert "companion_version" not in "\n".join( + _declaration_lines(_intent_frontmatter()) + ) + + +def test_standard_path_unversioned(): + match = re.search(r"^standard:\s*(\S+)", _intent_frontmatter(), re.MULTILINE) + assert match, "INTENT.md frontmatter names no standard" + assert match.group(1) == "net-kingdom/canon/standards/security-layer-model" + + +def test_detector_catches_the_forms_ruled_on(): + # The check must see the pin under any name, not only `standard_version`. + assert _findings("x", 'companion_version: "0.2"\n') + assert _findings("x", 'standard_version: "0.7"\n') + assert _findings( + "x", "standard: net-kingdom/canon/standards/security-layer-model_v0.7.md\n" + ) + assert not _findings("x", 'schema_version: "0.1"\n') + assert not _findings("x", "# Companion: SECURITY-COMPANION.md v0.2\n") + assert not _findings("x", ' statute: "§9.4"\n "5.1": []\n')