feat(audit): log_action_safe + V22 blocks_stale + citation resolver (FU-7)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1106,6 +1106,16 @@ CREATE INDEX IF NOT EXISTS idx_case_law_searchable ON case_law (searchable);
|
||||
"""
|
||||
|
||||
|
||||
# ── V22: cases.blocks_stale — DOCX↔blocks drift flag (GAP-17 / INV-EX1) ──
|
||||
# Set true when revise_draft/apply_user_edit make active_draft_path the live
|
||||
# source-of-truth without re-syncing decision_blocks; cleared when blocks are
|
||||
# re-exported or re-saved. Surfaced by health-check. Source-of-truth remains
|
||||
# decision_blocks — this only flags known drift (no fragile DOCX→blocks reparse).
|
||||
SCHEMA_V22_SQL = """
|
||||
ALTER TABLE cases ADD COLUMN IF NOT EXISTS blocks_stale boolean NOT NULL DEFAULT false;
|
||||
"""
|
||||
|
||||
|
||||
async def _run_schema_migrations(pool: asyncpg.Pool) -> None:
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(SCHEMA_SQL)
|
||||
@@ -1130,7 +1140,8 @@ async def _run_schema_migrations(pool: asyncpg.Pool) -> None:
|
||||
await conn.execute(SCHEMA_V19_SQL)
|
||||
await conn.execute(SCHEMA_V20_SQL)
|
||||
await conn.execute(SCHEMA_V21_SQL)
|
||||
logger.info("Database schema initialized (v1-v21)")
|
||||
await conn.execute(SCHEMA_V22_SQL)
|
||||
logger.info("Database schema initialized (v1-v22)")
|
||||
|
||||
|
||||
async def init_schema() -> None:
|
||||
@@ -1206,6 +1217,35 @@ async def get_active_draft_path(case_id: UUID) -> str | None:
|
||||
return row["active_draft_path"] if row else None
|
||||
|
||||
|
||||
async def mark_blocks_stale(case_id: UUID, stale: bool) -> None:
|
||||
"""Flag/clear DOCX↔blocks drift for a case (GAP-17)."""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"UPDATE cases SET blocks_stale = $1, updated_at = now() WHERE id = $2",
|
||||
stale, case_id,
|
||||
)
|
||||
|
||||
|
||||
async def resolve_citation_case_law_ids(ids) -> dict:
|
||||
"""Structural citation→corpus resolution (GAP-20 / INV-AUD3).
|
||||
|
||||
Given case_law_id values referenced by a decision's citations/provenance,
|
||||
split into resolvable (exist in case_law) vs unresolvable.
|
||||
"""
|
||||
resolved, unresolved = [], []
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
for cid in ids:
|
||||
try:
|
||||
exists = await conn.fetchval(
|
||||
"SELECT EXISTS(SELECT 1 FROM case_law WHERE id = $1)", cid)
|
||||
except Exception:
|
||||
exists = False
|
||||
(resolved if exists else unresolved).append(cid)
|
||||
return {"resolved": resolved, "unresolved": unresolved}
|
||||
|
||||
|
||||
def _normalize_case_number(s: str) -> str:
|
||||
"""Canonicalise a case number for tolerant lookup.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user