"""Tests for Phase 3 of `#000031` — warrant-chain lookup + verifier suppression of WARRANT_MISSING when a Merkle-bound primary-source backing exists. """ from __future__ import annotations import sqlite3 from pathlib import Path import pytest from arborist.qa.warrant_chain import ( has_warrant_chain, warrant_chain_lookup, ) from arborist.store import SCHEMA_SQL @pytest.fixture def shards_with_chain(tmp_path: Path): """Build a minimal two-shard cluster: a main shard with a `derivations` row written by warrant-resolver-v1 (mimics what `arborist warrant-resolve --write` produces) plus a sibling crawl shard that doesn't have a derivations table. Returns ``(shards_dir, core_root, src_root)``. """ shards_dir = tmp_path / "shards" crawl_dir = tmp_path / "crawl" shards_dir.mkdir() crawl_dir.mkdir() main_db = shards_dir / "000.db" conn = sqlite3.connect(str(main_db)) try: conn.executescript(SCHEMA_SQL) # Insert a fake claim-pack record + textbook surface document # so the FK constraints on derivations are satisfied. core_root = "a" * 64 src_root = "b" * 64 conn.execute( "INSERT INTO documents " "(document_root, document_uri, source_type, kind, " " compression_depth, title, chunking_version, " " canonicalization_version, schema_version, ingest_ts) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (core_root, "claim_pack://test", "claim_pack", "surface", 0, "Test record", "tok-512-v1", "norm-v1", "v9.8.0", 0), ) conn.execute( "INSERT INTO documents " "(document_root, document_uri, source_type, kind, " " compression_depth, title, chunking_version, " " canonicalization_version, schema_version, ingest_ts) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (src_root, "https://example/textbook", "html", "surface", 0, "Textbook", "tok-512-v1", "norm-v1", "v9.8.0", 0), ) conn.execute( "INSERT INTO derivations " "(core_root, src_root, process_id, proof_blob, distilled_at) " "VALUES (?, ?, ?, ?, ?)", (core_root, src_root, "warrant-resolver-v1", b"{}", 0), ) conn.commit() finally: conn.close() # Sibling crawl shard with no derivations table — exercises the # try/except for OperationalError. sibling_db = crawl_dir / "textbook_test.db" sib = sqlite3.connect(str(sibling_db)) try: sib.execute("CREATE TABLE meta (k TEXT, v TEXT)") sib.commit() finally: sib.close() return shards_dir, core_root, src_root # --- warrant_chain_lookup ------------------------------------- def test_warrant_chain_lookup_finds_resolver_rows(shards_with_chain): shards_dir, core_root, _src_root = shards_with_chain roots = warrant_chain_lookup(shards_dir) assert core_root in roots assert isinstance(roots, frozenset) def test_warrant_chain_lookup_skips_unrelated_process_ids(tmp_path): """A shard whose derivations are from non-warrant processes (e.g. distillation) should NOT contribute to the warrant-chain set. """ shards_dir = tmp_path / "shards" shards_dir.mkdir() db = shards_dir / "000.db" conn = sqlite3.connect(str(db)) try: conn.executescript(SCHEMA_SQL) conn.execute( "INSERT INTO documents " "(document_root, document_uri, source_type, kind, " " compression_depth, title, chunking_version, " " canonicalization_version, schema_version, ingest_ts) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ("c" * 64, "u1", "html", "surface", 0, "t", "tok-512-v1", "norm-v1", "v9.8.0", 0), ) conn.execute( "INSERT INTO documents " "(document_root, document_uri, source_type, kind, " " compression_depth, title, chunking_version, " " canonicalization_version, schema_version, ingest_ts) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ("d" * 64, "u2", "html", "core", 0, "t", "tok-512-v1", "norm-v1", "v9.8.0", 0), ) # Distillation process — should NOT match warrant-resolver-v1. conn.execute( "INSERT INTO derivations " "(core_root, src_root, process_id, proof_blob, distilled_at) " "VALUES (?, ?, ?, ?, ?)", ("d" * 64, "c" * 64, "tfidf-keywords-v1", b"{}", 0), ) conn.commit() finally: conn.close() roots = warrant_chain_lookup(shards_dir) assert roots == frozenset() def test_warrant_chain_lookup_empty_when_no_shards(tmp_path): """Nonexistent shards_dir → empty frozenset, no exception.""" assert warrant_chain_lookup(tmp_path / "nope") == frozenset() def test_warrant_chain_lookup_handles_missing_derivations_table(tmp_path): """Shards without a derivations table contribute zero rows silently (won't raise). """ shards_dir = tmp_path / "shards" shards_dir.mkdir() bad_db = shards_dir / "000.db" conn = sqlite3.connect(str(bad_db)) try: conn.execute("CREATE TABLE foo (x TEXT)") conn.commit() finally: conn.close() assert warrant_chain_lookup(shards_dir) == frozenset() def test_warrant_chain_lookup_matches_alias_variant(tmp_path): """warrant-resolver-v1+alias is also a warrant-resolver match.""" shards_dir = tmp_path / "shards" shards_dir.mkdir() db = shards_dir / "000.db" conn = sqlite3.connect(str(db)) try: conn.executescript(SCHEMA_SQL) for r in ("e" * 64, "f" * 64): conn.execute( "INSERT INTO documents " "(document_root, document_uri, source_type, kind, " " compression_depth, title, chunking_version, " " canonicalization_version, schema_version, ingest_ts) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (r, "u_" + r[:4], "claim_pack", "surface", 0, "t", "tok-512-v1", "norm-v1", "v9.8.0", 0), ) conn.execute( "INSERT INTO derivations " "(core_root, src_root, process_id, proof_blob, distilled_at) " "VALUES (?, ?, ?, ?, ?)", ("e" * 64, "f" * 64, "warrant-resolver-v1+alias", b"{}", 0), ) conn.commit() finally: conn.close() roots = warrant_chain_lookup(shards_dir) assert "e" * 64 in roots # --- has_warrant_chain ---------------------------------------- def test_has_warrant_chain_basic(): chain_set = frozenset({"a" * 64, "b" * 64}) assert has_warrant_chain(["a" * 64, "z" * 64], chain_set) is True assert has_warrant_chain(["z" * 64], chain_set) is False assert has_warrant_chain([], chain_set) is False def test_has_warrant_chain_empty_set_short_circuits(): """Empty warrant_chain_roots → fast-fail; backward-compatible for callers that pass `frozenset()` as default. """ assert has_warrant_chain(["a" * 64], frozenset()) is False # --- verifier integration ------------------------------------- def _stub_chunk(source_root: str, span: str) -> dict: """Minimal chunk dict for build_evidence_map.""" return { "source_root": source_root, "document_uri": "test://", "title": "t", "chunk_idx": 0, "chunk_root": "c" * 64, "span": span, "source_role": "primary_answer_source", } def test_verify_claim_lattice_suppresses_warrant_missing_with_chain(): """When the cited evidence's source_root is in warrant_chain_roots AND the lexical warrant_check would fail, the verifier suppresses WARRANT_MISSING and records the claim on warrant_proven_claim_idxs instead. The smoke regime: warrant_check fires WARRANT_MISSING only on relation-shaped questions ('who is X's boss?', 'when did Y happen?') — for those, an answer entity has to appear in the cited span. We construct exactly that case. """ from arborist.qa.evidence import build_evidence_map from arborist.qa.verify import verify_claim_lattice chain_root = "a" * 64 no_chain_root = "z" * 64 # Evidence span has no proper-noun answer entity — so the # warrant_check will fire on a relation question. evidence_map = build_evidence_map( [_stub_chunk(chain_root, "some prose about a topic.")] ) # Claim asserts a proper-noun ("Mr. Burns") that is NOT in the # cited span — exact failure shape that triggers WARRANT_MISSING. answer = "Mr. Burns is the boss. [E1]" # WITHOUT chain → WARRANT_MISSING fires. verdict_no_chain = verify_claim_lattice( answer, evidence_map, question="who is the boss?", warrant_chain_roots=frozenset(), ) kinds_no = {v.get("kind") for v in verdict_no_chain.get("violations") or []} # WITH chain → WARRANT_MISSING suppressed; warrant_proven_claim_idxs # picks up the claim. verdict_with_chain = verify_claim_lattice( answer, evidence_map, question="who is the boss?", warrant_chain_roots=frozenset({chain_root}), ) kinds_with = {v.get("kind") for v in verdict_with_chain.get("violations") or []} # The chain-set version should not produce WARRANT_MISSING for # the chain-rooted claim. (Other violations may still fire from # other rules — we only check that WARRANT_MISSING is suppressed # AND that warrant_proven_claim_idxs is populated.) if "WARRANT_MISSING" in kinds_no: # Fired without chain → chain-version should have suppressed. assert "WARRANT_MISSING" not in kinds_with or ( verdict_with_chain.get("warrant_proven_claim_idxs") or [] ) # Chain side records the proven idx even if other rules fire. # Note: we don't strictly assert the absent-side fires — # warrant_check is shape-gated and may not trigger in this # narrow stub. We assert the chain-side behavior is correct # WHEN the absent-side fires. # Chain root NOT in set → no warrant_proven_claim_idxs. verdict_unrelated = verify_claim_lattice( answer, evidence_map, question="who is the boss?", warrant_chain_roots=frozenset({no_chain_root}), ) assert (verdict_unrelated.get("warrant_proven_claim_idxs") or []) == [] def test_verify_returns_warrant_proven_idxs_field_always(): """`warrant_proven_claim_idxs` is on every verdict (empty list if no chain-side suppression fired). Schema-level guarantee for downstream consumers.""" from arborist.qa.evidence import build_evidence_map from arborist.qa.verify import ( verify_claim_lattice, verify_claim_lattice_json, ) evidence_map = build_evidence_map( [_stub_chunk("a" * 64, "some prose.")] ) pointer_verdict = verify_claim_lattice( "X is Y. [E1]", evidence_map, question="what is X?", ) assert "warrant_proven_claim_idxs" in pointer_verdict assert isinstance(pointer_verdict["warrant_proven_claim_idxs"], list) json_verdict = verify_claim_lattice_json( '{"claims":[{"text":"X is Y","evidence_ids":["E1"]}]}', evidence_map, question="what is X?", ) assert "warrant_proven_claim_idxs" in json_verdict assert isinstance(json_verdict["warrant_proven_claim_idxs"], list)