test(loadbearing): bind a rule phrase to every section the shape guard cannot see
The spec guard anchored SHAPE only: the `## 1.`-`## 12.` headings, `### Step 1`-`8`, and the bare literal `MUST` — the last checked against the WHOLE document. So a commons pull could empty a section of its normative content and stay green as long as the heading survived and `MUST` appeared anywhere else. §5 was the sharpest case: it carries no `MUST` at all, so nothing in the old guard ever touched it. One verbatim normative phrase is now bound to the section that OWNS it, matched whitespace- and emphasis-normalized so a reflow or a bolding change does not false-red — with a control that a changed WORD still does. Two properties make the binding real: the phrase must sit in its own section body (moving it elsewhere leaves the owner unanchored, proven), and it must be unique in the document (a phrase occurring twice could be satisfied from a section other than its owner). The selection is ours, not mirrored. Each phrase names a seam this implementation actually carries, owning module cited per row — a phrase anchoring a seam we do NOT implement would be a green-but-dead guard of the opposite kind. Value-proven against the REAL spec, not a copy: gutting each section (heading kept) reds this guard 11/11, and the OLD guard stayed GREEN for 10 of them. §3 is the single overlap — it owns the `### Step N` headings — so there this is defence in depth, not new coverage. That distinction is recorded in the file rather than left to sound like eleven. Restore sha256-verified; `git status` clean before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWMfPKmJtv7JvYnpomkdV
This commit is contained in:
parent
5c25ae4889
commit
3e91072a7c
1 changed files with 193 additions and 0 deletions
193
tests/test_method_spec_rule_phrases_loadbearing.py
Normal file
193
tests/test_method_spec_rule_phrases_loadbearing.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
"""Rule-phrase anchoring for the method spec (§11 spec-integrity, second half).
|
||||
|
||||
``test_method_spec_loadbearing.py`` anchors the spec's SHAPE: the ``## 1.``–``## 12.``
|
||||
headings, ``### Step 1``–``8``, and the bare literal ``MUST``. Measured hole: the literal
|
||||
is checked against the WHOLE document, so a commons pull could empty any section of its
|
||||
normative content and stay green as long as the heading survived and ``MUST`` appeared
|
||||
anywhere else. §5 is the sharpest case — it carries no ``MUST`` at all, so nothing in the
|
||||
old guard touched it.
|
||||
|
||||
This file closes that hole by binding ONE verbatim normative phrase to the section that
|
||||
OWNS it. Two properties make the binding real rather than decorative:
|
||||
|
||||
- **Bound, not global.** The phrase must appear in ITS OWN section body. Moving it to
|
||||
another section leaves the owning section unanchored (proven below) — which is exactly
|
||||
what a global substring check cannot see.
|
||||
- **Unique in the document.** A phrase that occurred twice could be satisfied by prose in a
|
||||
section other than its owner, reopening the hole in a quieter form.
|
||||
|
||||
Measured, not assumed: gutting each section in the REAL spec reds this guard for all 11,
|
||||
and the OLD guard stayed green for 10 of them. §3 is the one overlap — it owns the
|
||||
``### Step 1``–``8`` headings, so emptying it reds the shape guard too. For §3 this file is
|
||||
defence in depth; for the other ten it is the only thing standing there.
|
||||
|
||||
The SELECTION is ours, not mirrored: each phrase names a seam this implementation actually
|
||||
carries (the owning module is cited per row). A phrase anchoring a seam we do NOT implement
|
||||
would be a green-but-dead guard of the opposite kind — it would protect spec text nothing
|
||||
here depends on. Matching is whitespace- and emphasis-normalized so a harmless reflow or a
|
||||
bolding change does not false-red; the normalization has its own control below.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SPEC = Path(__file__).resolve().parents[1] / "shared" / "method-spec.md"
|
||||
|
||||
# section marker -> (verbatim normative phrase, the seam it anchors / owning module)
|
||||
_RULE_PHRASES: dict[str, tuple[str, str]] = {
|
||||
"## 1.": (
|
||||
"MUST be labelled as such wherever their output is presented",
|
||||
"honesty rule — scripted stand-ins are marked (artifacts.py, provenance.py)",
|
||||
),
|
||||
"## 2.": (
|
||||
"the only path from the output layer into the context layer",
|
||||
"promotion gate as the sole crossing (promotion.py)",
|
||||
),
|
||||
"## 3.": (
|
||||
"`type: verdict` files MUST be excluded from the read-context",
|
||||
"verdict-layer exclusion (okf.py)",
|
||||
),
|
||||
"## 4.": (
|
||||
"`marker` MUST be a substring of `rationale`",
|
||||
"verdict contract — the traceable payload (contracts.py, persona.py)",
|
||||
),
|
||||
"## 5.": (
|
||||
"a run ingests the inbox INTO its store (per-verdict add, first-write-wins per id)",
|
||||
"merge-never-replace on the async file loop (inbox.py)",
|
||||
),
|
||||
"## 6.": (
|
||||
"The timestamp MUST be an explicit required argument",
|
||||
"no wall-clock default in the promotion gate (promotion.py)",
|
||||
),
|
||||
"## 7.": (
|
||||
"Implementations MUST consume them unchanged",
|
||||
"the golden suite as the only ground truth (validator.py)",
|
||||
),
|
||||
"## 8.": (
|
||||
"Cap objects MUST refuse construction with non-positive values",
|
||||
"budget/stop-criteria fail-fast (budget.py, contracts.py)",
|
||||
),
|
||||
"## 9.": (
|
||||
"run whose context yields no citable content MUST fail fast",
|
||||
"provenance — no uncitable run (provenance.py)",
|
||||
),
|
||||
"## 10.": (
|
||||
"ALL configuration MUST be schema-validated fail-fast at startup",
|
||||
"startup contracts before any model client (contracts.py)",
|
||||
),
|
||||
"## 11.": (
|
||||
"MUST prove each seam with a test that FAILS when the seam is detached",
|
||||
"the load-bearing regime this suite is written under",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _normalize(text: str) -> str:
|
||||
"""Whitespace- and emphasis-insensitive: a reflow or a bolding change must not red."""
|
||||
return re.sub(r"\s+", " ", text.replace("*", "")).strip()
|
||||
|
||||
|
||||
def _section_body(text: str, marker: str) -> str:
|
||||
"""Heading to the next section. RED (ValueError) if the section is renamed or dropped."""
|
||||
start = text.index(marker)
|
||||
end = text.find("\n## ", start + len(marker))
|
||||
return text[start:] if end == -1 else text[start:end]
|
||||
|
||||
|
||||
def _is_anchored(text: str, marker: str) -> bool:
|
||||
phrase, _ = _RULE_PHRASES[marker]
|
||||
return _normalize(phrase) in _normalize(_section_body(text, marker))
|
||||
|
||||
|
||||
def _occurrences(text: str, marker: str) -> int:
|
||||
phrase, _ = _RULE_PHRASES[marker]
|
||||
return _normalize(text).count(_normalize(phrase))
|
||||
|
||||
|
||||
def _body_emptied(text: str, marker: str) -> str:
|
||||
"""The section, gutted to its heading alone — the pull this guard exists to catch."""
|
||||
body = _section_body(text, marker)
|
||||
heading = body.splitlines()[0]
|
||||
return text.replace(body, heading + "\n\n")
|
||||
|
||||
|
||||
# --- The guard itself (against the real spec) ---------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_section_keeps_its_rule_phrase(marker: str) -> None:
|
||||
# RED when the owning section loses its normative content — which the heading-anchored
|
||||
# guard cannot see, because the heading survives an emptied body.
|
||||
phrase, seam = _RULE_PHRASES[marker]
|
||||
assert _is_anchored(SPEC.read_text(encoding="utf-8"), marker), (
|
||||
f"{marker} no longer carries its rule phrase ({seam}): {phrase!r}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_rule_phrase_is_unique_in_the_spec(marker: str) -> None:
|
||||
# A phrase occurring twice could be satisfied from a section other than its owner,
|
||||
# reopening the hole quietly. Binding is only meaningful while the phrase is unique.
|
||||
count = _occurrences(SPEC.read_text(encoding="utf-8"), marker)
|
||||
assert count == 1, f"{marker}'s rule phrase occurs {count} times — the binding is ambiguous"
|
||||
|
||||
|
||||
# --- Red-proofs: the guard MUST fail on a detached spec (mutated copy, never shared/) --
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_guard_red_when_section_body_emptied(marker: str) -> None:
|
||||
# THE proof that the measured hole is closed: heading kept, body gone, `MUST` still
|
||||
# present elsewhere in the document — old guard green, this one red.
|
||||
text = SPEC.read_text(encoding="utf-8")
|
||||
assert _is_anchored(text, marker) # positive control: green before the mutation
|
||||
gutted = _body_emptied(text, marker)
|
||||
assert "MUST" in gutted, "the old guard's marker survives — that is the point"
|
||||
assert not _is_anchored(gutted, marker)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_guard_red_when_phrase_moves_to_another_section(marker: str) -> None:
|
||||
# Presence is not anchoring. The phrase survives in the document — appended to §12 —
|
||||
# and the owning section must still report unanchored.
|
||||
text = SPEC.read_text(encoding="utf-8")
|
||||
phrase, _ = _RULE_PHRASES[marker]
|
||||
moved = _body_emptied(text, marker).rstrip("\n") + f"\n\n{phrase}\n"
|
||||
assert _normalize(phrase) in _normalize(moved) # control: it IS still in the document
|
||||
assert not _is_anchored(moved, marker)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_guard_red_when_section_heading_renamed(marker: str) -> None:
|
||||
# Fail-closed: no heading means no anchor, and the slice must raise rather than
|
||||
# silently fall back to a wider (green-but-dead) match.
|
||||
text = SPEC.read_text(encoding="utf-8")
|
||||
renamed = text.replace(marker, marker.replace("## ", "## §"))
|
||||
with pytest.raises(ValueError):
|
||||
_section_body(renamed, marker)
|
||||
|
||||
|
||||
# --- Controls on the normalization itself --------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("marker", list(_RULE_PHRASES))
|
||||
def test_normalization_tolerates_reflow_and_emphasis(marker: str) -> None:
|
||||
# A line-rewrap or a bolding change is not a contract change; it must not red.
|
||||
text = SPEC.read_text(encoding="utf-8")
|
||||
phrase, _ = _RULE_PHRASES[marker]
|
||||
reflowed = text.replace(phrase, phrase.replace(" ", "\n ", 1))
|
||||
assert _is_anchored(reflowed, marker), "a reflow false-reds the guard"
|
||||
bolded = text.replace(phrase, f"**{phrase}**")
|
||||
assert _is_anchored(bolded, marker), "an emphasis change false-reds the guard"
|
||||
|
||||
|
||||
def test_normalization_is_not_so_loose_that_anything_matches() -> None:
|
||||
# The tolerance above is only safe while a CHANGED WORD still reds. Without this,
|
||||
# `_normalize` could degenerate into something that matches everything.
|
||||
assert _normalize("a b\n c") == "a b c"
|
||||
assert _normalize("**bold** text") == "bold text"
|
||||
assert _normalize("MUST fail fast") != _normalize("MUST fail slowly")
|
||||
Loading…
Add table
Add a link
Reference in a new issue