#!/usr/bin/env python3
"""Bank Batch 391: Rule 349 complementary bird-set-D non-singular past perception."""

from __future__ import annotations

import ast
import datetime as dt
import json
from pathlib import Path
import re
import sqlite3

from bank_386 import SUPPORT_EVIDENCE as PRONOUN_EVIDENCE, source_audit as pronoun_source_audit
from bank_388 import (
    DENY_ENGLISH,
    EXPECTED_HASHES,
    SUPPORT_EVIDENCE as BIRD_SUPPORT_EVIDENCE,
    TARGET_CONFIDENCE,
    TARGET_EVIDENCE as BIRD_TARGET_EVIDENCE,
    TARGET_GLOSSES,
    TARGET_LABELS,
    TARGET_ORDER,
    count_words,
    source_audit as bird_source_audit,
)
from bank_390 import DENY_KUKU
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-391"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-391-complementary-bird-set-d-nonsingular-past-perception-after-rule348-process.md"
EXPECTED_TARGET_COVERAGE = {
    "diwalngku": 20, "bujil-yiran": 20, "karrmbil": 20, "yiki": 20, "bajal": 21,
    "biru-biru": 21, "jangkan": 21, "kurranday": 21, "ngarkay": 21, "wakuka": 22,
}
EXPECTED_SUPPORT_COVERAGE = {
    "ngali": 683, "ngaliny": 625, "yubal": 635, "yurra": 620, "nganjin": 348,
    "ngana": 1115, "yaluy": 4612, "yinyay": 4774, "nyajin": 2500,
}
FRAME_SPECS = [
    ("ngali", "yinyay", "We two, not you, saw", "there", "1du.excl"),
    ("ngaliny", "yaluy", "You and I saw", "here", "1du.incl"),
    ("yubal", "yinyay", "You two saw", "there", "2du"),
    ("yurra", "yaluy", "You all saw", "here", "2pl"),
    ("nganjin", "yinyay", "We, not you, saw", "there", "1pl.excl"),
]
OPPOSITE = {"yaluy": "yinyay", "yinyay": "yaluy"}
TARGET_EVIDENCE = {
    target: (
        f"{BIRD_TARGET_EVIDENCE[target]} Rule 349 changes deictic location only and preserves every "
        "Rule 346-348 card, sense, and source boundary."
    )
    for target in TARGET_ORDER
}
SUPPORT_EVIDENCE = (
    f"{BIRD_SUPPORT_EVIDENCE} {PRONOUN_EVIDENCE} Rule 349 is the exact deictic complement of Rule 348; "
    "Patz controls clusivity, and deferred ngana remains grammatical but unselected."
)


def row(seq: int, english: str, kuku: str, analysis: str, frame: str, words: list[str]) -> tuple:
    target = words[1]
    return (
        seq, english, kuku, analysis, frame, 3, json.dumps(words, ensure_ascii=False), json.dumps([], ensure_ascii=False),
        f"{TARGET_EVIDENCE[target]} {SUPPORT_EVIDENCE}", TARGET_CONFIDENCE[target],
    )


ROWS = [
    row(1, "We two, not you, saw the diwalngku bird there.", "Ngali diwalngku yinyay nyajin.", "ngali diwalngku yinyay nyajin | 1du.excl.NOM diwalngku.bird there see.PST", "r349_1du_excl_there_bird_d_saw_diwalngku", ["ngali", "diwalngku", "yinyay", "nyajin"]),
    row(2, "We two, not you, saw the bujil-yiran ibis there.", "Ngali bujil-yiran yinyay nyajin.", "ngali bujil-yiran yinyay nyajin | 1du.excl.NOM bujil-yiran.ibis there see.PST", "r349_1du_excl_there_bird_d_saw_bujil_yiran", ["ngali", "bujil-yiran", "yinyay", "nyajin"]),
    row(3, "We two, not you, saw the karrmbil parakeet there.", "Ngali karrmbil yinyay nyajin.", "ngali karrmbil yinyay nyajin | 1du.excl.NOM karrmbil.parakeet there see.PST", "r349_1du_excl_there_bird_d_saw_karrmbil", ["ngali", "karrmbil", "yinyay", "nyajin"]),
    row(4, "We two, not you, saw the yiki owl there.", "Ngali yiki yinyay nyajin.", "ngali yiki yinyay nyajin | 1du.excl.NOM yiki.owl there see.PST", "r349_1du_excl_there_bird_d_saw_yiki", ["ngali", "yiki", "yinyay", "nyajin"]),
    row(5, "We two, not you, saw the bajal bower bird there.", "Ngali bajal yinyay nyajin.", "ngali bajal yinyay nyajin | 1du.excl.NOM bajal.bower-bird there see.PST", "r349_1du_excl_there_bird_d_saw_bajal", ["ngali", "bajal", "yinyay", "nyajin"]),
    row(6, "We two, not you, saw the biru-biru rainbow bird there.", "Ngali biru-biru yinyay nyajin.", "ngali biru-biru yinyay nyajin | 1du.excl.NOM biru-biru.rainbow-bird there see.PST", "r349_1du_excl_there_bird_d_saw_biru_biru", ["ngali", "biru-biru", "yinyay", "nyajin"]),
    row(7, "We two, not you, saw the jangkan swift there.", "Ngali jangkan yinyay nyajin.", "ngali jangkan yinyay nyajin | 1du.excl.NOM jangkan.swift there see.PST", "r349_1du_excl_there_bird_d_saw_jangkan", ["ngali", "jangkan", "yinyay", "nyajin"]),
    row(8, "We two, not you, saw the kurranday seagull there.", "Ngali kurranday yinyay nyajin.", "ngali kurranday yinyay nyajin | 1du.excl.NOM kurranday.seagull there see.PST", "r349_1du_excl_there_bird_d_saw_kurranday", ["ngali", "kurranday", "yinyay", "nyajin"]),
    row(9, "We two, not you, saw the ngarkay waterhen there.", "Ngali ngarkay yinyay nyajin.", "ngali ngarkay yinyay nyajin | 1du.excl.NOM ngarkay.waterhen there see.PST", "r349_1du_excl_there_bird_d_saw_ngarkay", ["ngali", "ngarkay", "yinyay", "nyajin"]),
    row(10, "We two, not you, saw the wakuka bird there.", "Ngali wakuka yinyay nyajin.", "ngali wakuka yinyay nyajin | 1du.excl.NOM wakuka.bird there see.PST", "r349_1du_excl_there_bird_d_saw_wakuka", ["ngali", "wakuka", "yinyay", "nyajin"]),
    row(11, "You and I saw the diwalngku bird here.", "Ngaliny diwalngku yaluy nyajin.", "ngaliny diwalngku yaluy nyajin | 1du.incl.NOM diwalngku.bird here see.PST", "r349_1du_incl_here_bird_d_saw_diwalngku", ["ngaliny", "diwalngku", "yaluy", "nyajin"]),
    row(12, "You and I saw the bujil-yiran ibis here.", "Ngaliny bujil-yiran yaluy nyajin.", "ngaliny bujil-yiran yaluy nyajin | 1du.incl.NOM bujil-yiran.ibis here see.PST", "r349_1du_incl_here_bird_d_saw_bujil_yiran", ["ngaliny", "bujil-yiran", "yaluy", "nyajin"]),
    row(13, "You and I saw the karrmbil parakeet here.", "Ngaliny karrmbil yaluy nyajin.", "ngaliny karrmbil yaluy nyajin | 1du.incl.NOM karrmbil.parakeet here see.PST", "r349_1du_incl_here_bird_d_saw_karrmbil", ["ngaliny", "karrmbil", "yaluy", "nyajin"]),
    row(14, "You and I saw the yiki owl here.", "Ngaliny yiki yaluy nyajin.", "ngaliny yiki yaluy nyajin | 1du.incl.NOM yiki.owl here see.PST", "r349_1du_incl_here_bird_d_saw_yiki", ["ngaliny", "yiki", "yaluy", "nyajin"]),
    row(15, "You and I saw the bajal bower bird here.", "Ngaliny bajal yaluy nyajin.", "ngaliny bajal yaluy nyajin | 1du.incl.NOM bajal.bower-bird here see.PST", "r349_1du_incl_here_bird_d_saw_bajal", ["ngaliny", "bajal", "yaluy", "nyajin"]),
    row(16, "You and I saw the biru-biru rainbow bird here.", "Ngaliny biru-biru yaluy nyajin.", "ngaliny biru-biru yaluy nyajin | 1du.incl.NOM biru-biru.rainbow-bird here see.PST", "r349_1du_incl_here_bird_d_saw_biru_biru", ["ngaliny", "biru-biru", "yaluy", "nyajin"]),
    row(17, "You and I saw the jangkan swift here.", "Ngaliny jangkan yaluy nyajin.", "ngaliny jangkan yaluy nyajin | 1du.incl.NOM jangkan.swift here see.PST", "r349_1du_incl_here_bird_d_saw_jangkan", ["ngaliny", "jangkan", "yaluy", "nyajin"]),
    row(18, "You and I saw the kurranday seagull here.", "Ngaliny kurranday yaluy nyajin.", "ngaliny kurranday yaluy nyajin | 1du.incl.NOM kurranday.seagull here see.PST", "r349_1du_incl_here_bird_d_saw_kurranday", ["ngaliny", "kurranday", "yaluy", "nyajin"]),
    row(19, "You and I saw the ngarkay waterhen here.", "Ngaliny ngarkay yaluy nyajin.", "ngaliny ngarkay yaluy nyajin | 1du.incl.NOM ngarkay.waterhen here see.PST", "r349_1du_incl_here_bird_d_saw_ngarkay", ["ngaliny", "ngarkay", "yaluy", "nyajin"]),
    row(20, "You and I saw the wakuka bird here.", "Ngaliny wakuka yaluy nyajin.", "ngaliny wakuka yaluy nyajin | 1du.incl.NOM wakuka.bird here see.PST", "r349_1du_incl_here_bird_d_saw_wakuka", ["ngaliny", "wakuka", "yaluy", "nyajin"]),
    row(21, "You two saw the diwalngku bird there.", "Yubal diwalngku yinyay nyajin.", "yubal diwalngku yinyay nyajin | 2du.NOM diwalngku.bird there see.PST", "r349_2du_there_bird_d_saw_diwalngku", ["yubal", "diwalngku", "yinyay", "nyajin"]),
    row(22, "You two saw the bujil-yiran ibis there.", "Yubal bujil-yiran yinyay nyajin.", "yubal bujil-yiran yinyay nyajin | 2du.NOM bujil-yiran.ibis there see.PST", "r349_2du_there_bird_d_saw_bujil_yiran", ["yubal", "bujil-yiran", "yinyay", "nyajin"]),
    row(23, "You two saw the karrmbil parakeet there.", "Yubal karrmbil yinyay nyajin.", "yubal karrmbil yinyay nyajin | 2du.NOM karrmbil.parakeet there see.PST", "r349_2du_there_bird_d_saw_karrmbil", ["yubal", "karrmbil", "yinyay", "nyajin"]),
    row(24, "You two saw the yiki owl there.", "Yubal yiki yinyay nyajin.", "yubal yiki yinyay nyajin | 2du.NOM yiki.owl there see.PST", "r349_2du_there_bird_d_saw_yiki", ["yubal", "yiki", "yinyay", "nyajin"]),
    row(25, "You two saw the bajal bower bird there.", "Yubal bajal yinyay nyajin.", "yubal bajal yinyay nyajin | 2du.NOM bajal.bower-bird there see.PST", "r349_2du_there_bird_d_saw_bajal", ["yubal", "bajal", "yinyay", "nyajin"]),
    row(26, "You two saw the biru-biru rainbow bird there.", "Yubal biru-biru yinyay nyajin.", "yubal biru-biru yinyay nyajin | 2du.NOM biru-biru.rainbow-bird there see.PST", "r349_2du_there_bird_d_saw_biru_biru", ["yubal", "biru-biru", "yinyay", "nyajin"]),
    row(27, "You two saw the jangkan swift there.", "Yubal jangkan yinyay nyajin.", "yubal jangkan yinyay nyajin | 2du.NOM jangkan.swift there see.PST", "r349_2du_there_bird_d_saw_jangkan", ["yubal", "jangkan", "yinyay", "nyajin"]),
    row(28, "You two saw the kurranday seagull there.", "Yubal kurranday yinyay nyajin.", "yubal kurranday yinyay nyajin | 2du.NOM kurranday.seagull there see.PST", "r349_2du_there_bird_d_saw_kurranday", ["yubal", "kurranday", "yinyay", "nyajin"]),
    row(29, "You two saw the ngarkay waterhen there.", "Yubal ngarkay yinyay nyajin.", "yubal ngarkay yinyay nyajin | 2du.NOM ngarkay.waterhen there see.PST", "r349_2du_there_bird_d_saw_ngarkay", ["yubal", "ngarkay", "yinyay", "nyajin"]),
    row(30, "You two saw the wakuka bird there.", "Yubal wakuka yinyay nyajin.", "yubal wakuka yinyay nyajin | 2du.NOM wakuka.bird there see.PST", "r349_2du_there_bird_d_saw_wakuka", ["yubal", "wakuka", "yinyay", "nyajin"]),
    row(31, "You all saw the diwalngku bird here.", "Yurra diwalngku yaluy nyajin.", "yurra diwalngku yaluy nyajin | 2pl.NOM diwalngku.bird here see.PST", "r349_2pl_here_bird_d_saw_diwalngku", ["yurra", "diwalngku", "yaluy", "nyajin"]),
    row(32, "You all saw the bujil-yiran ibis here.", "Yurra bujil-yiran yaluy nyajin.", "yurra bujil-yiran yaluy nyajin | 2pl.NOM bujil-yiran.ibis here see.PST", "r349_2pl_here_bird_d_saw_bujil_yiran", ["yurra", "bujil-yiran", "yaluy", "nyajin"]),
    row(33, "You all saw the karrmbil parakeet here.", "Yurra karrmbil yaluy nyajin.", "yurra karrmbil yaluy nyajin | 2pl.NOM karrmbil.parakeet here see.PST", "r349_2pl_here_bird_d_saw_karrmbil", ["yurra", "karrmbil", "yaluy", "nyajin"]),
    row(34, "You all saw the yiki owl here.", "Yurra yiki yaluy nyajin.", "yurra yiki yaluy nyajin | 2pl.NOM yiki.owl here see.PST", "r349_2pl_here_bird_d_saw_yiki", ["yurra", "yiki", "yaluy", "nyajin"]),
    row(35, "You all saw the bajal bower bird here.", "Yurra bajal yaluy nyajin.", "yurra bajal yaluy nyajin | 2pl.NOM bajal.bower-bird here see.PST", "r349_2pl_here_bird_d_saw_bajal", ["yurra", "bajal", "yaluy", "nyajin"]),
    row(36, "You all saw the biru-biru rainbow bird here.", "Yurra biru-biru yaluy nyajin.", "yurra biru-biru yaluy nyajin | 2pl.NOM biru-biru.rainbow-bird here see.PST", "r349_2pl_here_bird_d_saw_biru_biru", ["yurra", "biru-biru", "yaluy", "nyajin"]),
    row(37, "You all saw the jangkan swift here.", "Yurra jangkan yaluy nyajin.", "yurra jangkan yaluy nyajin | 2pl.NOM jangkan.swift here see.PST", "r349_2pl_here_bird_d_saw_jangkan", ["yurra", "jangkan", "yaluy", "nyajin"]),
    row(38, "You all saw the kurranday seagull here.", "Yurra kurranday yaluy nyajin.", "yurra kurranday yaluy nyajin | 2pl.NOM kurranday.seagull here see.PST", "r349_2pl_here_bird_d_saw_kurranday", ["yurra", "kurranday", "yaluy", "nyajin"]),
    row(39, "You all saw the ngarkay waterhen here.", "Yurra ngarkay yaluy nyajin.", "yurra ngarkay yaluy nyajin | 2pl.NOM ngarkay.waterhen here see.PST", "r349_2pl_here_bird_d_saw_ngarkay", ["yurra", "ngarkay", "yaluy", "nyajin"]),
    row(40, "You all saw the wakuka bird here.", "Yurra wakuka yaluy nyajin.", "yurra wakuka yaluy nyajin | 2pl.NOM wakuka.bird here see.PST", "r349_2pl_here_bird_d_saw_wakuka", ["yurra", "wakuka", "yaluy", "nyajin"]),
    row(41, "We, not you, saw the diwalngku bird there.", "Nganjin diwalngku yinyay nyajin.", "nganjin diwalngku yinyay nyajin | 1pl.excl.NOM diwalngku.bird there see.PST", "r349_1pl_excl_there_bird_d_saw_diwalngku", ["nganjin", "diwalngku", "yinyay", "nyajin"]),
    row(42, "We, not you, saw the bujil-yiran ibis there.", "Nganjin bujil-yiran yinyay nyajin.", "nganjin bujil-yiran yinyay nyajin | 1pl.excl.NOM bujil-yiran.ibis there see.PST", "r349_1pl_excl_there_bird_d_saw_bujil_yiran", ["nganjin", "bujil-yiran", "yinyay", "nyajin"]),
    row(43, "We, not you, saw the karrmbil parakeet there.", "Nganjin karrmbil yinyay nyajin.", "nganjin karrmbil yinyay nyajin | 1pl.excl.NOM karrmbil.parakeet there see.PST", "r349_1pl_excl_there_bird_d_saw_karrmbil", ["nganjin", "karrmbil", "yinyay", "nyajin"]),
    row(44, "We, not you, saw the yiki owl there.", "Nganjin yiki yinyay nyajin.", "nganjin yiki yinyay nyajin | 1pl.excl.NOM yiki.owl there see.PST", "r349_1pl_excl_there_bird_d_saw_yiki", ["nganjin", "yiki", "yinyay", "nyajin"]),
    row(45, "We, not you, saw the bajal bower bird there.", "Nganjin bajal yinyay nyajin.", "nganjin bajal yinyay nyajin | 1pl.excl.NOM bajal.bower-bird there see.PST", "r349_1pl_excl_there_bird_d_saw_bajal", ["nganjin", "bajal", "yinyay", "nyajin"]),
    row(46, "We, not you, saw the biru-biru rainbow bird there.", "Nganjin biru-biru yinyay nyajin.", "nganjin biru-biru yinyay nyajin | 1pl.excl.NOM biru-biru.rainbow-bird there see.PST", "r349_1pl_excl_there_bird_d_saw_biru_biru", ["nganjin", "biru-biru", "yinyay", "nyajin"]),
    row(47, "We, not you, saw the jangkan swift there.", "Nganjin jangkan yinyay nyajin.", "nganjin jangkan yinyay nyajin | 1pl.excl.NOM jangkan.swift there see.PST", "r349_1pl_excl_there_bird_d_saw_jangkan", ["nganjin", "jangkan", "yinyay", "nyajin"]),
    row(48, "We, not you, saw the kurranday seagull there.", "Nganjin kurranday yinyay nyajin.", "nganjin kurranday yinyay nyajin | 1pl.excl.NOM kurranday.seagull there see.PST", "r349_1pl_excl_there_bird_d_saw_kurranday", ["nganjin", "kurranday", "yinyay", "nyajin"]),
    row(49, "We, not you, saw the ngarkay waterhen there.", "Nganjin ngarkay yinyay nyajin.", "nganjin ngarkay yinyay nyajin | 1pl.excl.NOM ngarkay.waterhen there see.PST", "r349_1pl_excl_there_bird_d_saw_ngarkay", ["nganjin", "ngarkay", "yinyay", "nyajin"]),
    row(50, "We, not you, saw the wakuka bird there.", "Nganjin wakuka yinyay nyajin.", "nganjin wakuka yinyay nyajin | 1pl.excl.NOM wakuka.bird there see.PST", "r349_1pl_excl_there_bird_d_saw_wakuka", ["nganjin", "wakuka", "yinyay", "nyajin"]),
]


def literal_row_guard() -> list[str]:
    module = ast.parse(Path(__file__).read_text(encoding="utf-8"))
    assignments = [node for node in module.body if isinstance(node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == "ROWS" for t in node.targets)]
    if len(assignments) != 1 or not isinstance(assignments[0].value, ast.List):
        return ["ROWS is not one literal list"]
    elements = assignments[0].value.elts
    if len(elements) != 50:
        return [f"literal row call count {len(elements)}"]
    issues = []
    for index, element in enumerate(elements, 1):
        if not isinstance(element, ast.Call) or not isinstance(element.func, ast.Name) or element.func.id != "row" or element.keywords:
            issues.append(f"{index}: not a direct row(...) call")
            continue
        try:
            values = [ast.literal_eval(arg) for arg in element.args]
        except (ValueError, TypeError):
            issues.append(f"{index}: nonliteral row argument")
            continue
        if len(values) != 6 or values[0] != index:
            issues.append(f"{index}: literal argument/sequence mismatch")
    return issues


def scan_static() -> list[str]:
    issues = literal_row_guard()
    if len(ROWS) != 50 or [r[0] for r in ROWS] != list(range(1, 51)):
        issues.append("row count/sequence")
    if len({r[1] for r in ROWS}) != 50 or len({r[2] for r in ROWS}) != 50:
        issues.append("duplicate authored surface")
    for seq, english, kuku, analysis, frame, _tier, words_json, loans_json, evidence, confidence in ROWS:
        pronoun, demo, prefix, location, person = FRAME_SPECS[(seq - 1) // 10]
        target = TARGET_ORDER[(seq - 1) % 10]
        words = json.loads(words_json)
        if json.loads(loans_json):
            issues.append(f"{seq}: loans")
        denied = sorted(word for word in DENY_KUKU if re.search(rf"(^|\s){re.escape(word)}([\s\.]|$)", kuku.lower()))
        drift = [term for term in DENY_ENGLISH if re.search(rf"\b{re.escape(term)}\b", english.lower())]
        if denied:
            issues.append(f"{seq}: denied Kuku {denied}")
        if drift:
            issues.append(f"{seq}: English drift {drift}")
        if kuku != f"{pronoun.capitalize()} {target} {demo} nyajin.":
            issues.append(f"{seq}: Kuku mismatch")
        if english != f"{prefix} the {TARGET_LABELS[target]} {location}.":
            issues.append(f"{seq}: English mismatch")
        if analysis != f"{pronoun} {target} {demo} nyajin | {person}.NOM {TARGET_GLOSSES[target]} {location} see.PST":
            issues.append(f"{seq}: analysis mismatch")
        if words != [pronoun, target, demo, "nyajin"] or kuku.lower().rstrip(".") != " ".join(words):
            issues.append(f"{seq}: words/surface mismatch")
        if not frame.startswith("r349_"):
            issues.append(f"{seq}: frame mismatch")
        if TARGET_EVIDENCE[target] not in evidence or SUPPORT_EVIDENCE not in evidence:
            issues.append(f"{seq}: evidence mismatch")
        if confidence != TARGET_CONFIDENCE[target]:
            issues.append(f"{seq}: confidence mismatch")
    return issues


def main() -> int:
    bird_records, bird_hits = bird_source_audit()
    _pronoun_target_records, pronoun_records = pronoun_source_audit()
    issues = scan_static()
    if issues:
        raise SystemExit("\n".join(issues))
    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys=ON")
    db.execute("BEGIN")
    ensure_schema(db)
    if db.execute("SELECT 1 FROM batches WHERE label=?", (LABEL,)).fetchone():
        raise SystemExit(f"{LABEL} exists")
    if db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]:
        raise SystemExit("another batch is under review")
    target_coverage = count_words(db, TARGET_ORDER)
    support_coverage = count_words(db, list(EXPECTED_SUPPORT_COVERAGE))
    if target_coverage != EXPECTED_TARGET_COVERAGE or support_coverage != EXPECTED_SUPPORT_COVERAGE:
        raise SystemExit(f"coverage changed: {target_coverage} {support_coverage}")
    for seq, _english, kuku, _analysis, _frame, _tier, words_json, _loans, _evidence, _confidence in ROWS:
        words = json.loads(words_json)
        candidate_hits = db.execute(
            """SELECT b.label,s.seq FROM sentences s JOIN batches b ON b.id=s.batch_id
               WHERE lower(s.kuku)=lower(?) AND s.status!='rejected' ORDER BY b.id,s.seq""", (kuku,),
        ).fetchall()
        if candidate_hits:
            raise SystemExit(f"candidate collision {seq}: {candidate_hits}")
        opposite_surface = f"{words[0].capitalize()} {words[1]} {OPPOSITE[words[2]]} nyajin."
        opposite_hits = db.execute(
            """SELECT b.label,s.seq FROM sentences s JOIN batches b ON b.id=s.batch_id
               WHERE lower(s.kuku)=lower(?) AND s.status!='rejected' ORDER BY b.id,s.seq""", (opposite_surface,),
        ).fetchall()
        if opposite_hits != [("batch-390", seq)]:
            raise SystemExit(f"opposite mismatch {seq}: {opposite_hits}")
    verified, words = db.execute(
        """SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
           FROM sentences WHERE status IN ('accepted','revised')"""
    ).fetchone()
    if (verified, words) != (19597, 133747):
        raise SystemExit(f"baseline changed {verified}/{words}")
    notes = (
        "COMPLEMENTARY BIRD SET-D NON-SINGULAR PAST PERCEPTION AFTER RULE 348 (Rule 349). 50 under-review "
        "exact deictic complements; candidates empty and opposites exactly Batch 390 seq 1-50. Fresh bird-card, "
        "Patz-hit and pronoun-paradigm audits passed; all lexical and source boundaries retained; gap 162->172."
    )
    batch_id = db.execute(
        "INSERT INTO batches(label,cheatsheet_version,guide_version,notes,created_at) VALUES(?,?,?,?,?)",
        (LABEL, "Rule 349 under review", "operator-guide-2026-07-10", notes, NOW),
    ).lastrowid
    db.executemany(
        """INSERT INTO sentences(
             batch_id,seq,english,kuku,analysis,frame,tier,words_used,loans_used,evidence,status,confidence,
             rights_status,author,created_at,updated_at
           ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
        [
            (batch_id, seq, english, kuku, analysis, frame, tier, words_json, loans_json, evidence,
             "under_review", confidence, "project_approved_synthetic_pending_elder_verification", "codex-main", NOW, NOW)
            for seq, english, kuku, analysis, frame, tier, words_json, loans_json, evidence, confidence in ROWS
        ],
    )
    events = [
        ("source", "source-audit-pass", f"Bird records={bird_records}; Patz hits={bird_hits}; pronoun records={pronoun_records}; hashes={EXPECTED_HASHES}."),
        ("source", "pronoun-ruling", "Patz ngali exclusive / ngaliny inclusive controls over dictionary defect; ngana remains grammatical and deferred."),
        ("planning", "sense-preservation-audit", "All Rule 346-348 target card/taxonomy/whole-head/culture/property/habitat/place/event boundaries retained."),
        ("planning", "complement-map-audit", "All 50 candidates absent; every opposite exactly Batch 390 sequences 1-50; full 100-cell paradigm accounted for."),
        ("planning", "balance-audit", "Gap 162; completion projects 172. Apparent 152-gap inverse is Batch 390 duplication."),
        ("preflight", "coverage-audit", f"targets={target_coverage}; support={support_coverage}."),
        ("preflight", "duplicate-preflight", "50 selected Kuku and English surfaces unique and absent; opposites proved one-for-one."),
        ("preflight", "static-guard-pass", "AST proved 50 direct literal row calls; all source/sense/clusivity/frame contracts passed."),
    ]
    for phase, event_type, detail in events:
        record_event(db, batch_label=LABEL, phase=phase, event_type=event_type, actor="codex", detail=detail, artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(
        db, batch_label=LABEL, phase="bank", event_type="bank-success", actor="codex",
        detail=f"Banked 50 Rule 349 complementary rows under_review; verified remains {verified}; words {words}.",
        command="python3 batch_scripts/bank_391.py", output_excerpt="50 under_review", artifact_path=PROCESS_LOG, created_at=NOW,
    )
    db.commit()
    print("banked batch-391: 50 rows under_review")
    return 0


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