#!/usr/bin/env python3
"""Bank batch-281: Rule 239 dwelling and household construction consolidation."""

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-281"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-281-dwelling-household-construction-consolidation-after-rule238-process.md"

TARGET_HEADS = {
    "bakarr": "ridge pole",
    "bayan": "house",
    "bira": "windbreak",
    "jiji": "roof",
    "kilili": "windbreak",
    "wurun": "humpy",
    "jakway": "door",
    "yulal": "board",
    "kararr": "blanket",
    "bilangkurr": "blanket",
}
SUPPORT = {"ngayu", "nyulu", "yurra", "bula", "jana", "yaluy", "yinyay", "nyajil"}
PRONOUN_SUPPORT = {"ngayu", "nyulu", "yurra", "bula", "jana"}
DENY_KUKU = {
    # Rule 238 rejected homographs and source-prose traps remain closed.
    "yamba", "yamba-kari", "yamba-yamba", "jakal", "warrbi", "dubal", "kakarr",
    "karrngka", "dimbi", "wanjakan", "walal", "walan", "wadil", "ngaral",
    "ngaran", "yuru-dudal", "balanbalan", "balibali", "murba",
    "balkan", "buyku", "dumbul", "juku", "jikan", "kaji", "walngkan-daman",
    "walngkan", "nandan", "wunanka", "bundanday", "warngku", "wundi",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "nganya", "nyunguny", "yurrangan", "yurraniny", "yurranka", "yurranda",
    "yurranji", "yurranga", "bulangan", "bulaniny", "bulanka", "bulanda",
    "bulanji", "bulanga", "ianangan", "iananiny", "iananka", "iananda",
    "iananji", "iananga", "yinyangka", "yinyayngka", "yinyayanka", "yinyamba",
    "yinyayanda", "yinyamuny", "yinyamundu", "yinyanji", "yinyarriny",
    "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji",
    # Recent fields remain closed.
    "baya", "jimal", "jirramar", "kubu", "kunjin", "miyilkura", "ngalku",
    "ngaluri", "ngunjil", "wula", "bandin", "bari", "bibar", "birri",
    "jiba", "jandal", "jibar", "kangkul", "kuwinyu", "waba",
}
DENY_ENGLISH = (
    "build", "built", "building", "make", "made", "making", "set up", "camp out",
    "camping", "sleep", "sleeping", "slept", "lie on", "lying", "spread", "spreading",
    "cover", "covered", "covering", "warm", "warming", "cold", "rain", "storm",
    "bark", "grass", "sapling", "timber", "wood", "roofing", "front", "open",
    "opened", "close", "closed", "shut", "nail", "nailed", "carpenter", "plank",
    "round stick", "opium", "none", "nothing", "mouse", "axe", "old house",
    "place name", "wall", "crack", "floor", "inside", "outside", "own", "my ",
    "your ", "his ", "her ", "their ", "possess", "owner", "case", "compound",
    "loan", "ceremony", "spirit", "dead", "corpse", "purify", "smoke out",
)


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, 3, js(words), js([]), evidence, "0.86")


EVIDENCE = {
    "bakarr": "Rule 239 retains the Rule 238 dictionary-backed target bakarr 'ridge pole' and changes only the support frame. Patz 3.5 Table 3.6 sources bare nominative ngayu, nyulu, yurra, bula, and jana; Patz 3.7 sources yaluy/yinyay; Patz 3.8.3.2 sources nyaji-l 'see'. Ridge-pole reference/perception only.",
    "bayan": "Rule 239 retains Rule 238 bayan 'house' from the dictionary and Patz 3.1.2 generic bayan 'camp, house, shelter'. Patz 3.5, 3.7, and 3.8.3.2 source the support forms. House reference/perception only; no camping, building, possession, or locative row.",
    "bira": "Rule 239 retains Rule 238 bira 'windbreak'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the pronoun/local-demonstrative/perception support. Windbreak reference/perception only.",
    "jiji": "Rule 239 retains Rule 238 jiji 'roof'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Roof reference/perception only; top-of-anything extension closed.",
    "kilili": "Rule 239 retains Rule 238 kilili 'windbreak'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Windbreak reference/perception only.",
    "wurun": "Rule 239 retains Rule 238 wurun 'humpy'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Humpy reference/perception only; halfway/building/sleeping/rain-shelter rows closed.",
    "jakway": "Rule 239 retains Rule 238 jakway 'door'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Door reference/perception only; front/open/close/shut and jakal homograph closed.",
    "yulal": "Rule 239 retains Rule 238 yulal 'board'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Board reference/perception only; plank/round-stick/nailing/building rows closed.",
    "kararr": "Rule 239 retains Rule 238 grammar-added Patz-backed kararr 'blanket'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Blanket reference/perception only; sheet/grass-bedding/spread/sleep/cover/warm/year-counting rows closed.",
    "bilangkurr": "Rule 239 retains Rule 238 bilangkurr 'blanket'. Patz 3.5 Table 3.6, 3.7, and 3.8.3.2 source the support forms. Blanket reference/perception only; clothing/sleep/cover/warm/ownership rows closed.",
}


ROWS = [
    row(1, "I see the ridge pole there.", "Ngayu bakarr yinyay nyajil.", "ngayu bakarr yinyay nyajil | 1sg.NOM ridge.pole there see.NPST", "r239_1sg_target_there_see_bakarr", ["ngayu", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(2, "I see the house there.", "Ngayu bayan yinyay nyajil.", "ngayu bayan yinyay nyajil | 1sg.NOM house there see.NPST", "r239_1sg_target_there_see_bayan", ["ngayu", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(3, "I see the windbreak there.", "Ngayu bira yinyay nyajil.", "ngayu bira yinyay nyajil | 1sg.NOM windbreak there see.NPST", "r239_1sg_target_there_see_bira", ["ngayu", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(4, "I see the roof there.", "Ngayu jiji yinyay nyajil.", "ngayu jiji yinyay nyajil | 1sg.NOM roof there see.NPST", "r239_1sg_target_there_see_jiji", ["ngayu", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(5, "I see the windbreak there.", "Ngayu kilili yinyay nyajil.", "ngayu kilili yinyay nyajil | 1sg.NOM windbreak there see.NPST", "r239_1sg_target_there_see_kilili", ["ngayu", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(6, "I see the humpy there.", "Ngayu wurun yinyay nyajil.", "ngayu wurun yinyay nyajil | 1sg.NOM humpy there see.NPST", "r239_1sg_target_there_see_wurun", ["ngayu", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(7, "I see the door there.", "Ngayu jakway yinyay nyajil.", "ngayu jakway yinyay nyajil | 1sg.NOM door there see.NPST", "r239_1sg_target_there_see_jakway", ["ngayu", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(8, "I see the board there.", "Ngayu yulal yinyay nyajil.", "ngayu yulal yinyay nyajil | 1sg.NOM board there see.NPST", "r239_1sg_target_there_see_yulal", ["ngayu", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(9, "I see the blanket there.", "Ngayu kararr yinyay nyajil.", "ngayu kararr yinyay nyajil | 1sg.NOM blanket there see.NPST", "r239_1sg_target_there_see_kararr", ["ngayu", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(10, "I see the blanket there.", "Ngayu bilangkurr yinyay nyajil.", "ngayu bilangkurr yinyay nyajil | 1sg.NOM blanket there see.NPST", "r239_1sg_target_there_see_bilangkurr", ["ngayu", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(11, "He sees the ridge pole here.", "Nyulu bakarr yaluy nyajil.", "nyulu bakarr yaluy nyajil | 3sg.NOM ridge.pole here see.NPST", "r239_3sg_target_here_see_bakarr", ["nyulu", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(12, "He sees the house here.", "Nyulu bayan yaluy nyajil.", "nyulu bayan yaluy nyajil | 3sg.NOM house here see.NPST", "r239_3sg_target_here_see_bayan", ["nyulu", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(13, "He sees the windbreak here.", "Nyulu bira yaluy nyajil.", "nyulu bira yaluy nyajil | 3sg.NOM windbreak here see.NPST", "r239_3sg_target_here_see_bira", ["nyulu", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(14, "He sees the roof here.", "Nyulu jiji yaluy nyajil.", "nyulu jiji yaluy nyajil | 3sg.NOM roof here see.NPST", "r239_3sg_target_here_see_jiji", ["nyulu", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(15, "He sees the windbreak here.", "Nyulu kilili yaluy nyajil.", "nyulu kilili yaluy nyajil | 3sg.NOM windbreak here see.NPST", "r239_3sg_target_here_see_kilili", ["nyulu", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(16, "He sees the humpy here.", "Nyulu wurun yaluy nyajil.", "nyulu wurun yaluy nyajil | 3sg.NOM humpy here see.NPST", "r239_3sg_target_here_see_wurun", ["nyulu", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(17, "He sees the door here.", "Nyulu jakway yaluy nyajil.", "nyulu jakway yaluy nyajil | 3sg.NOM door here see.NPST", "r239_3sg_target_here_see_jakway", ["nyulu", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(18, "He sees the board here.", "Nyulu yulal yaluy nyajil.", "nyulu yulal yaluy nyajil | 3sg.NOM board here see.NPST", "r239_3sg_target_here_see_yulal", ["nyulu", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(19, "He sees the blanket here.", "Nyulu kararr yaluy nyajil.", "nyulu kararr yaluy nyajil | 3sg.NOM blanket here see.NPST", "r239_3sg_target_here_see_kararr", ["nyulu", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(20, "He sees the blanket here.", "Nyulu bilangkurr yaluy nyajil.", "nyulu bilangkurr yaluy nyajil | 3sg.NOM blanket here see.NPST", "r239_3sg_target_here_see_bilangkurr", ["nyulu", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(21, "You all see the ridge pole there.", "Yurra bakarr yinyay nyajil.", "yurra bakarr yinyay nyajil | 2pl.NOM ridge.pole there see.NPST", "r239_2pl_target_there_see_bakarr", ["yurra", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(22, "You all see the house there.", "Yurra bayan yinyay nyajil.", "yurra bayan yinyay nyajil | 2pl.NOM house there see.NPST", "r239_2pl_target_there_see_bayan", ["yurra", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(23, "You all see the windbreak there.", "Yurra bira yinyay nyajil.", "yurra bira yinyay nyajil | 2pl.NOM windbreak there see.NPST", "r239_2pl_target_there_see_bira", ["yurra", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(24, "You all see the roof there.", "Yurra jiji yinyay nyajil.", "yurra jiji yinyay nyajil | 2pl.NOM roof there see.NPST", "r239_2pl_target_there_see_jiji", ["yurra", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(25, "You all see the windbreak there.", "Yurra kilili yinyay nyajil.", "yurra kilili yinyay nyajil | 2pl.NOM windbreak there see.NPST", "r239_2pl_target_there_see_kilili", ["yurra", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(26, "You all see the humpy there.", "Yurra wurun yinyay nyajil.", "yurra wurun yinyay nyajil | 2pl.NOM humpy there see.NPST", "r239_2pl_target_there_see_wurun", ["yurra", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(27, "You all see the door there.", "Yurra jakway yinyay nyajil.", "yurra jakway yinyay nyajil | 2pl.NOM door there see.NPST", "r239_2pl_target_there_see_jakway", ["yurra", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(28, "You all see the board there.", "Yurra yulal yinyay nyajil.", "yurra yulal yinyay nyajil | 2pl.NOM board there see.NPST", "r239_2pl_target_there_see_yulal", ["yurra", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(29, "You all see the blanket there.", "Yurra kararr yinyay nyajil.", "yurra kararr yinyay nyajil | 2pl.NOM blanket there see.NPST", "r239_2pl_target_there_see_kararr", ["yurra", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(30, "You all see the blanket there.", "Yurra bilangkurr yinyay nyajil.", "yurra bilangkurr yinyay nyajil | 2pl.NOM blanket there see.NPST", "r239_2pl_target_there_see_bilangkurr", ["yurra", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(31, "The two of them see the ridge pole here.", "Bula bakarr yaluy nyajil.", "bula bakarr yaluy nyajil | 3du.NOM ridge.pole here see.NPST", "r239_3du_target_here_see_bakarr", ["bula", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(32, "The two of them see the house here.", "Bula bayan yaluy nyajil.", "bula bayan yaluy nyajil | 3du.NOM house here see.NPST", "r239_3du_target_here_see_bayan", ["bula", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(33, "The two of them see the windbreak here.", "Bula bira yaluy nyajil.", "bula bira yaluy nyajil | 3du.NOM windbreak here see.NPST", "r239_3du_target_here_see_bira", ["bula", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(34, "The two of them see the roof here.", "Bula jiji yaluy nyajil.", "bula jiji yaluy nyajil | 3du.NOM roof here see.NPST", "r239_3du_target_here_see_jiji", ["bula", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(35, "The two of them see the windbreak here.", "Bula kilili yaluy nyajil.", "bula kilili yaluy nyajil | 3du.NOM windbreak here see.NPST", "r239_3du_target_here_see_kilili", ["bula", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(36, "The two of them see the humpy here.", "Bula wurun yaluy nyajil.", "bula wurun yaluy nyajil | 3du.NOM humpy here see.NPST", "r239_3du_target_here_see_wurun", ["bula", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(37, "The two of them see the door here.", "Bula jakway yaluy nyajil.", "bula jakway yaluy nyajil | 3du.NOM door here see.NPST", "r239_3du_target_here_see_jakway", ["bula", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(38, "The two of them see the board here.", "Bula yulal yaluy nyajil.", "bula yulal yaluy nyajil | 3du.NOM board here see.NPST", "r239_3du_target_here_see_yulal", ["bula", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(39, "The two of them see the blanket here.", "Bula kararr yaluy nyajil.", "bula kararr yaluy nyajil | 3du.NOM blanket here see.NPST", "r239_3du_target_here_see_kararr", ["bula", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(40, "The two of them see the blanket here.", "Bula bilangkurr yaluy nyajil.", "bula bilangkurr yaluy nyajil | 3du.NOM blanket here see.NPST", "r239_3du_target_here_see_bilangkurr", ["bula", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(41, "They see the ridge pole there.", "Jana bakarr yinyay nyajil.", "jana bakarr yinyay nyajil | 3pl.NOM ridge.pole there see.NPST", "r239_3pl_target_there_see_bakarr", ["jana", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(42, "They see the house there.", "Jana bayan yinyay nyajil.", "jana bayan yinyay nyajil | 3pl.NOM house there see.NPST", "r239_3pl_target_there_see_bayan", ["jana", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(43, "They see the windbreak there.", "Jana bira yinyay nyajil.", "jana bira yinyay nyajil | 3pl.NOM windbreak there see.NPST", "r239_3pl_target_there_see_bira", ["jana", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(44, "They see the roof there.", "Jana jiji yinyay nyajil.", "jana jiji yinyay nyajil | 3pl.NOM roof there see.NPST", "r239_3pl_target_there_see_jiji", ["jana", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(45, "They see the windbreak there.", "Jana kilili yinyay nyajil.", "jana kilili yinyay nyajil | 3pl.NOM windbreak there see.NPST", "r239_3pl_target_there_see_kilili", ["jana", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(46, "They see the humpy there.", "Jana wurun yinyay nyajil.", "jana wurun yinyay nyajil | 3pl.NOM humpy there see.NPST", "r239_3pl_target_there_see_wurun", ["jana", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(47, "They see the door there.", "Jana jakway yinyay nyajil.", "jana jakway yinyay nyajil | 3pl.NOM door there see.NPST", "r239_3pl_target_there_see_jakway", ["jana", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(48, "They see the board there.", "Jana yulal yinyay nyajil.", "jana yulal yinyay nyajil | 3pl.NOM board there see.NPST", "r239_3pl_target_there_see_yulal", ["jana", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(49, "They see the blanket there.", "Jana kararr yinyay nyajil.", "jana kararr yinyay nyajil | 3pl.NOM blanket there see.NPST", "r239_3pl_target_there_see_kararr", ["jana", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(50, "They see the blanket there.", "Jana bilangkurr yinyay nyajil.", "jana bilangkurr yinyay nyajil | 3pl.NOM blanket there see.NPST", "r239_3pl_target_there_see_bilangkurr", ["jana", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
]


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|karra|manda|bulal"
    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.strip())}\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("r239_"):
            issues.append(f"{label}: Rule 239 frame must start with r239_")
        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 239")
        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 239 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")
        pronouns = [pronoun for pronoun in PRONOUN_SUPPORT if has_token(low, pronoun)]
        if len(pronouns) != 1:
            issues.append(f"{label}: rows require exactly one bare nominative pronoun, found {pronouns}")
        if 1 <= seq <= 10 and (not low.startswith("ngayu ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngayu TARGET yinyay nyajil")
        if 11 <= seq <= 20 and (not low.startswith("nyulu ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Nyulu TARGET yaluy nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yurra ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Yurra TARGET yinyay nyajil")
        if 31 <= seq <= 40 and (not low.startswith("bula ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Bula TARGET yaluy nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yinyay nyajil")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied dwelling action/use/homograph/source-prose 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 lessons WHERE batch_id=?", (batch_id,))
        db.execute("DELETE FROM sentences WHERE batch_id=?", (batch_id,))
    else:
        db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 239 draft", "operator-guide-2026-07-09", "draft under review", NOW),
        )
        batch_id = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()[0]

    collisions: list[str] = []
    for seq, _english, kuku, *_rest in ROWS:
        found = db.execute(
            """
            SELECT b.label, s.seq, s.status
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(s.kuku)=lower(?) AND s.status IN ('accepted','revised','under_review')
            """,
            (kuku,),
        ).fetchall()
        if found:
            collisions.append(f"{seq}: {kuku} -> {found}")
    if collisions:
        raise SystemExit("collision scan failed:\n" + "\n".join(collisions))

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, created_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (
                batch_id,
                seq,
                english,
                kuku,
                analysis,
                frame,
                tier,
                words_used,
                loans_used,
                evidence,
                "under_review",
                confidence,
                NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-readback",
        actor="claude-main",
        detail="Rule 239 source pass retained the ten Rule 238 dwelling/household target nouns and checked Patz 3.5 Table 3.6 for bare nominative ngayu, nyulu, yurra, bula, and jana; Patz 3.7 for yaluy/yinyay; and Patz 3.8.3.2 for nyaji-l 'see'.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="planning",
        event_type="construction-scope",
        actor="claude-main",
        detail="Selected Rule 239 construction consolidation after Rule 238: same ten dwelling/household nouns in five zero-collision nominative-pronoun perception bands. No yamba/jakal/warrbi homograph, build/set/spread/enter/nail action, source-material, use, target-case, pronoun-case, or support-inflection semantics admitted.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="coverage-audit",
        actor="claude-main",
        detail="Pre-batch token-boundary coverage among accepted/revised rows: bakarr 5, bayan 442, bira 6, jiji 5, kilili 5, wurun 28, jakway 15, yulal 15, kararr 22, and bilangkurr 22.",
        command="sqlite3 synthetic.db WITH targets(t) AS (...) SELECT t, COUNT(*) ...",
        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 239 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_281.py internal collision scan",
        output_excerpt="0",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="Static guards passed: 50 rows, exactly one Rule 239 target noun per row, exactly one bare nominative pronoun per row, no loans, no target suffixes, no excluded homograph/action/source-prose target drift, no pronoun case, no perception compound, and five planned frame bands only.",
        command="python3 batch_scripts/bank_281.py",
        artifact_path="batch_scripts/bank_281.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 hand-authored Rule 239 dwelling and household construction rows as under_review.",
        command="python3 batch_scripts/bank_281.py",
        artifact_path="batch_scripts/bank_281.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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