fix(learning): process_final_version מאחסן דיסטילציה גם כשאין pair (create-or-update, INV-LRN4)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s

אודיט #122 חשף ש-process_final_version מחשב diff+analysis אך משליך אותם כשאין
draft_final_pair במצב final_received — קרה ל-5 תיקים סופיים היסטוריים שקדמו למנגנון
ה-snapshot ב-mark-final (pair ראשון 2026-06-06), ולכל קריאת ingest_final_version ישירה.
התוצאה: הפרת INV-LRN4 בפועל (סופי שלא הושווה/נשמר).

התיקון: create-or-update — כשאין pair, פותחים אחד מ-decision_blocks החיים (status→analyzed)
כך שהדיסטילציה נשמרת כ-הצעה ברשם. לתיקים חדשים אין שינוי-התנהגות (תמיד יש pair
מ-mark-final → רק ה-update רץ). זה keystone שמאפשר backfill (#125.2) דרך הפייפליין הקיים.

caveat מתועד בלוג: לתיק היסטורי ה-draft = blocks נוכחיים (אולי נערכו אחרי-חתימה),
לא snapshot-אמיתי.

Invariants:
- INV-LRN4 (מקיים) — כל סופי מקבל pair ומנותח; אין סופי "פתוח".
- INV-LRN1/G10 (נשמר) — הדיסטילציה נשמרת כ-הצעה (analyzed) בלבד; שער ה-promote הידני
  לקיפול ל-appeal_type_rules לא נעקף.
- G2 (מקיים) — אותו פנקס draft_final_pairs, לא מסלול מקביל.
- G1 (מקיים) — נרמול במקור (הרשם) במקום תיקון-בקריאה.

ref: data/audit/learning-loop-activity-20260611.md · TaskMaster legal-ai #122/#125.1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 17:07:41 +00:00
parent e91c1c4afc
commit 94a4c3600e

View File

@@ -149,7 +149,21 @@ async def process_final_version(
# it (→ decision_lessons / appeal_type_rules, surfaced by T15) via the gate. # it (→ decision_lessons / appeal_type_rules, surfaced by T15) via the gate.
# (Previously this auto-upserted every new_expression as a style_pattern — # (Previously this auto-upserted every new_expression as a style_pattern —
# that both bypassed the gate and contaminated style with substance. Removed.) # that both bypassed the gate and contaminated style with substance. Removed.)
if pair_id is not None: #
# create-or-update (INV-LRN4): normally mark-final already opened a
# 'final_received' pair, so we just advance it. For a case whose final
# pre-dates the mark-final snapshot mechanism (historical backfill) or a direct
# ingest_final_version call, no pair exists — open one now from the live blocks
# so the distillation is actually persisted instead of silently discarded.
# Caveat: the captured draft is the CURRENT blocks (possibly edited after
# sign-off), not a true mark-final snapshot.
if pair_id is None:
pair_id = await db.create_draft_final_pair(case_id, draft_text, "")
logger.info(
"process_final_version: no 'final_received' pair for case %s — opened one "
"from live blocks (backfill path; draft may post-date sign-off)",
case_id,
)
await db.update_draft_final_pair( await db.update_draft_final_pair(
UUID(str(pair_id)), UUID(str(pair_id)),
final_text=final_text, final_text=final_text,