commons executed V1 (54e0ec7): §7's `generated` row is now the O2 inline mapping
`{ by: process:okf-ingest, at: <ingested_at> }`, and `generated: true` is gone from
the spec. The subtree pull brought that text in — and all 711 tests stayed green
while the shipped spec and the shipped emission disagreed. Green-but-dead, the
exact failure mode §11 exists for.
The byte form is not ours to edit: the golden bundles are compared byte-for-byte
against `materialize()` output, which delegates to llm-ingestion-okf pinned at
v0.3.2 — measured emitting `"generated": "true"` (materialize.py:103), with an
ownership predicate on the literal (`:89`) that the new §3 forbids. Editing the
golden to O2 would turn the suite RED against the pin, not conformant. Adoption is
gated on an okf release that emits O2, and the pin move is operator-owned.
So the divergence is RECORDED instead of hidden, and ratcheted in both directions:
RED if commons reverts §7, RED the moment a materialize() run reaches O2 — which is
when the four golden blobs and the two verbatim asserts must be adopted in the same
commit and this file deleted.
Value-proven with the mutation harness, all four green-before/red-after, restores
sha256-verified: spec row removed · actor renamed · pre-V1 literal returned · and
the one that matters, the installed emitter mutated to O2 (M4 fails on MY assert at
:115, after the population control passed — not on a NameError, not on the control).
The two existing `== "true"` asserts no longer read as conformance claims, and the
§12 anchor's justification is re-measured (`generated` appears 22 times outside §12,
once inside) since the old one cited a literal the spec no longer carries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JYWMfPKmJtv7JvYnpomkdV
197 lines
9.5 KiB
Python
197 lines
9.5 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
|
|
# NOT a conformance claim: §7 defines the O2 mapping since commons' V1, and the
|
|
# pinned emitter still writes the pre-V1 literal. The divergence is recorded and
|
|
# ratcheted in `test_ingest_stamp_conformance_loadbearing.py` — adopt there first.
|
|
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
|