diff --git a/mcp-server/src/legal_mcp/services/learning_loop.py b/mcp-server/src/legal_mcp/services/learning_loop.py index 14fa9a7..1b55217 100644 --- a/mcp-server/src/legal_mcp/services/learning_loop.py +++ b/mcp-server/src/legal_mcp/services/learning_loop.py @@ -149,14 +149,28 @@ async def process_final_version( # it (→ decision_lessons / appeal_type_rules, surfaced by T15) via the gate. # (Previously this auto-upserted every new_expression as a style_pattern — # that both bypassed the gate and contaminated style with substance. Removed.) - if pair_id is not None: - await db.update_draft_final_pair( - UUID(str(pair_id)), - final_text=final_text, - diff_stats=diff_stats, - analysis=analysis, - status="analyzed", + # + # 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( + UUID(str(pair_id)), + final_text=final_text, + diff_stats=diff_stats, + analysis=analysis, + status="analyzed", + ) # Update decision + case status await db.update_decision(UUID(decision["id"]), status="final")