STATE pkt. 2 scoped a measurement of the substring guards against tmp_path-
GENERATED artefacts. Measured, not reasoned: every one of the 18 assertions
behind those 11 line refs was detached for real and each is individually
load-bearing. Mutation matrix (src/lib mutated in place, restored + sha-verified,
`git status` clean before and after):
M1 render_table drops rows -> ingest_lb:91, sql_lb:104,105 RED
M2 SQL NULL -> naive str() "None" -> sql_lb:61,62 RED
M3 whole REAL loses its .0 -> sql_lb:69 RED
M4 _update_index_lines over-reaches -> ingest_lb:165,166,189 sql:162 RED
M5 _update_index_lines under-reaches -> ingest_lb:188 (negative) RED
M6 _link_in_index no-op -> ingest_lb:169, sql_lb:164 RED
M7 collision gate clobbers first -> test_ingest:141 RED
M8 index label leaks the rationale -> step8:179,180,194 (negative) RED
M9 index label varies per verdict -> step8:186,187,188 RED
M10 re-promotion double-links -> step8:170 RED
M11 fold drops the rationale prose -> step8:151 RED
M12 seeding re-mints the verdict id -> step8:163,164 RED
A second pass was required because pytest stops at the FIRST failing assert:
six assertions sat behind a failing one and were therefore unmeasured at test
level. Re-run with the preceding assertion neutralised, each of those six is
load-bearing too (ingest_lb:91-B, :166; sql_lb:62, :105; step8:180, :164).
The finding is structural, and it is the reason this commit is not empty. Five
NEGATIVE assertions carried no positive control, so they measure an absence
without ever establishing the presence. Proven by value-proof (not merely a red
proof): under a plausible drift — `_link_in_index` detached, or `description`
stopped carrying the rationale — all three tests stayed GREEN with the control
removed and go RED with it present. green-without / red-with is what makes these
controls value-adding rather than decorative.
test_ingest_loadbearing.py the ingest-edge link is asserted PRESENT, in
exactly the form the removal assertion seeks
test_step8_promotion_loadbearing the marker/rationale are asserted live in the
promoted file before the index/context
exclusions are allowed to mean anything
Next lens, enumerated rather than assumed: the class reaches 23 test files, not
the 4 STATE named — ~34 negative substring assertions in total. "Negative without
a positive control" is the sharp, cheap successor to "substring assertion".
Suite 688 passed; ruff + ruff format + mypy --strict clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzEtJzL6SKYbYtSQRY5o57
232 lines
10 KiB
Python
232 lines
10 KiB
Python
"""Step-8 promotion gate — LOAD-BEARING (method-spec §3 Step 8, §6, §11).
|
|
|
|
The seam this file keeps alive: only APPROVED knowledge enters the wiki
|
|
(fail-closed), the promoted file is NAVIGABLE (index-linked — an unlinked file
|
|
is unreachable), and the index label is NEUTRAL (the index body flows verbatim
|
|
into the rendered read-context, so a descriptive label would leak the learning
|
|
signal around the gated fold). RED when a non-approved verdict reaches the
|
|
wiki, when an approved one is not navigable, or when the label leaks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser_claude.experience import (
|
|
CandidateFeatures,
|
|
VerdictStore,
|
|
fold_experience,
|
|
seed_store_from_bundle,
|
|
)
|
|
from portfolio_optimiser_claude.inbox import VerdictDocument
|
|
from portfolio_optimiser_claude.ir import load_validator_input
|
|
from portfolio_optimiser_claude.okf import bundle_context, navigate_bundle, parse_concept_file
|
|
from portfolio_optimiser_claude.promotion import PromotionError, promote
|
|
|
|
SHARED_BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
|
|
|
|
MARKER = "realiseringsgrad=0.79"
|
|
RATIONALE = f"Godkjent med korreksjon: i drift realiseres ~79% ({MARKER})."
|
|
BASE_PROMPT = "Propose exactly one cost-saving measure for this project."
|
|
|
|
# Structured learning fields a promoted file MUST NOT reproduce (§6) — the raw
|
|
# verdict model carries the signal only as rationale prose.
|
|
_SEED_ONLY_FIELDS = (
|
|
"realization_rate",
|
|
"expected_actual_saving_nok",
|
|
"modelled_saving_nok",
|
|
"gap_source",
|
|
"context_key",
|
|
)
|
|
|
|
|
|
def _document(
|
|
decision: str = "approved",
|
|
rationale: str = RATIONALE,
|
|
codes: frozenset[str] = frozenset({"E01"}),
|
|
verdict_id: str | None = None,
|
|
) -> VerdictDocument:
|
|
doc = VerdictDocument.from_candidate(
|
|
CandidateFeatures(
|
|
affected_codes=codes, measure_type="led-retrofit", claimed_saving_nok=25000.0
|
|
),
|
|
decision=decision,
|
|
rationale=rationale,
|
|
description="LED retrofit",
|
|
)
|
|
if verdict_id is not None:
|
|
doc = doc.model_copy(update={"id": verdict_id})
|
|
return doc
|
|
|
|
|
|
def _promote(doc: VerdictDocument, bundle: Path) -> Path:
|
|
return promote(
|
|
doc,
|
|
bundle,
|
|
approved_by="persona:expert-reviewer",
|
|
experiment="S9-offline-sim",
|
|
timestamp="2026-07-03T12:00:00Z",
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def bundle(tmp_path: Path) -> Path:
|
|
target = tmp_path / "bundle"
|
|
shutil.copytree(SHARED_BUNDLE, target)
|
|
return target
|
|
|
|
|
|
class TestFailClosed:
|
|
"""§6: a non-approved verdict is refused — writing and linking NOTHING."""
|
|
|
|
@pytest.mark.parametrize("decision", ["rejected", "maybe"])
|
|
def test_non_approved_decisions_are_refused_writing_nothing(
|
|
self, bundle: Path, decision: str
|
|
) -> None:
|
|
index_before = (bundle / "index.md").read_bytes()
|
|
files_before = sorted(p.name for p in bundle.iterdir())
|
|
with pytest.raises(PromotionError):
|
|
_promote(_document(decision=decision), bundle)
|
|
assert (bundle / "index.md").read_bytes() == index_before
|
|
assert sorted(p.name for p in bundle.iterdir()) == files_before
|
|
|
|
@pytest.mark.parametrize("decision", ["approved", "approved_with_adjustment"])
|
|
def test_the_accepted_set_is_exactly_the_two_approval_forms(
|
|
self, bundle: Path, decision: str
|
|
) -> None:
|
|
assert _promote(_document(decision=decision), bundle).is_file()
|
|
|
|
|
|
class TestPromotedFile:
|
|
"""§6: minimal promoted file — signal as rationale prose, provenance-stamped."""
|
|
|
|
def test_frontmatter_carries_the_contract_fields(self, bundle: Path) -> None:
|
|
doc = _document()
|
|
concept = parse_concept_file(_promote(doc, bundle))
|
|
assert concept.type == "verdict"
|
|
assert concept.frontmatter["decision"] == "approved"
|
|
assert concept.frontmatter["description"] == RATIONALE
|
|
assert concept.frontmatter["verdict_id"] == doc.id # verbatim
|
|
provenance = concept.frontmatter["provenance"]
|
|
for part in ("persona:expert-reviewer", "S9-offline-sim", "2026-07-03T12:00:00Z"):
|
|
assert part in provenance
|
|
|
|
def test_no_structured_learning_fields_are_reproduced(self, bundle: Path) -> None:
|
|
concept = parse_concept_file(_promote(_document(), bundle))
|
|
for field in _SEED_ONLY_FIELDS:
|
|
assert field not in concept.frontmatter
|
|
|
|
def test_timestamp_is_an_explicit_required_argument(self, bundle: Path) -> None:
|
|
# No wall-clock default — promotion is deterministic and reproducible.
|
|
with pytest.raises(TypeError):
|
|
promote( # type: ignore[call-arg]
|
|
_document(), bundle, approved_by="x", experiment="y"
|
|
)
|
|
|
|
def test_same_candidate_id_is_last_write_wins_per_file(self, bundle: Path) -> None:
|
|
first = _promote(_document(rationale=f"first ({MARKER})"), bundle)
|
|
second = _promote(_document(rationale=f"second ({MARKER})"), bundle)
|
|
assert first == second
|
|
assert parse_concept_file(second).frontmatter["description"] == f"second ({MARKER})"
|
|
|
|
|
|
class TestNavigability:
|
|
"""LOAD-BEARING (§11): an approved verdict must be navigable — and fold back."""
|
|
|
|
def test_promoted_file_is_reachable_via_index_navigation(self, bundle: Path) -> None:
|
|
path = _promote(_document(), bundle)
|
|
assert path.name in {c.path.name for c in navigate_bundle(bundle)}
|
|
|
|
def test_promoted_verdict_closes_the_loop_through_seeding(self, bundle: Path) -> None:
|
|
# Promotion → navigation → seeding → fold: the expert's rationale (the
|
|
# learning signal as prose) reaches the next run's hypothesis prompt.
|
|
_promote(_document(), bundle)
|
|
store = VerdictStore()
|
|
seed_store_from_bundle(store, bundle)
|
|
features = CandidateFeatures.from_proposal(load_validator_input(bundle))
|
|
prompt = fold_experience(store, features, BASE_PROMPT, k=3)
|
|
assert MARKER in prompt
|
|
|
|
def test_two_promoted_candidates_both_seed_verbatim_ids(self, bundle: Path) -> None:
|
|
# §4.2 read-VERBATIM at the seeding layer: distinct candidates get
|
|
# distinct store entries — re-minting from bundle features would
|
|
# collide them (first-write-wins) and silently drop one rationale.
|
|
_promote(_document(codes=frozenset({"E01"}), rationale=f"one ({MARKER})"), bundle)
|
|
_promote(_document(codes=frozenset({"E02"}), rationale=f"two ({MARKER})"), bundle)
|
|
store = VerdictStore()
|
|
seed_store_from_bundle(store, bundle)
|
|
features = CandidateFeatures.from_proposal(load_validator_input(bundle))
|
|
rationales = {r.rationale for r in store.retrieve(features, k=10)}
|
|
assert f"one ({MARKER})" in rationales
|
|
assert f"two ({MARKER})" in rationales
|
|
|
|
def test_linking_is_idempotent(self, bundle: Path) -> None:
|
|
path = _promote(_document(), bundle)
|
|
_promote(_document(), bundle)
|
|
index_text = (bundle / "index.md").read_text(encoding="utf-8")
|
|
assert index_text.count(f"]({path.name})") == 1
|
|
|
|
|
|
class TestNeutralLabel:
|
|
"""LOAD-BEARING (§11): the index label carries NO verdict signal."""
|
|
|
|
def test_index_never_carries_the_rationale_or_marker(self, bundle: Path) -> None:
|
|
path = _promote(_document(), bundle)
|
|
# Positive control: the signal IS live in the promoted file, in exactly the form
|
|
# the index assertions search for. Without it both `not in` checks would also
|
|
# pass if the marker stopped being written at all — green for the wrong reason.
|
|
promoted_text = path.read_text(encoding="utf-8")
|
|
assert MARKER in promoted_text
|
|
assert RATIONALE in promoted_text
|
|
index_text = (bundle / "index.md").read_text(encoding="utf-8")
|
|
assert MARKER not in index_text
|
|
assert RATIONALE not in index_text
|
|
|
|
def test_label_is_fixed_across_verdicts(self, bundle: Path) -> None:
|
|
a = _promote(_document(codes=frozenset({"E01"})), bundle)
|
|
b = _promote(_document(codes=frozenset({"E02"})), bundle)
|
|
index_lines = (bundle / "index.md").read_text(encoding="utf-8").splitlines()
|
|
line_a = next(line for line in index_lines if a.name in line)
|
|
line_b = next(line for line in index_lines if b.name in line)
|
|
assert line_a.replace(a.name, "") == line_b.replace(b.name, "")
|
|
|
|
def test_rendered_read_context_never_carries_the_marker(self, bundle: Path) -> None:
|
|
# Belt and braces: the index body flows verbatim into the rendered
|
|
# read-context — after promotion it must still exclude the signal.
|
|
path = _promote(_document(), bundle)
|
|
# Positive control (same reason as above): the marker is live on disk, so the
|
|
# exclusion below measures the RENDERING boundary and not a missing marker.
|
|
assert MARKER in path.read_text(encoding="utf-8")
|
|
assert MARKER not in bundle_context(bundle)
|
|
|
|
|
|
class TestPathSafety:
|
|
"""§6: path-safe, fail-closed against escaping names."""
|
|
|
|
def test_escaping_id_is_sanitised_into_the_bundle(self, bundle: Path, tmp_path: Path) -> None:
|
|
path = _promote(_document(verdict_id="../../evil"), bundle)
|
|
assert path.parent == bundle
|
|
assert not (tmp_path / "evil").exists()
|
|
assert "/" not in path.name and "\\" not in path.name
|
|
|
|
def test_sanitised_ids_never_collide_with_a_distinct_id_owning_the_name(
|
|
self, bundle: Path
|
|
) -> None:
|
|
# C2.5 restarbeid-funn 2: 'e/vil' used to sanitise to 'evil' — the
|
|
# SAME promoted filename as the distinct id 'evil', so the second
|
|
# promotion silently overwrote the first curated verdict.
|
|
first = _promote(_document(verdict_id="e/vil", rationale=f"one ({MARKER})"), bundle)
|
|
second = _promote(_document(verdict_id="evil", rationale=f"two ({MARKER})"), bundle)
|
|
assert first != second
|
|
assert parse_concept_file(first).frontmatter["verdict_id"] == "e/vil"
|
|
assert parse_concept_file(second).frontmatter["verdict_id"] == "evil"
|
|
|
|
def test_degenerate_token_falls_back_to_a_content_hash(self, bundle: Path) -> None:
|
|
path = _promote(_document(verdict_id="///"), bundle)
|
|
assert path.parent == bundle
|
|
token = path.name.removeprefix("promoted-verdict-").removesuffix(".md")
|
|
assert token, "degenerate token must fall back to a non-empty content hash"
|
|
assert set(token) <= set("0123456789abcdef")
|