"""Tests for the Lead / Provenance datamodel (F-13).""" from __future__ import annotations from typing import get_args import pytest from codex.synthesis import Lead, LeadKind, LeadStatus, Provenance def test_provenance_requires_bibkey_and_locator() -> None: """Provenance has two required positional fields.""" p = Provenance(bibkey="Smith2020", locator="chunk 7") assert p.bibkey == "Smith2020" assert p.locator == "chunk 7" def test_provenance_missing_field_raises() -> None: """Provenance without locator must fail to construct (TypeError).""" with pytest.raises(TypeError): Provenance(bibkey="Smith2020") # type: ignore[call-arg] def test_lead_required_fields() -> None: """Lead requires id, kind, title, body, provenance, confidence.""" lead = Lead( id="L-0001", kind="connection", title="A connection", body="Some body text.", provenance=[Provenance(bibkey="X2020", locator="chunk 1")], confidence=0.8, ) assert lead.id == "L-0001" assert lead.kind == "connection" assert lead.status == "unverified" # default assert lead.suggested_validation == "" # default assert lead.claims == [] # default factory def test_lead_kind_literals_exact() -> None: """Lead kinds are exactly: connection, gap, improvement, conjecture.""" assert set(get_args(LeadKind)) == {"connection", "gap", "improvement", "conjecture"} def test_lead_status_literals_exact() -> None: """Lead statuses are exactly: unverified, spiking, go, no-go.""" assert set(get_args(LeadStatus)) == {"unverified", "spiking", "go", "no-go"} def test_lead_provenance_can_be_multiple() -> None: """A Lead may carry multiple provenance pointers.""" provs = [ Provenance(bibkey="A2020", locator="chunk 1"), Provenance(bibkey="B2021", locator="chunk 7"), ] lead = Lead( id="L-0002", kind="gap", title="t", body="b", provenance=provs, confidence=0.5, ) assert len(lead.provenance) == 2 def test_lead_missing_provenance_field_raises() -> None: """Constructing Lead without provenance is a TypeError.""" with pytest.raises(TypeError): Lead( # type: ignore[call-arg] id="L-0003", kind="connection", title="x", body="y", confidence=0.5, ) def test_lead_empty_provenance_raises() -> None: """Constructing Lead with empty provenance list raises ValueError.""" with pytest.raises(ValueError, match="at least one Provenance"): Lead( id="L-0004", kind="connection", title="x", body="y", provenance=[], confidence=0.5, ) def test_lead_id_format_helper() -> None: """_make_lead_id produces a kind-prefixed, zero-padded L--XXXX string.""" from codex.synthesis import _make_lead_id assert _make_lead_id(1, "connection") == "L-C-0001" assert _make_lead_id(42, "gap") == "L-G-0042" assert _make_lead_id(9999, "improvement") == "L-I-9999" assert _make_lead_id(1, "conjecture") == "L-X-0001" def test_lead_id_namespaced_per_kind_no_collision() -> None: """Regression for audit C-11: each stage numbers from 1, so without a kind prefix connection/gap/improvement all collide on L-0001 and overwrite one another in grounded/. The prefix must keep same-seq ids distinct. """ from codex.synthesis import _make_lead_id ids = {_make_lead_id(1, k) for k in ("connection", "gap", "improvement", "conjecture")} assert len(ids) == 4, f"same-seq ids must stay distinct across kinds, got {ids}"