diff --git a/aborist/qa/query.py b/aborist/qa/query.py index 2f7cbde..7081242 100644 --- a/aborist/qa/query.py +++ b/aborist/qa/query.py @@ -1835,6 +1835,10 @@ def query( max_claims_per_answer=int(policy.get( "claim_lattice_max_claims_per_answer", 12 )), + question=question, + warrant_check_enabled=bool(policy.get( + "claim_lattice_warrant_check_enabled", True + )), ) rendered = verdict["rendered_text"] answer_text = rendered if rendered else raw_answer diff --git a/aborist/qa/runner.py b/aborist/qa/runner.py index 5701f95..81b3350 100644 --- a/aborist/qa/runner.py +++ b/aborist/qa/runner.py @@ -652,6 +652,10 @@ def ask( max_claims_per_answer=int(policy.get( "claim_lattice_max_claims_per_answer", 12 )), + question=question, + warrant_check_enabled=bool(policy.get( + "claim_lattice_warrant_check_enabled", True + )), ) rendered = verdict["rendered_text"] answer_text = rendered if rendered else raw_answer diff --git a/aborist/qa/verify.py b/aborist/qa/verify.py index 86195fa..f070eb5 100644 --- a/aborist/qa/verify.py +++ b/aborist/qa/verify.py @@ -1467,6 +1467,8 @@ def verify_claim_lattice_json( max_evidence_per_claim: int = DEFAULT_MAX_POINTERS_PER_CLAIM, min_citation_coverage: float = DEFAULT_MIN_CITATION_COVERAGE, max_claims_per_answer: int = DEFAULT_MAX_CLAIMS_PER_ANSWER, + question: str | None = None, + warrant_check_enabled: bool = True, ) -> dict: """Deterministic verifier for ``answer_mode="claim_lattice"`` (JSON). @@ -1710,6 +1712,38 @@ def verify_claim_lattice_json( else: audit_mode = "UNGROUNDED" + # Warrant-lite — same relation-question hard check as the pointer + # variant. See verify_claim_lattice for the rationale (Ticket H, + # 2026-05-01). Identical demote-to-HYBRID semantics; the JSON + # variant carries the same WARRANT_MISSING violations & the same + # warrant_missing_claim_idxs field on the verdict. + warrant_missing_claims: list[int] = [] + if warrant_check_enabled and question and is_relation_question(question): + for cs in claim_statuses: + if cs.get("status") not in ("EVIDENCE_LINKED", "EVIDENCE_LINKED_PARTIAL"): + continue + cited_eids = cs.get("evidence_ids") or [] + cited_spans = [ + obj.span + for eid in cited_eids + for obj in [evidence_map_by_evidence_id_local(evidence_map, eid)] + if obj is not None + ] + ok, missing = warrant_check(cs.get("text") or "", cited_spans) + if not ok: + warrant_missing_claims.append(cs.get("claim_idx")) + violations.append({ + "kind": "WARRANT_MISSING", + "claim_idx": cs.get("claim_idx"), + "missing_anchors": missing, + "rationale": ( + "claim names answer entity not present in any " + "cited span — pointer-linked but warrant missing" + ), + }) + if warrant_missing_claims and audit_mode == "STRICT": + audit_mode = "HYBRID" + # Same `verifier_method` as the pointer variant ("claim_lattice") # so the providence_cache CHECK constraint accepts both. The mode # is disambiguated downstream via `answer_mode` on the run-DAG & @@ -1725,4 +1759,5 @@ def verify_claim_lattice_json( "rendered_text": rendered_text, "evidence_id_pairs": evidence_id_pairs, "json_fixups": json_fixups, + "warrant_missing_claim_idxs": warrant_missing_claims, }