portfolio-optimiser/tests/test_provenance.py
Kjell Tore Guttormsen baae7507a9 feat(okf): den erklaerte bundle_id-en er identiteten - mountet er en filsystem-tilfeldighet
S7a-3 pkt. 1. Til i dag NEKTET reconcile_bundle_id en base som erklaerte en id
katalogen ikke bar. Maalt mot K2 - den foerste leverte basen som erklaerer sin
egen id (618 av 630 konseptfiler + rot-index, alle "k2-trinn1-20260903", levert
som "K2-bundle-20260903") - betydde det at basen ikke kunne aapnes slik den var
levert, og at eneste botemiddel var aa montere den paa nytt for haand, en gang
per leveranse. PM-beslutning: konsumenten slakker.

- Erklaert vinner (B1s rekkefoelge uroert), avviket REGISTRERES:
  ResolvedBundleId.mount + ProvenanceStamp.bundle_id_source +
  DryRunReport.bundle_id_source + run.bundle_id_notice (None ved enighet).
  Stempel-feltet er PAAKREVD uten default: None er en VERDI (veg-stien).
- Det som fortsatt nekter er den EKTE kollisjonen: to KONSEPTER i en base som
  erklaerer ULIKE id-er (okf.assert_declared_ids_agree, kalt ved hver doer som
  aapner en base). Rot-index er IKKE med i enighets-settet - konsept-slaar-index
  er en presedens-regel, saa en index i utakt er fallbacken som taper.
- KONSEKVENS, ikke scope-krype: explore._bundle_index loeser naa den erklaerte
  id-en. Den brukte Path(raw).name mens dispatcheren brukte reconcile...id; med
  erklaert-vinner ville explore() myntet approaches som navngir MOUNTET mens
  dispatcheren ruter paa ERKLAERINGEN - en utforskning med uruterbart mandat.

Load-bearing MAALT: 10 mutasjoner alle roede mot HELE suiten, groenn kontroll
1243 passed / 5 skipped og golden demo-transcript.stdout byte-uendret
(shasum -a 1 = ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
M1 1 / M2 1 / M3 1 / M4 9 / M5 2 / M6 1 / M7 1 / M8 2 / M9 2 / M11 1.

Tre armer i test_bundle_id_reconciliation_loadbearing er SKREVET OM (ikke
slettet) - de pinnet nekten beslutningen fjernet. (j) ble skarpere enn den den
erstattet: erklaert id ruter, mountet nektes.

Kontrakt: docs/okf-konsum-kontrakter.md § 3.1. Invariantrad i CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:47:58 +02:00

51 lines
1.7 KiB
Python

"""Step 6 tests — first-class Pydantic provenance stamp.
At least one citation is mandatory (the load-bearing NFR); a valid stamp exposes the
proposal's provenance fields; the annotation adapter yields MAF citation dicts for display.
Pattern: tests/spikes/test_c_validator.py (pydantic raises + field assertions).
"""
import pytest
from pydantic import ValidationError
from portfolio_optimiser.provenance import Citation, ProvenanceStamp
from portfolio_optimiser.retrieval import TextSpan
def _stamp() -> ProvenanceStamp:
return ProvenanceStamp(
citations=[Citation(file="asphalt.txt", locator=TextSpan(0, 10), snippet="0123456789")],
model="qwen3:4b",
role="proposer",
validator_decision="validated",
token_usage=42,
cost_baseline_anchored=True,
bundle_id_source=None,
)
def test_zero_citations_is_rejected() -> None:
with pytest.raises(ValidationError):
ProvenanceStamp(
citations=[],
model="m",
role="proposer",
validator_decision="validated",
token_usage=1,
cost_baseline_anchored=True,
bundle_id_source=None,
)
def test_valid_stamp_exposes_provenance_fields() -> None:
stamp = _stamp()
assert len(stamp.citations) >= 1
assert stamp.model and stamp.role
assert stamp.validator_decision in {"validated", "rejected"}
assert isinstance(stamp.token_usage, int) and stamp.token_usage > 0
def test_to_annotations_yields_citation_dicts() -> None:
anns = _stamp().to_annotations()
assert anns and all(a["type"] == "citation" for a in anns)
assert anns[0]["annotated_regions"][0]["type"] == "text_span"