Apply GH-DEC-2026-020: print scope on every run, widen A12 detection.

The checker now prints the standard version and the run's scope first on
every run, pass or fail, and enforces A12 r2 over every key and value of
the INTENT.md frontmatter and layer.yaml, not only a key named
standard_version. Tests fail if a versioned standard: path or a
companion_version comes back. Neither declaration form changed.

KG-DEC-2026-005 records assent to A9, A10, A11 and A13 and returns A12 r2
revised, with one finding: "any value" reaches prose revision citations.

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:
tegwick 2026-09-21 09:38:27 +02:00
parent 358f759c72
commit 7b678e5d73
3 changed files with 291 additions and 17 deletions

View file

@ -363,3 +363,91 @@ dereferencing a judgment. Argument: `docs/StatuteV08Review.md` F1.
*Superseded 2026-09-21:* B4 was ruled in `GH-DEC-2026-017` §5 and the field was
removed under `KG-IN-0007`; the validated-against version now lives in
`scripts/check_layer_conformance.py` (`VALIDATED_AGAINST`).
## KG-DEC-2026-005 — Apply GH-DEC-2026-020 to the checker; assent to A9, A10, A11 and A13, and return A12 r2 with one finding
```yaml
id: KG-DEC-2026-005
kind: decision
title: Apply GH-DEC-2026-020 to the checker; assent to A9, A10, A11 and A13, and
return A12 r2 with one finding
status: resolved
owner: codex
repo: kings-guard
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source: gate-house docs/amendments/v0.8-section-11-declaration-amendments.md v0.2 (gate-house@104f3fc)
related:
- gate-house GH-DEC-2026-020
- gate-house GH-DEC-2026-017
- gate-house GH-WP-0004-T09
- KG-DEC-2026-004
affects:
- kings-guard
- gate-house
created: '2026-09-21'
updated: '2026-09-21'
decided_by: codex
disposition: 'A9 approved; A10 approved; A11 approved; A12 r2 revised; A13 approved'
```
## Context
`gate-house` circulated A9A13 for assent (message `09a2d1cb`), then ruled what A12
reaches in `GH-DEC-2026-020` and re-circulated A12 as A12 r2 (message `54080f87`),
asking again because assent to A12 as first circulated does not carry over. Both were
read from gate-house's committed files at `104f3fc`, not from the messages; the ruling
and the message agree.
`GH-DEC-2026-020` §4 adopts this repository's `VALIDATED_AGAINST` constant as the
reference pattern and requires two changes of us: print the run's scope, and widen A12
detection from the key name `standard_version` to any version in any key or value.
## Applied
`scripts/check_layer_conformance.py` now prints `checking against:` and `scope:` as the
first two lines of **every** run, before anything can exit — including a malformed-
declaration failure — and the OK line carries both. A12 is enforced over every key and
value of the `INTENT.md` frontmatter and `layer.yaml`: any version key other than
`schema_version`, a versioned standard or companion path or file name, and any version
token in `standard:`, `companion:` or `framework:`. Comments are not read. Stance,
claims and classification maps are never read for A12; this repository has none.
Tests in `tests/test_layer_conformance.py` fail if a versioned `standard:` path or a
`companion_version` comes back.
Neither declaration form changed. Both were already clean under r2: `standard:` in
`INTENT.md` was de-versioned under `GH-DEC-2026-017`, and there is no companion version.
## Dispositions
- **A9 — approved.** The four-token closed vocabulary with a mandatory fold is what
`KG-IN-0007` needed and what `GH-DEC-2026-017` §2 already governs here.
- **A10 — approved.** A marking rather than a reading is the right fix. Under it our §4
row is *unassessed* until marked, and a run reports it so — which is the answer
`KG-IN-0008` asked for: the repository that benefits from the narrow reading does not
settle whether published posture is evidence.
- **A11 — approved.** One finding on wording, not substance: *"A run over §4 and a run
over every repository carrying a declaration"* names two estate scopes. Our checker's
run is a third, one repository, and prints so. The scope sentence should admit a
single-repository run rather than leave it to read as neither.
- **A13 — approved.**
- **A12 r2 — revised, one finding.** *"No key or value of either carries a version of
this standard"* reaches a revision **citation** in prose as literally as it reaches a
pin. `layer.yaml` carries three: `v0.5` in a non-Tooling note, and `v0.6` twice in gap
records (`owner_status: access-engine declined (v0.6 §13)`). They are provenance —
where a ruling was made, in section numbering that has since moved — and are not
readable as a validity condition, which is §5's ground. But the text as written reaches
them, and an implementer who reads "any value" literally must fail them. Proposed
addition: *"A version reached is one carried as a pin: a version key, or a version in a
path, file name, or identity-bearing value naming this standard or its companion. A
citation of an earlier revision in a record's prose is provenance and is not reached."*
**kings-guard benefits from the narrower reading**, so it is not ours to settle. Until
ruled, the checker reports each citation on every run as `NOTE (A12 r2 reach unruled)`
and does not fail on it; if the wider reading is ruled, we reword the three values and
the checker fails on them. We did not measure other repositories for the same shape.
## Consequences
- `VALIDATED_AGAINST` still names v0.7, the accepted text the checker was validated
against. It moves when the checker is re-validated after the v0.8 flip, not before.
- The pre-existing ruff E501 at the no_standing_credential message in the checker is
outside `make lint`'s scope and is not touched here.

View file

@ -21,10 +21,24 @@ frontmatter `layer:` key governs; layer.yaml is a derived artifact that must be
marked derived, name INTENT.md, and agree with it. Layer comparison ASCII-folds
case against §3's closed four-token vocabulary, so `Staff` and `staff` agree and
neither file is re-spelled. A disagreement that survives the fold is reported as
a finding, not resolved by precedence. Neither form may carry a
`standard_version`; the version this check was built and validated against is
the checker's own, VALIDATED_AGAINST below, and is printed in every run's output
this script's output is the derived conformance record A12 points to.
a finding, not resolved by precedence. Neither form may carry a version of the
standard or its companion in any key or value (A12 r2, GH-DEC-2026-020 §1§2):
not only a key named `standard_version`, but `companion_version`, any other
version key, and a version-bearing path or file name such as a `standard:` of
`security-layer-model_v0.7.md`. Comments are not read (YAML drops them) and
`schema_version` is the sidecar's own schema, so neither is reached. Only
INTENT.md frontmatter and layer.yaml are read for A12: stance, claims and
classification maps are not declarations and this run never applies A12 to them
(GH-DEC-2026-020 §3).
A bare revision citation in a prose value `layer.yaml` cites "v0.6 §13" as
provenance in a gap record is reported on every run, not failed: whether A12 r2's
"any value" reaches a citation rather than a pin is raised with gate-house by
kings-guard (see decisions/decisions.md KG-DEC-2026-005), not settled here.
Every run prints, first, the standard version this check was built and validated
against (VALIDATED_AGAINST) and the scope it ranges over (SCOPE), including runs
that fail (GH-DEC-2026-020 §4). A run is sufficient; nothing durable is emitted.
Review dates are reported, never enforced: a date-triggered failure breaks the
build on a calendar day with no code change.
@ -55,6 +69,29 @@ INTENT = ROOT / "INTENT.md"
# the checker is re-validated against a newer accepted text.
VALIDATED_AGAINST = "net-kingdom/canon/standards/security-layer-model_v0.7.md"
# The scope every run ranges over, printed on every run (GH-DEC-2026-020 §4, A11).
# This is a single-repository run, not an estate run: it grades kings-guard, a §4
# row, and nothing else.
SCOPE = (
"kings-guard only (§4 row, Staff): INTENT.md frontmatter, layer.yaml, "
"src/kings_guard, repository tree for credential files"
)
# A12 r2 detection. A key naming a version is a pin, except the sidecar's own
# schema_version. A value naming the standard or companion with a version, or any
# versioned file name, is a pin. In identity-bearing keys any version token is a
# pin. A bare "vN.N" elsewhere in a value is a citation: reported, not failed.
A12_EXEMPT_KEYS = {"schema_version"}
A12_IDENTITY_KEYS = {"standard", "companion", "framework"}
A12_PIN_VALUE = re.compile(
r"""(?ix)
(?:security[-_ ]?layer[-_ ]?model|security[-_ ]?companion)[^\s'"]*?[-_.]v?\d+(?:\.\d+)+
| [-_]v\d+(?:\.\d+)*\.(?:md|ya?ml|json)\b
"""
)
A12_VERSION_TOKEN = re.compile(r"\bv?\d+\.\d+(?:\.\d+)*\b")
A12_CITATION = re.compile(r"\bv\d+\.\d+\b")
# §3's closed layer vocabulary (A9): four tokens, compared ASCII case-insensitively.
LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
EXPECTED_LAYER = "staff" # kings-guard's own §4 row, folded
@ -105,13 +142,60 @@ def fold(value: object) -> str:
return str(value).encode("ascii", "replace").decode("ascii").lower()
def a12_findings(node: object, where: str) -> tuple[list[str], list[str]]:
"""Every key and value of a declaration form, walked (A12 r2).
Returns (pins, citations). Pins fail the run; citations are reported.
"""
pins: list[str] = []
citations: list[str] = []
def walk(value: object, path: str, key: str | None) -> None:
if isinstance(value, dict):
for k, v in value.items():
k_str = str(k)
sub = f"{path}.{k_str}" if path else k_str
if "version" in k_str.lower() and k_str not in A12_EXEMPT_KEYS:
pins.append(f"{where}: key '{sub}' carries a version")
if k_str in A12_EXEMPT_KEYS:
continue
walk(v, sub, k_str)
return
if isinstance(value, list):
for i, v in enumerate(value):
walk(v, f"{path}[{i}]", key)
return
if value is None or isinstance(value, bool):
return
text = str(value)
if A12_PIN_VALUE.search(text) or (
key in A12_IDENTITY_KEYS and A12_VERSION_TOKEN.search(text)
):
pins.append(f"{where}: '{path}' = {text!r} names a versioned standard")
elif A12_CITATION.search(text):
match = A12_CITATION.search(text).group(0)
citations.append(f"{where}: '{path}' cites {match} in prose")
walk(node, "", None)
return pins, citations
def _refuse_pins(pins: list[str]) -> None:
if pins:
_malformed(
"a layer declaration MUST NOT carry a standard or companion version "
"in any key or value (§11 as amended by A12 r2, GH-DEC-2026-020 §1§2):\n "
+ "\n ".join(pins)
)
def _malformed(message: str) -> None:
print(f"FAIL: {message}", file=sys.stderr)
raise SystemExit(2)
def load_governing_layer() -> str:
"""The declaration: INTENT.md frontmatter `layer:` (§11, GH-DEC-2026-017 §1)."""
def load_intent_frontmatter() -> dict:
"""INTENT.md's frontmatter, parsed; the governing declaration form (§11)."""
if not INTENT.exists():
_malformed(f"no {INTENT.name} — §11's governing declaration form")
lines = INTENT.read_text().splitlines()
@ -125,13 +209,17 @@ def load_governing_layer() -> str:
front = yaml.safe_load("\n".join(lines[1:end])) or {}
except yaml.YAMLError as exc:
_malformed(f"{INTENT.name} frontmatter is not parseable: {exc}")
if not isinstance(front, dict):
_malformed(f"{INTENT.name} frontmatter is not a mapping")
return front
def load_governing_layer() -> str:
"""The declaration: INTENT.md frontmatter `layer:` (§11, GH-DEC-2026-017 §1)."""
front = load_intent_frontmatter()
if "layer" not in front:
_malformed(f"{INTENT.name} frontmatter has no 'layer' key — §11's declaration")
if "standard_version" in front:
_malformed(
f"{INTENT.name} frontmatter carries 'standard_version' — a layer "
"declaration MUST NOT carry a standard version (§11 as amended by A12)"
)
_refuse_pins(a12_findings(front, f"{INTENT.name} frontmatter")[0])
layer = front["layer"]
if fold(layer) not in LAYER_VOCABULARY:
_malformed(
@ -162,11 +250,7 @@ def load_declaration() -> dict:
f"{DECL.name} derives from {data['derived_from']!r}; §11 names INTENT.md "
"as the governing declaration"
)
if "standard_version" in data:
_malformed(
f"{DECL.name} carries 'standard_version' — a layer declaration MUST NOT "
"carry a standard version (§11 as amended by A12)"
)
_refuse_pins(a12_findings(data, DECL.name)[0])
if fold(data["layer"]) not in LAYER_VOCABULARY:
_malformed(
f"{DECL.name} declares layer {data['layer']!r}, outside §3's closed "
@ -231,6 +315,11 @@ def main() -> int:
parser.add_argument("--report", action="store_true", help="print the declaration summary")
args = parser.parse_args()
# Every run, pass or fail, states what it checks against and over what
# (GH-DEC-2026-020 §4). Printed before anything can exit.
print(f"checking against: {VALIDATED_AGAINST}")
print(f"scope: {SCOPE}")
governing = load_governing_layer()
decl = load_declaration()
declared = {c.get("id") for c in decl.get("tooling_contacts") or []}
@ -260,6 +349,13 @@ def main() -> int:
form = meta.get("form", "unspecified") if isinstance(meta, dict) else "unspecified"
print(f" - {name}: {form} (claimed={rules.get(name)})")
citations = (
a12_findings(load_intent_frontmatter(), f"{INTENT.name} frontmatter")[1]
+ a12_findings(decl, DECL.name)[1]
)
for citation in citations:
print(f"NOTE (A12 r2 reach unruled, reported not failed): {citation}")
if forms_disagree(governing, decl["layer"]):
print("", file=sys.stderr)
print("FAIL: the two declaration forms disagree (§11, A11)", file=sys.stderr)
@ -303,7 +399,8 @@ def main() -> int:
print(
f"OK: no direct Tooling client in {SRC.relative_to(ROOT)}; "
"no standing credential (§5, §11, §3.4 rule 1); "
f"declaration forms agree; validated against {VALIDATED_AGAINST}"
"declaration forms agree; no standard or companion version in "
f"either form; validated against {VALIDATED_AGAINST}; scope: {SCOPE}"
)
return 0

View file

@ -75,6 +75,95 @@ def test_no_standard_version_in_either_form():
)
def test_no_version_anywhere_in_either_form():
"""A12 r2 / GH-DEC-2026-020 §1§2: content, not a key name.
Fails if a versioned `standard:` path or a `companion_version` comes back, or
any other version pin in any key or value of either declaration form.
"""
module = _load_checker()
front = _intent_frontmatter()
assert "companion_version" not in front
assert "companion_version" not in yaml.safe_load(DECL.read_text())
assert "_v0" not in str(front.get("standard", ""))
assert module.a12_findings(front, "INTENT.md")[0] == []
assert module.a12_findings(yaml.safe_load(DECL.read_text()), "layer.yaml")[0] == []
@pytest.mark.parametrize(
"front_extra",
[
"standard: net-kingdom/canon/standards/security-layer-model_v0.7.md",
"standard: security-layer-model 0.8",
"companion: net-kingdom/SECURITY-COMPANION_v0.2.md",
'companion_version: "0.2"',
'standard_version: "0.7"',
'spec_version: "0.7"',
],
)
def test_checker_rejects_a_version_pin_in_intent_frontmatter(tmp_path, monkeypatch, front_extra):
module = _load_checker()
front = {k: v for k, v in _intent_frontmatter().items() if k != "standard"}
intent = tmp_path / "INTENT.md"
intent.write_text("---\n" + yaml.safe_dump(front) + front_extra + "\n---\n\n# INTENT\n")
monkeypatch.setattr(module, "INTENT", intent)
with pytest.raises(SystemExit) as exc:
module.load_governing_layer()
assert exc.value.code == 2
def test_checker_rejects_companion_version_in_the_sidecar(tmp_path, monkeypatch):
module = _load_checker()
decl = tmp_path / "layer.yaml"
decl.write_text(DECL.read_text() + '\ncompanion_version: "0.2"\n')
monkeypatch.setattr(module, "DECL", decl)
monkeypatch.setattr(module, "ROOT", tmp_path)
with pytest.raises(SystemExit) as exc:
module.load_declaration()
assert exc.value.code == 2
def test_schema_version_and_comments_are_not_reached():
"""GH-DEC-2026-020 §1: the sidecar's own schema version is not the standard's."""
module = _load_checker()
pins, _ = module.a12_findings(
yaml.safe_load('# Framework: security-layer-model_v0.7.md\nschema_version: "0.1"\n'),
"x",
)
assert pins == []
def test_a_prose_citation_is_reported_not_failed():
"""A revision citation in a gap record is provenance; its reach is unruled."""
module = _load_checker()
pins, citations = module.a12_findings({"owner_status": "declined (v0.6 §13)"}, "x")
assert pins == [] and citations
def test_every_run_prints_version_and_scope_even_when_it_fails(tmp_path):
"""GH-DEC-2026-020 §4: the version belongs to the run, and so does the scope."""
module = _load_checker()
ok = _run()
assert module.VALIDATED_AGAINST in ok.stdout and module.SCOPE in ok.stdout
ok_line = [line for line in ok.stdout.splitlines() if line.startswith("OK:")][0]
assert module.VALIDATED_AGAINST in ok_line and module.SCOPE in ok_line
report = _run("--report")
assert module.VALIDATED_AGAINST in report.stdout and module.SCOPE in report.stdout
# A failing run: copy the checker beside a declaration carrying a pin.
(tmp_path / "scripts").mkdir()
(tmp_path / "scripts" / SCRIPT.name).write_text(SCRIPT.read_text())
(tmp_path / "INTENT.md").write_text(
"---\nlayer: Staff\nstandard: security-layer-model_v0.7.md\n---\n"
)
failed = subprocess.run(
[sys.executable, str(tmp_path / "scripts" / SCRIPT.name)],
capture_output=True,
text=True,
)
assert failed.returncode == 2
assert module.VALIDATED_AGAINST in failed.stdout and module.SCOPE in failed.stdout
def test_the_two_forms_agree_after_folding_case():
"""`Staff` in INTENT.md and `staff` in layer.yaml are one value, not a finding."""
module = _load_checker()