portfolio-optimiser-claude/tests/test_ingest_loadbearing.py
Kjell Tore Guttormsen 123ecc3113 test(loadbearing): positive controls for the vacuous-negative class
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
2026-07-31 21:15:57 +02:00

194 lines
9.2 KiB
Python

"""Load-bearing ingest seams (ingest-spec §11) — D7 mirror of MAF I2's set.
Each test must go RED when its seam is detached (the method-spec §11 regime): a
grønn-men-død test is the failure mode the rule exists for. The seams mirrored here,
proven through the consumer seam (``portfolio_optimiser_claude.ingest``, backed by
llm-ingestion-okf since the adoption):
- Provenance stamping — a generated file carries the §7 provenance layer, in order.
- Navigability — the generated bundle is consumable by the UNCHANGED ``okf`` navigation
(index links included), incl. the frontmatter-less generated index.
- Verdict reservation — a manifest mapping to ``type: verdict`` is rejected fail-fast,
before any source call.
- Re-ingest layer safety — re-materialization over a bundle carrying a promoted verdict
preserves the verdict file AND its index link (curated content always survives).
"""
from __future__ import annotations
import hashlib
import json
import shutil
from pathlib import Path
import pytest
from portfolio_optimiser_claude import okf
from portfolio_optimiser_claude.ingest import ManifestError, load_manifest, materialize
GOLDEN = Path(__file__).resolve().parents[1] / "examples" / "ingest-golden-file"
INGESTED_AT = (GOLDEN / "ingested-at.txt").read_text(encoding="utf-8").strip()
_PROVENANCE_KEYS = ("source_system", "source_query", "ingested_at", "ingest_manifest", "generated")
def _expected_stamp(manifest_path: Path) -> str:
# The §5 stamp rule, recomputed INDEPENDENTLY of the implementation:
# ``{manifest stem}@{sha256(raw bytes)[:16]}`` — RED if the stamping detaches.
digest = hashlib.sha256(manifest_path.read_bytes()).hexdigest()[:16]
return f"{manifest_path.stem}@{digest}"
def _materialized(tmp_path: Path) -> Path:
bundle = tmp_path / "bundle"
bundle.mkdir()
materialize(GOLDEN / "manifest.json", bundle, INGESTED_AT)
return bundle
class TestProvenanceStamping:
"""Seam: a generated file carries the §7 provenance layer (RED if it stops)."""
def test_generated_file_carries_the_provenance_layer(self, tmp_path: Path) -> None:
bundle = _materialized(tmp_path)
concept = okf.parse_concept_file(bundle / "ingest-costs.md")
for key in _PROVENANCE_KEYS:
assert key in concept.frontmatter, f"provenance key {key} detached"
assert concept.frontmatter["source_system"] == "prosjekt-arkiv"
assert concept.frontmatter["ingested_at"] == INGESTED_AT
assert concept.frontmatter["generated"] == "true"
assert concept.frontmatter["ingest_manifest"] == _expected_stamp(GOLDEN / "manifest.json")
def test_provenance_keys_are_in_the_spec_order(self, tmp_path: Path) -> None:
# §5: exactly these keys, in exactly this order — the chain is a contract.
bundle = _materialized(tmp_path)
lines = (bundle / "ingest-costs.md").read_text(encoding="utf-8").splitlines()
keys = [ln.split(":", 1)[0] for ln in lines[1 : lines.index("---", 1)]]
assert keys == [
"type",
"title",
"source_system",
"source_query",
"ingested_at",
"ingest_manifest",
"generated",
]
class TestNavigability:
"""Seam: the generated bundle is consumable by the UNCHANGED okf navigation."""
def test_generated_bundle_navigates_via_unchanged_okf(self, tmp_path: Path) -> None:
bundle = _materialized(tmp_path)
names = [c.path.name for c in okf.navigate_bundle(bundle)]
# Every generated ingest file must be REACHABLE via index cross-links — an
# unlinked generated file is unreachable (RED if index linking detaches).
assert names == ["index.md", "ingest-costs.md", "ingest-edge.md"]
def test_generated_context_renders_the_extracted_rows(self, tmp_path: Path) -> None:
bundle = _materialized(tmp_path)
context = okf.bundle_context(bundle)
assert "led-retrofit" in context and "requires vendor quote" in context
def test_generated_index_is_frontmatterless_and_still_navigates(self, tmp_path: Path) -> None:
# The generated index carries no frontmatter (§6 shape) — navigation must
# still work, exercising the okf entry-point relaxation end to end.
bundle = _materialized(tmp_path)
assert not (bundle / "index.md").read_text(encoding="utf-8").startswith("---")
assert okf.navigate_bundle(bundle)[0].path.name == "index.md"
class TestVerdictReservation:
"""Seam: an ingest mapping to the verdict layer is rejected fail-fast (§3)."""
def _manifest(self, okf_type: str) -> dict:
return {
"manifest_version": 1,
"source": {"type": "file", "id": "x", "root": "fixture"},
"bundle_summary": "s",
"extractions": [
{"id": "e", "title": "T", "query": "e.csv", "okf_type": okf_type, "max_rows": 1}
],
}
def _write(self, tmp_path: Path, okf_type: str) -> Path:
manifest = tmp_path / "manifest.json"
manifest.write_text(json.dumps(self._manifest(okf_type)), encoding="utf-8")
return manifest
def test_verdict_okf_type_is_rejected(self, tmp_path: Path) -> None:
with pytest.raises(ManifestError):
load_manifest(self._write(tmp_path, "verdict"))
def test_verdict_reservation_is_case_insensitive(self, tmp_path: Path) -> None:
with pytest.raises(ManifestError):
load_manifest(self._write(tmp_path, "Verdict"))
def test_rejection_is_fail_fast_before_any_source_call(self, tmp_path: Path) -> None:
# A verdict manifest never touches the source: no bundle is written.
manifest = self._write(tmp_path, "verdict")
bundle = tmp_path / "bundle"
with pytest.raises(ManifestError):
materialize(manifest, bundle, INGESTED_AT)
assert not bundle.exists() or not any(bundle.iterdir())
class TestReingestLayerSafety:
"""Seam: re-ingest preserves a promoted verdict AND its index link (§3, §6)."""
def _promote(self, bundle: Path) -> None:
# Simulate the promotion gate writing a verdict file + its (neutral) index link,
# plus a curated file + link. Neither carries the ingest stamp.
(bundle / "promoted-verdict-led.md").write_text(
"---\ntype: verdict\ndecision: approved\ndescription: LED holds up.\n---\n\nBody.\n",
encoding="utf-8",
)
(bundle / "curated-note.md").write_text(
"---\ntype: reference\ntitle: Curated\n---\n\nHand-written.\n", encoding="utf-8"
)
index = bundle / "index.md"
index.write_text(
index.read_text(encoding="utf-8")
+ "- [promoted](promoted-verdict-led.md)\n"
+ "- [Curated](curated-note.md)\n",
encoding="utf-8",
)
def test_promoted_verdict_and_curated_survive_reingest(self, tmp_path: Path) -> None:
bundle = _materialized(tmp_path)
self._promote(bundle)
materialize(GOLDEN / "manifest.json", bundle, INGESTED_AT) # re-ingest
assert (bundle / "promoted-verdict-led.md").is_file() # verdict file survives
assert (bundle / "curated-note.md").is_file() # curated file survives
index_text = (bundle / "index.md").read_text(encoding="utf-8")
assert "- [promoted](promoted-verdict-led.md)" in index_text # verdict link survives
assert "- [Curated](curated-note.md)" in index_text # curated link survives
# ...and the ingest files were still refreshed.
assert (bundle / "ingest-costs.md").is_file()
assert "](ingest-costs.md)" in index_text
def test_reingest_drops_a_stale_ingest_file_and_its_link(self, tmp_path: Path) -> None:
# A manifest that no longer generates ingest-edge → the stale file AND its index
# link are removed; the promoted/curated content is untouched (RED if replacement
# over-reaches or under-reaches).
bundle = _materialized(tmp_path)
self._promote(bundle)
# Positive control for the negative assertion below: the link is present, in
# EXACTLY the form the removal assertion searches for. Without this the `not in`
# would also pass if the link form drifted — green for the wrong reason.
assert "](ingest-edge.md)" in (bundle / "index.md").read_text(encoding="utf-8")
# The shrunk manifest must sit beside its own fixture/ (root resolves relative to
# the manifest dir), so copy the golden case and rewrite the manifest there.
case = tmp_path / "case"
shutil.copytree(GOLDEN, case)
shrunk = json.loads((case / "manifest.json").read_text(encoding="utf-8"))
shrunk["extractions"] = [e for e in shrunk["extractions"] if e["id"] == "costs"]
(case / "manifest.json").write_text(json.dumps(shrunk), encoding="utf-8")
materialize(case / "manifest.json", bundle, INGESTED_AT)
assert not (bundle / "ingest-edge.md").exists() # stale ingest file removed
index_text = (bundle / "index.md").read_text(encoding="utf-8")
assert "](ingest-edge.md)" not in index_text # stale ingest link removed
assert "- [promoted](promoted-verdict-led.md)" in index_text # promoted survives
assert (bundle / "ingest-costs.md").is_file() # kept extraction survives