core/tests/test_provenance.py
Shay 2e4e45b49b feat(evals): provenance lane v1 — replay determinism + source back-pointers
Phase 2's first lane: every articulated claim must back-point to one of
{pack axiom, vault entry, teaching event}, and replay must reproduce the
trace bit-for-bit.

Components:
- core/cognition/provenance.py: Provenance dataclass + compute_provenance()
  deriving sources from a CognitiveTurnResult. Pack source = non-UNKNOWN
  intent.tag (pack-defined intent rule matched); vault source = vault_hits
  count; teaching source = pack_mutation_proposal.proposal_id.
- evals/provenance/{contract.md, runner.py, dev/, public/v1/, holdouts/v1/}:
  45 cases across pack_axiom / vault_recall / teaching / mixed categories.
- tests/test_provenance.py: 6 unit tests covering all source-kind profiles.

Sub-metrics (all four must pass):
- replay_determinism: same input + fresh runtime -> same trace_hash
- input_sensitivity: distinct prompts -> distinct trace_hashes
- source_attribution: every expected source kind present in Provenance
- source_validity: every cited source resolves to a real artefact

Results:
- dev: 10/10 (all sub-metrics 1.0)
- public/v1: 20/20 (all sub-metrics 1.0)
- holdouts/v1: 15/15 (all sub-metrics 1.0)

PROGRESS.md updated to mark Phase 2 in progress with provenance v1 complete.
2026-05-16 11:45:00 -07:00

170 lines
4.5 KiB
Python

"""Unit tests for core.cognition.provenance.
Covers the four expected source profiles:
- pack only (intent classified, no vault, no teaching)
- pack + vault (recall fired)
- pack + teaching (correction captured)
- no provenance (UNKNOWN intent, no vault, no teaching)
"""
from __future__ import annotations
import numpy as np
from core.cognition.provenance import compute_provenance
from core.cognition.result import CognitiveTurnResult
from field.state import FieldState
from generate.articulation import ArticulationPlan
from generate.intent import DialogueIntent, IntentTag
from generate.proposition import Proposition
from teaching.store import PackMutationProposal
def _zero_versor() -> np.ndarray:
v = np.zeros(32, dtype=np.float32)
v[0] = 1.0
return v
def _make_field_state() -> FieldState:
"""Build a minimal valid field state for tests."""
F = _zero_versor()
return FieldState(F=F)
def _make_result(
*,
intent_tag: IntentTag,
vault_hits: int,
teaching_proposal: PackMutationProposal | None,
trace_hash: str = "deadbeef",
) -> CognitiveTurnResult:
proposition = Proposition(
subject="x",
predicate="is",
object_="y",
surface="x is y",
frame_id="test",
subject_versor=_zero_versor(),
predicate_versor=_zero_versor(),
)
articulation = ArticulationPlan(
subject="x",
predicate="is",
object="y",
surface="x is y",
output_language="en",
frame_id="test",
)
fs = _make_field_state()
intent = (
DialogueIntent(tag=intent_tag, subject="x")
if intent_tag is not None
else None
)
return CognitiveTurnResult(
input_text="what is x?",
input_tokens=("what", "is", "x"),
filtered_tokens=("x",),
field_state_before=None,
field_state_after=fs,
proposition=proposition,
articulation=articulation,
surface="x is y",
walk_surface="x is y",
articulation_surface="x is y",
dialogue_role="elaborate",
identity_score=None,
vault_hits=vault_hits,
intent=intent,
proposition_graph=None,
articulation_target=None,
teaching_candidate=None,
reviewed_teaching_example=None,
pack_mutation_proposal=teaching_proposal,
versor_condition=0.0,
trace_hash=trace_hash,
)
def test_pack_only_source() -> None:
result = _make_result(
intent_tag=IntentTag.DEFINITION,
vault_hits=0,
teaching_proposal=None,
)
prov = compute_provenance(result)
assert prov.is_empty is False
assert prov.kinds() == ("pack",)
assert prov.refs("pack") == ("definition",)
assert prov.refs("vault") == ()
assert prov.refs("teaching") == ()
def test_pack_plus_vault() -> None:
result = _make_result(
intent_tag=IntentTag.RECALL,
vault_hits=3,
teaching_proposal=None,
)
prov = compute_provenance(result)
assert prov.kinds() == ("pack", "vault")
assert prov.refs("pack") == ("recall",)
assert prov.refs("vault") == ("vault_hit_0", "vault_hit_1", "vault_hit_2")
def test_pack_plus_teaching() -> None:
proposal = PackMutationProposal(
proposal_id="abc123",
candidate_id="cand1",
subject="x",
correction_text="x is z",
prior_surface="x is y",
)
result = _make_result(
intent_tag=IntentTag.CORRECTION,
vault_hits=0,
teaching_proposal=proposal,
)
prov = compute_provenance(result)
assert prov.kinds() == ("pack", "teaching")
assert prov.refs("teaching") == ("abc123",)
def test_unknown_intent_no_vault_no_teaching_is_empty() -> None:
result = _make_result(
intent_tag=IntentTag.UNKNOWN,
vault_hits=0,
teaching_proposal=None,
)
prov = compute_provenance(result)
assert prov.is_empty is True
assert prov.kinds() == ()
def test_provenance_has_kind_helper() -> None:
result = _make_result(
intent_tag=IntentTag.DEFINITION,
vault_hits=1,
teaching_proposal=None,
)
prov = compute_provenance(result)
assert prov.has_kind("pack") is True
assert prov.has_kind("vault") is True
assert prov.has_kind("teaching") is False
def test_trace_hash_preserved() -> None:
result = _make_result(
intent_tag=IntentTag.DEFINITION,
vault_hits=0,
teaching_proposal=None,
trace_hash="cafebabe",
)
prov = compute_provenance(result)
assert prov.turn_trace_hash == "cafebabe"