"""Liability firewall: the rules that stop a false accusation.""" from text_detection import guard as tg from text_detection.schema import DetectionResult, EnsembleResult, Verdict def _enable_zeroshot_guard(monkeypatch): monkeypatch.setattr(tg, "_ZEROSHOT_AI_OK", False) def _mk_ensemble(verdict, p_fake, confidence): return EnsembleResult( verdict=verdict, p_fake=p_fake, confidence=confidence, primary_evidence="x", detector_results=[], ) def _mk_det(name, verdict, p_fake, confidence=0.9): return DetectionResult( detector=name, p_fake=p_fake, verdict=verdict, confidence=confidence, ) def test_word_count_counts_words(): assert tg.word_count("hello world foo") == 3 assert tg.word_count("") == 0 def test_is_too_short_boundary_at_50(): assert tg.is_too_short(" ".join(["w"] * 49)) is True assert tg.is_too_short(" ".join(["w"] * 50)) is False def test_short_text_forced_uncertain_and_capped(): res = _mk_ensemble(Verdict.AI_GENERATED, 0.92, 0.90) out = tg.apply_liability_firewall(res, "too short here", []) assert out.verdict == Verdict.UNCERTAIN assert out.confidence <= tg.SHORT_TEXT_CONF_CAP # original untouched (immutability) assert res.verdict == Verdict.AI_GENERATED assert res.confidence == 0.90 def test_ai_verdict_withheld_when_only_zeroshot_agrees(monkeypatch): _enable_zeroshot_guard(monkeypatch) text = " ".join(["word"] * 80) res = _mk_ensemble(Verdict.AI_GENERATED, 0.80, 0.60) dets = [_mk_det("text_fastdetectgpt", Verdict.AI_GENERATED, 0.80)] out = tg.apply_liability_firewall(res, text, dets) assert out.verdict == Verdict.UNCERTAIN def test_ai_verdict_kept_when_non_zeroshot_supports(): text = " ".join(["word"] * 80) res = _mk_ensemble(Verdict.AI_GENERATED, 0.80, 0.60) dets = [ _mk_det("text_fastdetectgpt", Verdict.AI_GENERATED, 0.80), _mk_det("text_watermark", Verdict.AI_GENERATED, 0.98), ] out = tg.apply_liability_firewall(res, text, dets) assert out.verdict == Verdict.AI_GENERATED def test_errored_zeroshot_vote_does_not_count_as_support(monkeypatch): _enable_zeroshot_guard(monkeypatch) text = " ".join(["word"] * 80) res = _mk_ensemble(Verdict.AI_GENERATED, 0.80, 0.60) dets = [ _mk_det("text_fastdetectgpt", Verdict.AI_GENERATED, 0.80), DetectionResult( detector="text_watermark", p_fake=0.9, verdict=Verdict.AI_GENERATED, confidence=0.0, error="boom", ), ] out = tg.apply_liability_firewall(res, text, dets) assert out.verdict == Verdict.UNCERTAIN def test_real_verdict_on_long_text_is_left_alone(): text = " ".join(["word"] * 80) res = _mk_ensemble(Verdict.REAL, 0.10, 0.80) dets = [_mk_det("text_fastdetectgpt", Verdict.REAL, 0.10)] out = tg.apply_liability_firewall(res, text, dets) assert out.verdict == Verdict.REAL assert out.confidence == 0.80