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 <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
39e3380447
commit
71ae259877
2 changed files with 99 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
94
tests/test_layer_declaration.py
Normal file
94
tests/test_layer_declaration.py
Normal file
|
|
@ -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')
|
||||
Loading…
Add table
Add a link
Reference in a new issue