#!/usr/bin/env python3
"""Bank batch-278: Rule 236 fire-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-278"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-278-fire-reference-lexical-expansion-after-rule235-process.md"

TARGET_HEADS = {
    "baya": "fire",
    "jimal": "fire stick",
    "jirramar": "small sticks",
    "kubu": "smoke",
    "kunjin": "fire",
    "miyilkura": "smouldering log",
    "ngalku": "bush fire",
    "ngaluri": "flames",
    "ngunjil": "charcoal",
    "wula": "flames",
}
PLURAL_TARGETS = {"jirramar", "ngaluri", "wula"}
SUPPORT = {"yinya", "yaluy", "yinyay"}
DENY_KUKU = {
    # Fire verbs, unselected fire nouns, loans, and homograph-heavy neighbours.
    "baya-wayu", "wayu", "waju", "dalngarri", "dama-murnil", "jakalambal", "jimal-damal",
    "jimalili", "maji", "minjiji", "nulung", "nyunil", "wulay",
    # Recent body/landform targets and homographs stay closed.
    "bandin", "bari", "bibar", "birri", "jiba", "jandal", "jibar", "kangkul", "kuwinyu",
    "waba", "jibarr", "kangku", "mabil", "karrirrangkal", "jalngarr", "jinali", "yalmba",
    "kambal-kambal", "jalkar", "janku", "jamba",
}
DENY_ENGLISH = (
    "firewood", "wood", "light ", "lit ", "start", "started", "make", "made", "making", "twirl",
    "drill", "collect", "collected", "gather", "gathered", "cook", "cooked", "cooking", "food",
    "damper", "leap", "leapt", "shoot", "shooting", "spread", "burn", "burned", "burning", "clear",
    "clearing", "grassfire", "purify", "purified", "smoke out", "warm", "warmed", "urine", "pus",
    "sore", "body fluid", "ashes", "hot coals", "coals", "matches", "loan", "motor", "story",
)


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")


EVIDENCE = {
    "baya": "Dictionary baya noun gloss 'fire', definitions 'flame, fire; fire ... light a fire; firewood'. Rule 236 narrows to fire/flame reference only and does not open lighting, firewood, gathering, or cooking prose. Patz 3.1 nominal semantic domain and Patz 3.7 yinya/yaluy/yinyay.",
    "jimal": "Dictionary jimal noun gloss 'fire stick', definition 'fire stick'. Rule 236 keeps the object reference only and does not open jimal damal fire-making. Patz 3.1 and 3.7.",
    "jirramar": "Dictionary jirramar noun gloss 'chips', definition 'chips, small sticks to start a fire'. Rule 236 selects small-sticks reference only and does not open starting-fire use. Patz 3.1 and 3.7.",
    "kubu": "Dictionary kubu noun gloss 'smoke', definition 'smoke'. Rule 236 uses smoke reference only. Patz 3.1 and 3.7.",
    "kunjin": "Dictionary kunjin noun gloss 'fire', definition 'fire'. Rule 236 uses fire reference only. Patz 3.1 and 3.7.",
    "miyilkura": "Dictionary miyilkura noun gloss 'smouldering log', definition 'smouldering log'. Rule 236 uses the object/state reference only, not burning/cooking prose. Patz 3.1 and 3.7.",
    "ngalku": "Dictionary ngalku noun gloss 'bush fire', definition 'bush fire'. Rule 236 uses bush-fire reference only, not spreading/burning/clearing event prose. Patz 3.1 and 3.7.",
    "ngaluri": "Dictionary ngaluri noun gloss 'flames', definition 'flames'; usage has flames leaping up from fire. Rule 236 selects flames reference only and does not open leaping/event prose. Patz 3.1 and 3.7.",
    "ngunjil": "Dictionary ngunjil noun gloss 'charcoal', definition 'charcoal; hot coals or hot ashes'. Rule 236 narrows to charcoal reference only. Patz 3.1 and 3.7.",
    "wula": "Dictionary wula noun gloss 'flames', definition 'flames', synonym dalngarri. Rule 236 selects flames only and does not admit dalngarri's urine homograph. Patz 3.1 and 3.7.",
}


ROWS = [
    row(1, "The fire is here.", "Baya yaluy.", "baya yaluy | fire here", "r236_fire_ref_here_baya", ["baya", "yaluy"], EVIDENCE["baya"]),
    row(2, "The fire stick is here.", "Jimal yaluy.", "jimal yaluy | fire.stick here", "r236_fire_ref_here_jimal", ["jimal", "yaluy"], EVIDENCE["jimal"]),
    row(3, "The small sticks are here.", "Jirramar yaluy.", "jirramar yaluy | small.sticks here", "r236_fire_ref_here_jirramar", ["jirramar", "yaluy"], EVIDENCE["jirramar"]),
    row(4, "The smoke is here.", "Kubu yaluy.", "kubu yaluy | smoke here", "r236_fire_ref_here_kubu", ["kubu", "yaluy"], EVIDENCE["kubu"]),
    row(5, "The fire is here.", "Kunjin yaluy.", "kunjin yaluy | fire here", "r236_fire_ref_here_kunjin", ["kunjin", "yaluy"], EVIDENCE["kunjin"]),
    row(6, "The smouldering log is here.", "Miyilkura yaluy.", "miyilkura yaluy | smouldering.log here", "r236_fire_ref_here_miyilkura", ["miyilkura", "yaluy"], EVIDENCE["miyilkura"]),
    row(7, "The bush fire is here.", "Ngalku yaluy.", "ngalku yaluy | bush.fire here", "r236_fire_ref_here_ngalku", ["ngalku", "yaluy"], EVIDENCE["ngalku"]),
    row(8, "The flames are here.", "Ngaluri yaluy.", "ngaluri yaluy | flames here", "r236_fire_ref_here_ngaluri", ["ngaluri", "yaluy"], EVIDENCE["ngaluri"]),
    row(9, "The charcoal is here.", "Ngunjil yaluy.", "ngunjil yaluy | charcoal here", "r236_fire_ref_here_ngunjil", ["ngunjil", "yaluy"], EVIDENCE["ngunjil"]),
    row(10, "The flames are here.", "Wula yaluy.", "wula yaluy | flames here", "r236_fire_ref_here_wula", ["wula", "yaluy"], EVIDENCE["wula"]),
    row(11, "The fire is there.", "Baya yinyay.", "baya yinyay | fire there", "r236_fire_ref_there_baya", ["baya", "yinyay"], EVIDENCE["baya"]),
    row(12, "The fire stick is there.", "Jimal yinyay.", "jimal yinyay | fire.stick there", "r236_fire_ref_there_jimal", ["jimal", "yinyay"], EVIDENCE["jimal"]),
    row(13, "The small sticks are there.", "Jirramar yinyay.", "jirramar yinyay | small.sticks there", "r236_fire_ref_there_jirramar", ["jirramar", "yinyay"], EVIDENCE["jirramar"]),
    row(14, "The smoke is there.", "Kubu yinyay.", "kubu yinyay | smoke there", "r236_fire_ref_there_kubu", ["kubu", "yinyay"], EVIDENCE["kubu"]),
    row(15, "The fire is there.", "Kunjin yinyay.", "kunjin yinyay | fire there", "r236_fire_ref_there_kunjin", ["kunjin", "yinyay"], EVIDENCE["kunjin"]),
    row(16, "The smouldering log is there.", "Miyilkura yinyay.", "miyilkura yinyay | smouldering.log there", "r236_fire_ref_there_miyilkura", ["miyilkura", "yinyay"], EVIDENCE["miyilkura"]),
    row(17, "The bush fire is there.", "Ngalku yinyay.", "ngalku yinyay | bush.fire there", "r236_fire_ref_there_ngalku", ["ngalku", "yinyay"], EVIDENCE["ngalku"]),
    row(18, "The flames are there.", "Ngaluri yinyay.", "ngaluri yinyay | flames there", "r236_fire_ref_there_ngaluri", ["ngaluri", "yinyay"], EVIDENCE["ngaluri"]),
    row(19, "The charcoal is there.", "Ngunjil yinyay.", "ngunjil yinyay | charcoal there", "r236_fire_ref_there_ngunjil", ["ngunjil", "yinyay"], EVIDENCE["ngunjil"]),
    row(20, "The flames are there.", "Wula yinyay.", "wula yinyay | flames there", "r236_fire_ref_there_wula", ["wula", "yinyay"], EVIDENCE["wula"]),
    row(21, "That fire is there.", "Yinya baya yinyay.", "yinya baya yinyay | that fire there", "r236_that_fire_ref_there_baya", ["yinya", "baya", "yinyay"], EVIDENCE["baya"]),
    row(22, "That fire stick is there.", "Yinya jimal yinyay.", "yinya jimal yinyay | that fire.stick there", "r236_that_fire_ref_there_jimal", ["yinya", "jimal", "yinyay"], EVIDENCE["jimal"]),
    row(23, "Those small sticks are there.", "Yinya jirramar yinyay.", "yinya jirramar yinyay | those small.sticks there", "r236_that_fire_ref_there_jirramar", ["yinya", "jirramar", "yinyay"], EVIDENCE["jirramar"]),
    row(24, "That smoke is there.", "Yinya kubu yinyay.", "yinya kubu yinyay | that smoke there", "r236_that_fire_ref_there_kubu", ["yinya", "kubu", "yinyay"], EVIDENCE["kubu"]),
    row(25, "That fire is there.", "Yinya kunjin yinyay.", "yinya kunjin yinyay | that fire there", "r236_that_fire_ref_there_kunjin", ["yinya", "kunjin", "yinyay"], EVIDENCE["kunjin"]),
    row(26, "That smouldering log is there.", "Yinya miyilkura yinyay.", "yinya miyilkura yinyay | that smouldering.log there", "r236_that_fire_ref_there_miyilkura", ["yinya", "miyilkura", "yinyay"], EVIDENCE["miyilkura"]),
    row(27, "That bush fire is there.", "Yinya ngalku yinyay.", "yinya ngalku yinyay | that bush.fire there", "r236_that_fire_ref_there_ngalku", ["yinya", "ngalku", "yinyay"], EVIDENCE["ngalku"]),
    row(28, "Those flames are there.", "Yinya ngaluri yinyay.", "yinya ngaluri yinyay | those flames there", "r236_that_fire_ref_there_ngaluri", ["yinya", "ngaluri", "yinyay"], EVIDENCE["ngaluri"]),
    row(29, "That charcoal is there.", "Yinya ngunjil yinyay.", "yinya ngunjil yinyay | that charcoal there", "r236_that_fire_ref_there_ngunjil", ["yinya", "ngunjil", "yinyay"], EVIDENCE["ngunjil"]),
    row(30, "Those flames are there.", "Yinya wula yinyay.", "yinya wula yinyay | those flames there", "r236_that_fire_ref_there_wula", ["yinya", "wula", "yinyay"], EVIDENCE["wula"]),
    row(31, "That fire is there.", "Baya yinya yinyay.", "baya yinya yinyay | fire that there", "r236_fire_that_there_baya", ["baya", "yinya", "yinyay"], EVIDENCE["baya"]),
    row(32, "That fire stick is there.", "Jimal yinya yinyay.", "jimal yinya yinyay | fire.stick that there", "r236_fire_that_there_jimal", ["jimal", "yinya", "yinyay"], EVIDENCE["jimal"]),
    row(33, "Those small sticks are there.", "Jirramar yinya yinyay.", "jirramar yinya yinyay | small.sticks that there", "r236_fire_that_there_jirramar", ["jirramar", "yinya", "yinyay"], EVIDENCE["jirramar"]),
    row(34, "That smoke is there.", "Kubu yinya yinyay.", "kubu yinya yinyay | smoke that there", "r236_fire_that_there_kubu", ["kubu", "yinya", "yinyay"], EVIDENCE["kubu"]),
    row(35, "That fire is there.", "Kunjin yinya yinyay.", "kunjin yinya yinyay | fire that there", "r236_fire_that_there_kunjin", ["kunjin", "yinya", "yinyay"], EVIDENCE["kunjin"]),
    row(36, "That smouldering log is there.", "Miyilkura yinya yinyay.", "miyilkura yinya yinyay | smouldering.log that there", "r236_fire_that_there_miyilkura", ["miyilkura", "yinya", "yinyay"], EVIDENCE["miyilkura"]),
    row(37, "That bush fire is there.", "Ngalku yinya yinyay.", "ngalku yinya yinyay | bush.fire that there", "r236_fire_that_there_ngalku", ["ngalku", "yinya", "yinyay"], EVIDENCE["ngalku"]),
    row(38, "Those flames are there.", "Ngaluri yinya yinyay.", "ngaluri yinya yinyay | flames that there", "r236_fire_that_there_ngaluri", ["ngaluri", "yinya", "yinyay"], EVIDENCE["ngaluri"]),
    row(39, "That charcoal is there.", "Ngunjil yinya yinyay.", "ngunjil yinya yinyay | charcoal that there", "r236_fire_that_there_ngunjil", ["ngunjil", "yinya", "yinyay"], EVIDENCE["ngunjil"]),
    row(40, "Those flames are there.", "Wula yinya yinyay.", "wula yinya yinyay | flames that there", "r236_fire_that_there_wula", ["wula", "yinya", "yinyay"], EVIDENCE["wula"]),
    row(41, "That fire is here.", "Yinya baya yaluy.", "yinya baya yaluy | that fire here", "r236_that_fire_ref_here_baya", ["yinya", "baya", "yaluy"], EVIDENCE["baya"]),
    row(42, "That fire stick is here.", "Yinya jimal yaluy.", "yinya jimal yaluy | that fire.stick here", "r236_that_fire_ref_here_jimal", ["yinya", "jimal", "yaluy"], EVIDENCE["jimal"]),
    row(43, "Those small sticks are here.", "Yinya jirramar yaluy.", "yinya jirramar yaluy | those small.sticks here", "r236_that_fire_ref_here_jirramar", ["yinya", "jirramar", "yaluy"], EVIDENCE["jirramar"]),
    row(44, "That smoke is here.", "Yinya kubu yaluy.", "yinya kubu yaluy | that smoke here", "r236_that_fire_ref_here_kubu", ["yinya", "kubu", "yaluy"], EVIDENCE["kubu"]),
    row(45, "That fire is here.", "Yinya kunjin yaluy.", "yinya kunjin yaluy | that fire here", "r236_that_fire_ref_here_kunjin", ["yinya", "kunjin", "yaluy"], EVIDENCE["kunjin"]),
    row(46, "That smouldering log is here.", "Yinya miyilkura yaluy.", "yinya miyilkura yaluy | that smouldering.log here", "r236_that_fire_ref_here_miyilkura", ["yinya", "miyilkura", "yaluy"], EVIDENCE["miyilkura"]),
    row(47, "That bush fire is here.", "Yinya ngalku yaluy.", "yinya ngalku yaluy | that bush.fire here", "r236_that_fire_ref_here_ngalku", ["yinya", "ngalku", "yaluy"], EVIDENCE["ngalku"]),
    row(48, "Those flames are here.", "Yinya ngaluri yaluy.", "yinya ngaluri yaluy | those flames here", "r236_that_fire_ref_here_ngaluri", ["yinya", "ngaluri", "yaluy"], EVIDENCE["ngaluri"]),
    row(49, "That charcoal is here.", "Yinya ngunjil yaluy.", "yinya ngunjil yaluy | that charcoal here", "r236_that_fire_ref_here_ngunjil", ["yinya", "ngunjil", "yaluy"], EVIDENCE["ngunjil"]),
    row(50, "Those flames are here.", "Yinya wula yaluy.", "yinya wula yaluy | those flames here", "r236_that_fire_ref_here_wula", ["yinya", "wula", "yaluy"], EVIDENCE["wula"]),
]


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("r236_"):
            issues.append(f"{label}: Rule 236 frame must start with r236_")
        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 236")
        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 236 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")
            if head in PLURAL_TARGETS and selected not in english_low:
                issues.append(f"{label}: plural lexical target {head} must stay lexical/contextual")
        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.") or len(low.split()) != 2):
            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.") or len(low.split()) != 2):
            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.") or len(low.split()) != 3):
            issues.append(f"{label}: rows 21-30 require Yinya TARGET yinyay")
        if 31 <= seq <= 40 and (not has_token(low, "yinya") or has_token(low, "yaluy") or not low.endswith(" yinyay.") or len(low.split()) != 3 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.") or len(low.split()) != 3):
            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 fire action/use/homograph 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 236 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 236 source pass checked Patz 3.1 for nouns as terms for objects/weather/human habitat, Patz 3.7 for yinya/yaluy/yinyay, and dictionary entries for baya, jimal, jirramar, kubu, kunjin, miyilkura, ngalku, ngaluri, ngunjil, and wula.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="planning",
        event_type="lexical-scope",
        actor="claude-main",
        detail="Selected Rule 236 fire-reference lexical expansion after Rule 235: ten fire-domain nouns in five deictic/demonstrative reference bands. No fire-making, cooking, burning, clearing, collecting, use, body-fluid homograph, loan, or event 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 coverage among accepted/revised rows: baya 444, jimal 28, jirramar 0, kubu 54, kunjin 6, miyilkura 0, ngalku 0, ngaluri 0, ngunjil 63, wula 44.",
        command="sqlite3 synthetic.db WITH heads(h) AS (...) SELECT h, token_hits ...",
        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 236 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="sqlite3 synthetic.db WITH planned(kuku) AS (...) SELECT COUNT(*) ...",
        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 236 target per row, no loans, no target suffixes, no denied recent target drift, no fire action/use/homograph English, and five planned deictic/demonstrative frame bands only.",
        command="python3 batch_scripts/bank_278.py",
        artifact_path="batch_scripts/bank_278.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 236 fire-reference lexical rows as under_review.",
        command="python3 batch_scripts/bank_278.py",
        artifact_path="batch_scripts/bank_278.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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