"""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, ) def test_zero_citations_is_rejected() -> None: with pytest.raises(ValidationError): ProvenanceStamp( citations=[], model="m", role="proposer", validator_decision="validated", token_usage=1, ) 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"