#!/usr/bin/env python3
"""Bank batch-249: Rule 207 low-coverage object reference lexical expansion."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3

from process_ledger import ensure_schema, record_event

DB = "/mnt/donto-data/donto-resources/research/translation-training/kuku-yalanji-runpod-2026-06-30/synthetic/claude-synthetic-v1-2026-07-02/synthetic.db"
LABEL = "batch-249"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-249-low-coverage-object-reference-lexical-expansion-after-rule206-process.md"

TARGET_HEADS = {
    "jidi": "torch",
    "jundi": "pumice stone",
    "dikarra": "war spear",
    "yirrmba": "many-pronged fish spear",
    "balur": "spear thrower",
    "kimil": "stick",
    "binan": "rudder",
    "wuju": "sponge",
    "jungkalu": "billycan",
    "kaban": "paper",
}
SUPPORT = {"yinya", "yaluy", "yinyay"}
DENY_KUKU = {
    "kari", "yala", "yanyu", "nyaji-l", "nyajil", "nyajin", "nuka", "nukal", "nukan",
    "mani", "manil", "manin", "muja", "mujal", "mujan", "dunga", "dungay", "dungan",
    "kada", "kaday", "kadan", "wuna", "wunay", "wunan", "bawa", "bawal", "bawan",
    "yijarri", "yijarril", "yijarrin", "yurru-duda", "yurru-dudal", "kuni", "kunil",
    "kunin", "kalka", "kalkal", "kalkan", "balkal", "balkal", "warrmba", "warrmbal",
    "jurrnga", "jurrngal", "jurrngan", "baja", "bajaku", "marrkin", "bulin", "baybu",
    "naybu", "wuybu", "dikarr", "jindal", "badur", "dubal", "yikari", "nambar",
}
DENY_ENGLISH = (
    "with", "without", "use", "uses", "used", "using", "make", "makes", "made", "making",
    "source", "from", "paperbark", "bark", "grass", "stingray", "barb", "steer", "steers",
    "steering", "throw", "throws", "throwing", "thrown", "fight", "fighting with", "hit",
    "hits", "hitting", "kill", "killing", "hunt", "hunting", "catch", "catching", "fish with",
    "fishing with", "rub", "rubbing", "scrape", "scraping", "cook", "cooking", "water",
    "honey", "soup", "eat", "eating", "drink", "drinking", "read", "reading", "write",
    "writing", "book", "boat", "sickness", "sick", "magic", "sorcery", "ceremony", "mourning",
    "dead", "body", "owner", "own", "belongs", "carry", "carrying", "repair", "travel",
)


def js(values: list[str]) -> str:
    return json.dumps(values, ensure_ascii=False)


def row(
    seq: int,
    english: str,
    kuku: str,
    analysis: str,
    frame: str,
    words: list[str],
    evidence: str,
) -> tuple:
    return (seq, english, kuku, analysis, frame, 2, js(words), js([]), evidence, "0.86")


ROWS = [
    row(1, "The torch is here.", "Jidi yaluy.", "jidi yaluy | torch here", "r207_deictic_here_jidi", ["jidi", "yaluy"], "Dictionary jidi noun 'torch'; Patz §3.7 yaluy 'here'."),
    row(2, "The pumice stone is here.", "Jundi yaluy.", "jundi yaluy | pumice.stone here", "r207_deictic_here_jundi", ["jundi", "yaluy"], "Dictionary jundi noun 'pumice stone'; Patz §3.7 yaluy 'here'."),
    row(3, "The war spear is here.", "Dikarra yaluy.", "dikarra yaluy | war.spear here", "r207_deictic_here_dikarra", ["dikarra", "yaluy"], "Dictionary dikarra noun 'war spear'; Patz §3.7 yaluy 'here'."),
    row(4, "The many-pronged fish spear is here.", "Yirrmba yaluy.", "yirrmba yaluy | many.pronged.fish.spear here", "r207_deictic_here_yirrmba", ["yirrmba", "yaluy"], "Dictionary yirrmba noun 'many-pronged fish spear'; Patz §3.7 yaluy 'here'."),
    row(5, "The spear thrower is here.", "Balur yaluy.", "balur yaluy | spear.thrower here", "r207_deictic_here_balur", ["balur", "yaluy"], "Dictionary balur noun 'spear thrower'; Patz §3.7 yaluy 'here'."),
    row(6, "The stick is here.", "Kimil yaluy.", "kimil yaluy | stick here", "r207_deictic_here_kimil", ["kimil", "yaluy"], "Dictionary kimil noun 'stick'; Patz §3.7 yaluy 'here'."),
    row(7, "The rudder is here.", "Binan yaluy.", "binan yaluy | rudder here", "r207_deictic_here_binan", ["binan", "yaluy"], "Dictionary binan noun 'rudder'; Patz §3.7 yaluy 'here'."),
    row(8, "The sponge is here.", "Wuju yaluy.", "wuju yaluy | sponge here", "r207_deictic_here_wuju", ["wuju", "yaluy"], "Dictionary wuju noun 'sponge'; Patz §3.7 yaluy 'here'."),
    row(9, "The billycan is here.", "Jungkalu yaluy.", "jungkalu yaluy | billycan here", "r207_deictic_here_jungkalu", ["jungkalu", "yaluy"], "Dictionary jungkalu noun 'billycan'; Patz §3.7 yaluy 'here'."),
    row(10, "The paper is here.", "Kaban yaluy.", "kaban yaluy | paper here", "r207_deictic_here_kaban", ["kaban", "yaluy"], "Dictionary kaban noun 'paper, book'; selected paper only; Patz §3.7 yaluy 'here'."),
    row(11, "The torch is there.", "Jidi yinyay.", "jidi yinyay | torch there", "r207_deictic_there_jidi", ["jidi", "yinyay"], "Dictionary jidi noun 'torch'; Patz §3.7 yinyay 'there'."),
    row(12, "The pumice stone is there.", "Jundi yinyay.", "jundi yinyay | pumice.stone there", "r207_deictic_there_jundi", ["jundi", "yinyay"], "Dictionary jundi noun 'pumice stone'; Patz §3.7 yinyay 'there'."),
    row(13, "The war spear is there.", "Dikarra yinyay.", "dikarra yinyay | war.spear there", "r207_deictic_there_dikarra", ["dikarra", "yinyay"], "Dictionary dikarra noun 'war spear'; Patz §3.7 yinyay 'there'."),
    row(14, "The many-pronged fish spear is there.", "Yirrmba yinyay.", "yirrmba yinyay | many.pronged.fish.spear there", "r207_deictic_there_yirrmba", ["yirrmba", "yinyay"], "Dictionary yirrmba noun 'many-pronged fish spear'; Patz §3.7 yinyay 'there'."),
    row(15, "The spear thrower is there.", "Balur yinyay.", "balur yinyay | spear.thrower there", "r207_deictic_there_balur", ["balur", "yinyay"], "Dictionary balur noun 'spear thrower'; Patz §3.7 yinyay 'there'."),
    row(16, "The stick is there.", "Kimil yinyay.", "kimil yinyay | stick there", "r207_deictic_there_kimil", ["kimil", "yinyay"], "Dictionary kimil noun 'stick'; Patz §3.7 yinyay 'there'."),
    row(17, "The rudder is there.", "Binan yinyay.", "binan yinyay | rudder there", "r207_deictic_there_binan", ["binan", "yinyay"], "Dictionary binan noun 'rudder'; Patz §3.7 yinyay 'there'."),
    row(18, "The sponge is there.", "Wuju yinyay.", "wuju yinyay | sponge there", "r207_deictic_there_wuju", ["wuju", "yinyay"], "Dictionary wuju noun 'sponge'; Patz §3.7 yinyay 'there'."),
    row(19, "The billycan is there.", "Jungkalu yinyay.", "jungkalu yinyay | billycan there", "r207_deictic_there_jungkalu", ["jungkalu", "yinyay"], "Dictionary jungkalu noun 'billycan'; Patz §3.7 yinyay 'there'."),
    row(20, "The paper is there.", "Kaban yinyay.", "kaban yinyay | paper there", "r207_deictic_there_kaban", ["kaban", "yinyay"], "Dictionary kaban noun 'paper, book'; selected paper only; Patz §3.7 yinyay 'there'."),
    row(21, "That torch is there.", "Yinya jidi yinyay.", "yinya jidi yinyay | that torch there", "r207_initial_that_there_jidi", ["yinya", "jidi", "yinyay"], "Patz §3.7 yinya 'that/those' can co-occur with a noun; jidi torch; yinyay there."),
    row(22, "That pumice stone is there.", "Yinya jundi yinyay.", "yinya jundi yinyay | that pumice.stone there", "r207_initial_that_there_jundi", ["yinya", "jundi", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; jundi pumice stone; yinyay there."),
    row(23, "That war spear is there.", "Yinya dikarra yinyay.", "yinya dikarra yinyay | that war.spear there", "r207_initial_that_there_dikarra", ["yinya", "dikarra", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; dikarra war spear; yinyay there."),
    row(24, "That many-pronged fish spear is there.", "Yinya yirrmba yinyay.", "yinya yirrmba yinyay | that many.pronged.fish.spear there", "r207_initial_that_there_yirrmba", ["yinya", "yirrmba", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; yirrmba many-pronged fish spear; yinyay there."),
    row(25, "That spear thrower is there.", "Yinya balur yinyay.", "yinya balur yinyay | that spear.thrower there", "r207_initial_that_there_balur", ["yinya", "balur", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; balur spear thrower; yinyay there."),
    row(26, "That stick is there.", "Yinya kimil yinyay.", "yinya kimil yinyay | that stick there", "r207_initial_that_there_kimil", ["yinya", "kimil", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; kimil stick; yinyay there."),
    row(27, "That rudder is there.", "Yinya binan yinyay.", "yinya binan yinyay | that rudder there", "r207_initial_that_there_binan", ["yinya", "binan", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; binan rudder; yinyay there."),
    row(28, "That sponge is there.", "Yinya wuju yinyay.", "yinya wuju yinyay | that sponge there", "r207_initial_that_there_wuju", ["yinya", "wuju", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; wuju sponge; yinyay there."),
    row(29, "That billycan is there.", "Yinya jungkalu yinyay.", "yinya jungkalu yinyay | that billycan there", "r207_initial_that_there_jungkalu", ["yinya", "jungkalu", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; jungkalu billycan; yinyay there."),
    row(30, "That paper is there.", "Yinya kaban yinyay.", "yinya kaban yinyay | that paper there", "r207_initial_that_there_kaban", ["yinya", "kaban", "yinyay"], "Patz §3.7 yinya can co-occur with a noun; kaban paper; yinyay there."),
    row(31, "That torch is there.", "Jidi yinya yinyay.", "jidi yinya yinyay | torch that there", "r207_postposed_that_there_jidi", ["jidi", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun as in karrkay yinya; jidi torch; yinyay there."),
    row(32, "That pumice stone is there.", "Jundi yinya yinyay.", "jundi yinya yinyay | pumice.stone that there", "r207_postposed_that_there_jundi", ["jundi", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; jundi pumice stone; yinyay there."),
    row(33, "That war spear is there.", "Dikarra yinya yinyay.", "dikarra yinya yinyay | war.spear that there", "r207_postposed_that_there_dikarra", ["dikarra", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; dikarra war spear; yinyay there."),
    row(34, "That many-pronged fish spear is there.", "Yirrmba yinya yinyay.", "yirrmba yinya yinyay | many.pronged.fish.spear that there", "r207_postposed_that_there_yirrmba", ["yirrmba", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; yirrmba many-pronged fish spear; yinyay there."),
    row(35, "That spear thrower is there.", "Balur yinya yinyay.", "balur yinya yinyay | spear.thrower that there", "r207_postposed_that_there_balur", ["balur", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; balur spear thrower; yinyay there."),
    row(36, "That stick is there.", "Kimil yinya yinyay.", "kimil yinya yinyay | stick that there", "r207_postposed_that_there_kimil", ["kimil", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; kimil stick; yinyay there."),
    row(37, "That rudder is there.", "Binan yinya yinyay.", "binan yinya yinyay | rudder that there", "r207_postposed_that_there_binan", ["binan", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; binan rudder; yinyay there."),
    row(38, "That sponge is there.", "Wuju yinya yinyay.", "wuju yinya yinyay | sponge that there", "r207_postposed_that_there_wuju", ["wuju", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; wuju sponge; yinyay there."),
    row(39, "That billycan is there.", "Jungkalu yinya yinyay.", "jungkalu yinya yinyay | billycan that there", "r207_postposed_that_there_jungkalu", ["jungkalu", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; jungkalu billycan; yinyay there."),
    row(40, "That paper is there.", "Kaban yinya yinyay.", "kaban yinya yinyay | paper that there", "r207_postposed_that_there_kaban", ["kaban", "yinya", "yinyay"], "Patz §3.7 permits yinya after a noun; kaban paper; yinyay there."),
    row(41, "That torch is here.", "Yinya jidi yaluy.", "yinya jidi yaluy | that torch here", "r207_initial_that_here_jidi", ["yinya", "jidi", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; jidi torch; yaluy here."),
    row(42, "That pumice stone is here.", "Yinya jundi yaluy.", "yinya jundi yaluy | that pumice.stone here", "r207_initial_that_here_jundi", ["yinya", "jundi", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; jundi pumice stone; yaluy here."),
    row(43, "That war spear is here.", "Yinya dikarra yaluy.", "yinya dikarra yaluy | that war.spear here", "r207_initial_that_here_dikarra", ["yinya", "dikarra", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; dikarra war spear; yaluy here."),
    row(44, "That many-pronged fish spear is here.", "Yinya yirrmba yaluy.", "yinya yirrmba yaluy | that many.pronged.fish.spear here", "r207_initial_that_here_yirrmba", ["yinya", "yirrmba", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; yirrmba many-pronged fish spear; yaluy here."),
    row(45, "That spear thrower is here.", "Yinya balur yaluy.", "yinya balur yaluy | that spear.thrower here", "r207_initial_that_here_balur", ["yinya", "balur", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; balur spear thrower; yaluy here."),
    row(46, "That stick is here.", "Yinya kimil yaluy.", "yinya kimil yaluy | that stick here", "r207_initial_that_here_kimil", ["yinya", "kimil", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; kimil stick; yaluy here."),
    row(47, "That rudder is here.", "Yinya binan yaluy.", "yinya binan yaluy | that rudder here", "r207_initial_that_here_binan", ["yinya", "binan", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; binan rudder; yaluy here."),
    row(48, "That sponge is here.", "Yinya wuju yaluy.", "yinya wuju yaluy | that sponge here", "r207_initial_that_here_wuju", ["yinya", "wuju", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; wuju sponge; yaluy here."),
    row(49, "That billycan is here.", "Yinya jungkalu yaluy.", "yinya jungkalu yaluy | that billycan here", "r207_initial_that_here_jungkalu", ["yinya", "jungkalu", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; jungkalu billycan; yaluy here."),
    row(50, "That paper is here.", "Yinya kaban yaluy.", "yinya kaban yaluy | that paper here", "r207_initial_that_here_kaban", ["yinya", "kaban", "yaluy"], "Patz §3.7 yinya can co-occur with a noun; kaban paper; yaluy here."),
]


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


def has_target_morphology(low: str, head: str) -> bool:
    suffixes = "ngka|ngku|angka|nga|ngu|ba|mba|mbu|muny|darr|burr|bu|ka|nka|nku|ji|jirr|ku|mundu|amu|mu|iji|nda|ndu|anda|undu|unji|nji|kari|baka|baja|bungal|bungan|mal|man|rriny"
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:{suffixes})\b", low) is not None


def english_has_denied(english_low: str, denied: str) -> bool:
    return denied in english_low if " " in denied else re.search(rf"\b{re.escape(denied)}\b", english_low) is not None


def scan_static() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"{LABEL}: expected 50 rows, found {len(ROWS)}")
    seen_ku: set[str] = set()
    for expected, row_data in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row_data
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        english_low = english.lower()
        words = json.loads(words_used)
        if seq != expected:
            issues.append(f"{label}: sequence mismatch; expected {expected}")
        if not english[:1].isupper() or english[-1:] not in ".!?":
            issues.append(f"{label}: English punctuation/capitalization failure")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        if not frame.startswith("r207_"):
            issues.append(f"{label}: Rule 207 frame must start with r207_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: no loans are admitted in Rule 207")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied carry-forward token {denied!r}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 207 target noun, found {targets}")
        for head in targets:
            selected = TARGET_HEADS[head]
            dotted = selected.replace("-", ".").replace(" ", ".")
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if selected not in english_low:
                issues.append(f"{label}: English must include selected sense {selected!r}")
            if selected not in analysis and dotted not in analysis:
                issues.append(f"{label}: analysis must gloss {head} as {selected}")
            if has_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        for support in SUPPORT:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if 1 <= seq <= 10 and (has_token(low, "yinya") or has_token(low, "yinyay") or not low.endswith(" yaluy.")):
            issues.append(f"{label}: rows 1-10 require TARGET yaluy")
        if 11 <= seq <= 20 and (has_token(low, "yinya") or has_token(low, "yaluy") or not low.endswith(" yinyay.")):
            issues.append(f"{label}: rows 11-20 require TARGET yinyay")
        if 21 <= seq <= 30 and (not low.startswith("yinya ") or has_token(low, "yaluy") or not low.endswith(" yinyay.")):
            issues.append(f"{label}: rows 21-30 require Yinya TARGET yinyay")
        if 31 <= seq <= 40 and (has_token(low, "yaluy") or not has_token(low, "yinya") or not low.endswith(" yinyay.") or low.startswith("yinya ")):
            issues.append(f"{label}: rows 31-40 require TARGET yinya yinyay")
        if 41 <= seq <= 50 and (not low.startswith("yinya ") or has_token(low, "yinyay") or not low.endswith(" yaluy.")):
            issues.append(f"{label}: rows 41-50 require Yinya TARGET yaluy")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied action/event/source/use/body/social field in English")
        if not evidence or not confidence:
            issues.append(f"{label}: missing evidence/confidence")
    return issues


def main() -> int:
    issues = scan_static()
    if issues:
        print("pre-bank scan failed:")
        for issue in issues:
            print(f"  - {issue}")
        return 1

    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys = ON")
    db.execute("BEGIN")
    ensure_schema(db)
    existing = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()
    if existing:
        batch_id = existing[0]
        accepted = db.execute(
            "SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status IN ('accepted','revised')",
            (batch_id,),
        ).fetchone()[0]
        if accepted:
            raise SystemExit(f"{LABEL} already has accepted/revised rows; refusing to overwrite")
        db.execute("DELETE FROM reviews WHERE sentence_id IN (SELECT id FROM sentences WHERE batch_id=?)", (batch_id,))
        db.execute("DELETE FROM sentences WHERE batch_id=?", (batch_id,))
        db.execute("UPDATE batches SET notes=?, created_at=? WHERE id=?", ("rebanked under_review", NOW, batch_id))
    else:
        cur = db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (
                LABEL,
                "Rule 207 draft: low-coverage object reference lexical expansion after Rule 206",
                "operator-guide-2026-07-08",
                "under_review",
                NOW,
            ),
        )
        batch_id = cur.lastrowid

    dupes: list[str] = []
    for seq, _english, kuku, *_rest in ROWS:
        hit = db.execute(
            """
            SELECT b.label, s.seq
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(trim(s.kuku))=lower(trim(?))
              AND s.status!='rejected'
              AND s.batch_id!=?
            """,
            (kuku, batch_id),
        ).fetchone()
        if hit:
            dupes.append(f"{LABEL}.{seq:02d} duplicates {hit[0]}.{hit[1]:02d}: {kuku}")
    if dupes:
        db.rollback()
        print("cross-corpus duplicate check failed:")
        for dupe in dupes:
            print(f"  - {dupe}")
        return 1

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, author, created_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (batch_id, *row_data[:-1], "under_review", row_data[-1], "claude", NOW)
            for row_data in ROWS
        ],
    )
    verified_before, words_before = db.execute(
        """
        SELECT COUNT(*), SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
        FROM sentences
        WHERE status IN ('accepted','revised')
        """
    ).fetchone()
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-pass",
        actor="claude-main",
        detail="Checked Patz §3.7 demonstrative frames plus dictionary entries for jidi, jundi, dikarra, yirrmba, balur, kimil, binan, wuju, jungkalu, and kaban. Selected only bare object-reference readings.",
        command="rg/sed dictionary.yaml and grammar.md around selected entries and demonstrative grammar",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="candidate-rejection",
        actor="claude-main",
        detail="Rejected yulal and durray for exact deictic collisions; rejected bikirr and wukurr for lack of exact source-backed dictionary headwords; avoided homograph, loan, source/use, food, ceremony, death, and action-heavy candidates.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="duplicate-preflight",
        actor="claude-main",
        detail="Exact duplicate preflight over all 50 planned Rule 207 Kuku surfaces returned no accepted, revised, or under-review collisions after final target selection.",
        command="sqlite3 duplicate-preflight over planned surfaces",
        output_excerpt="No collisions.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="bank_249.py static guards passed: 50 explicit rows, exactly one target noun per row, support words recorded, correct five frame bands, no target morphology, no denied carry-forward Kuku/English fields, no loans.",
        artifact_path="batch_scripts/bank_249.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: rows=50, under_review=50, verified_before_close={verified_before}, words_before={words_before}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
