feat(corroboration): approval_action decision fn + kill-switch (INV-COR2/COR4, X11 Phase 2)
- HALACHA_CORROBORATION_AUTO_APPROVE config (default ON, Dafna validated 2026-06-01) - approval_action(agg, has_overruled): overruled→demote, corroborated→approve, else None - 4 offline unit tests; Phase 2 plan + TaskMaster #75 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,12 @@ HALACHA_BULK_EXTRACT_EFFORT = os.environ.get("HALACHA_BULK_EXTRACT_EFFORT", "hig
|
||||
HALACHA_CHUNK_CONCURRENCY = int(os.environ.get("HALACHA_CHUNK_CONCURRENCY", "3"))
|
||||
HALACHA_CORROBORATION_MATCH_FLOOR = float(os.environ.get("HALACHA_CORROBORATION_MATCH_FLOOR", "0.50"))
|
||||
HALACHA_CORROBORATION_MIN_CITES = int(os.environ.get("HALACHA_CORROBORATION_MIN_CITES", "2"))
|
||||
# X11 Phase 2: gate corroboration → approval. Default ON (Dafna validated the
|
||||
# Phase 1 signal, 2026-06-01). Set to "false" to disable the auto-approve/demote
|
||||
# wiring while keeping the Phase 1 signal intact.
|
||||
HALACHA_CORROBORATION_AUTO_APPROVE = os.environ.get(
|
||||
"HALACHA_CORROBORATION_AUTO_APPROVE", "true"
|
||||
).strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
# Voyage AI
|
||||
VOYAGE_API_KEY = os.environ.get("VOYAGE_API_KEY", "")
|
||||
|
||||
@@ -52,6 +52,22 @@ def aggregate(links: list[dict], min_cites: int = config.HALACHA_CORROBORATION_M
|
||||
}
|
||||
|
||||
|
||||
def approval_action(agg: dict, has_overruled: bool) -> str | None:
|
||||
"""Decide the corroboration→approval action for ONE halacha (INV-COR2/COR4).
|
||||
|
||||
- 'demote' : a later court overruled it → back to the chair gate (overruled
|
||||
outranks any positive count, INV-COR2 strong form).
|
||||
- 'approve' : corroborated (≥N distinct positives, 0 negatives — INV-COR4).
|
||||
- None : leave as-is (single source, non-overruled negative, or the
|
||||
uncorroborated tail — INV-COR5 keeps the chair gate).
|
||||
"""
|
||||
if has_overruled:
|
||||
return "demote"
|
||||
if agg.get("corroborated"):
|
||||
return "approve"
|
||||
return None
|
||||
|
||||
|
||||
_TREATMENT_PROMPT = """אתה משפטן בכיר. נתון ציטוט של פסק/החלטה קודמים בתוך החלטה מאוחרת.
|
||||
סווג כיצד ההחלטה המאוחרת **מטפלת** בתקדים המצוטט, לפי אחת מהקטגוריות:
|
||||
- followed — אימצה והחילה את ההלכה.
|
||||
|
||||
@@ -44,3 +44,24 @@ def test_aggregate_negative_blocks():
|
||||
def test_aggregate_below_threshold():
|
||||
agg = cor.aggregate([_link("d1","followed")], min_cites=2)
|
||||
assert agg["corroborated"] is False # single source insufficient (INV-COR4)
|
||||
|
||||
|
||||
# --- Phase 2: approval decision (INV-COR2/COR4) ---
|
||||
|
||||
def test_approval_action_corroborated_approves():
|
||||
agg = {"positive_sources": 2, "has_negative": False, "corroborated": True}
|
||||
assert cor.approval_action(agg, has_overruled=False) == "approve"
|
||||
|
||||
def test_approval_action_overruled_demotes_even_if_corroborated():
|
||||
# overruled outranks any positive count (INV-COR2 strong form)
|
||||
agg = {"positive_sources": 3, "has_negative": True, "corroborated": False}
|
||||
assert cor.approval_action(agg, has_overruled=True) == "demote"
|
||||
|
||||
def test_approval_action_single_source_noop():
|
||||
agg = {"positive_sources": 1, "has_negative": False, "corroborated": False}
|
||||
assert cor.approval_action(agg, has_overruled=False) is None
|
||||
|
||||
def test_approval_action_negative_nonoverruled_noop():
|
||||
# distinguished blocks approval but does not demote (no overruled)
|
||||
agg = {"positive_sources": 2, "has_negative": True, "corroborated": False}
|
||||
assert cor.approval_action(agg, has_overruled=False) is None
|
||||
|
||||
Reference in New Issue
Block a user