#!/usr/bin/env python3
"""Bank batch-168: Rule 126 case-frame consolidation after Rule 125."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3
import sys

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

DENY = {"yah", "ngawuya", "walnga", "maja-nga", "yunga", "bada-bada", "wangkar-wangkar", "biyul"}
CONSTRUCTION_CLITIC_MARKERS = ("-kuda", "-ngarr", "-ngurr", "-l-urr", "wawu-")
PRONOUN_A = {"ngayu", "ngana", "nganjin", "yundu", "yurra", "jana", "nyulu"}
SOURCED_COMITATIVES = ("ngawa-nji", "nganjan-anji", "jawun-unji", "juku-ji", "kambi-ji")
LAUGH_TARGET_SUFFIXES = ("-anka", "-anda", "-nda", "-ndu", "-nka")
LOAN_REQUIRED = ("naybu", "marrkin", "mayngku", "wulman")


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


ROWS = [
    (1, "The fish lay in the shade.",
     "Kuyu wabarr-ba wunan.",
     "kuyu wabarr-ba wuna-ny | fish shade-LOC lie-PAST",
     "r126_rest_fish_shade", 3, js(["kuyu", "wabarr", "wunay"]), js([]),
     "Kuyu fish and wabarr-ba shade LOC retained from source-backed Rule 125 frames.", "0.84"),
    (2, "The meat lay in the bag.",
     "Minya balji-nga wunan.",
     "minya balji-nga wuna-ny | meat bag-LOC lie-PAST",
     "r126_rest_meat_bag", 3, js(["minya", "balji", "wunay"]), js([]),
     "Minya meat/flesh-food plus balji-nga bag LOC; no new morphology.", "0.84"),
    (3, "The axe lay outside.",
     "Warrbi ngalkal-ba wunan.",
     "warrbi ngalkal-ba wuna-ny | axe outside-LOC lie-PAST",
     "r126_rest_axe_outside", 3, js(["warrbi", "ngalkal", "wunay"]), js([]),
     "Warrbi kept to axe sense only; ngalkal-ba outside LOC retained.", "0.82"),
    (4, "The knife lay on the ground.",
     "Naybu bubu-ngu wunan.",
     "naybu bubu-ngu wuna-ny | knife ground-LOC lie-PAST",
     "r126_rest_knife_ground", 3, js(["naybu", "bubu", "wunay"]), js(["naybu"]),
     "Naybu is a source-backed English loan; used as ABS rest subject, not instrument.", "0.82"),
    (5, "The spear lay on the road.",
     "Kalka baral-ba wunan.",
     "kalka baral-ba wuna-ny | spear road-LOC lie-PAST",
     "r126_rest_spear_road", 3, js(["kalka", "baral", "wunay"]), js([]),
     "Kalka is the clean spear head used instead of excluded biyul.", "0.84"),
    (6, "The club lay outside.",
     "Mirru ngalkal-ba wunan.",
     "mirru ngalkal-ba wuna-ny | club outside-LOC lie-PAST",
     "r126_rest_club_outside", 3, js(["mirru", "ngalkal", "wunay"]), js([]),
     "Mirru club/nullanulla in rest frame; no new case analogy.", "0.84"),
    (7, "The shield lay on the ground.",
     "Kunjuri bubu-ngu wunan.",
     "kunjuri bubu-ngu wuna-ny | shield ground-LOC lie-PAST",
     "r126_rest_shield_ground", 3, js(["kunjuri", "bubu", "wunay"]), js([]),
     "Kunjuri shield as ABS rest subject with retained ground LOC.", "0.84"),
    (8, "The boomerang lay in the shade.",
     "Wangal wabarr-ba wunan.",
     "wangal wabarr-ba wuna-ny | boomerang shade-LOC lie-PAST",
     "r126_rest_boomerang_shade", 3, js(["wangal", "wabarr", "wunay"]), js([]),
     "Wangal is boomerang noun only, not a dance verb.", "0.84"),
    (9, "The gun lay outside.",
     "Marrkin ngalkal-ba wunan.",
     "marrkin ngalkal-ba wuna-ny | gun outside-LOC lie-PAST",
     "r126_rest_gun_outside", 3, js(["marrkin", "ngalkal", "wunay"]), js(["marrkin"]),
     "Marrkin is a source-backed musket/rifle loan; used as ABS rest subject.", "0.82"),
    (10, "Rain fell at camp.",
     "Kaba bayan-ba daran.",
     "kaba bayan-ba dara-ny | rain camp-LOC fall-PAST",
     "r126_weather_rain_camp", 3, js(["kaba", "bayan", "daray"]), js([]),
     "Kaba remains a weather noun; dara-y fall and bayan-ba camp LOC are already source-backed.", "0.84"),

    (11, "The woman put the fish on the ground.",
     "Jalbu-ngku kuyu bubu-ngu yijarrin.",
     "jalbu-ngku kuyu bubu-ngu yijarri-ny | woman-ERG fish ground-LOC put-PAST",
     "r126_object_fish_ground", 4, js(["jalbu", "kuyu", "bubu", "yijarril"]), js([]),
     "Common-noun A ERG; kuyu object in retained placement frame.", "0.84"),
    (12, "The man put the meat in the bag.",
     "Dingkar-angka minya balji-nga yijarrin.",
     "dingkar-angka minya balji-nga yijarri-ny | man-ERG meat bag-LOC put-PAST",
     "r126_object_meat_bag", 4, js(["dingkar", "minya", "balji", "yijarril"]), js([]),
     "Minya meat object plus balji-nga bag LOC; no lexical expansion.", "0.84"),
    (13, "The young man left the axe in the shade.",
     "Warru-ngku warrbi wabarr-ba bawan.",
     "warru-ngku warrbi wabarr-ba bawa-ny | young.man-ERG axe shade-LOC leave-PAST",
     "r126_object_axe_shade", 4, js(["warru", "warrbi", "wabarr", "bawal"]), js([]),
     "Warrbi axe sense only; retained leave-location frame.", "0.82"),
    (14, "Grandmother left the knife on the ground.",
     "Babi-ngka naybu bubu-ngu bawan.",
     "babi-ngka naybu bubu-ngu bawa-ny | grandmother-ERG knife ground-LOC leave-PAST",
     "r126_object_knife_ground", 4, js(["babi", "naybu", "bubu", "bawal"]), js(["naybu"]),
     "Naybu loan object in retained leave-location frame.", "0.82"),
    (15, "Mother put the spear on the road.",
     "Ngamu-ngku kalka baral-ba yijarrin.",
     "ngamu-ngku kalka baral-ba yijarri-ny | mother-ERG spear road-LOC put-PAST",
     "r126_object_spear_road", 4, js(["ngamu", "kalka", "baral", "yijarril"]), js([]),
     "Kalka spear object plus baral-ba road LOC; no baral-muny.", "0.84"),
    (16, "Grandfather got the club outside.",
     "Kami-ngka mirru ngalkal-ba manin.",
     "kami-ngka mirru ngalkal-ba mani-ny | grandfather-ERG club outside-LOC get-PAST",
     "r126_object_club_outside", 4, js(["kami", "mirru", "ngalkal", "manil"]), js([]),
     "Mirru club object in retained get-at-place frame.", "0.84"),
    (17, "The man brought the shield from the house.",
     "Dingkar-angka kunjuri bayan-muny wundin.",
     "dingkar-angka kunjuri bayan-muny wundi-ny | man-ERG shield house-ABL bring-PAST",
     "r126_object_shield_house_source", 4, js(["dingkar", "kunjuri", "bayan", "wundil"]), js([]),
     "Kunjuri shield object plus bayan-muny ABL source.", "0.84"),
    (18, "The old woman left the boomerang in the shade.",
     "Wulbuman-angka wangal wabarr-ba bawan.",
     "wulbuman-angka wangal wabarr-ba bawa-ny | old.woman-ERG boomerang shade-LOC leave-PAST",
     "r126_object_boomerang_shade", 4, js(["wulbuman", "wangal", "wabarr", "bawal"]), js([]),
     "Wangal boomerang noun in retained leave-location frame.", "0.84"),
    (19, "We brought meat from the house.",
     "Ngana minya bayan-muny wundin.",
     "ngana minya bayan-muny wundi-ny | we meat house-ABL bring-PAST",
     "r126_object_meat_house_source", 4, js(["ngana", "minya", "bayan", "wundil"]), js([]),
     "Pronoun A bare; minya object with bayan-muny source.", "0.84"),
    (20, "I took the knife to camp.",
     "Ngayu naybu bayan-ka wundin.",
     "ngayu naybu bayan-ka wundi-ny | I knife camp-DAT take-PAST",
     "r126_object_knife_camp_goal", 4, js(["ngayu", "naybu", "bayan", "wundil"]), js(["naybu"]),
     "Pronoun A bare; naybu loan object plus bayan-ka DAT goal.", "0.82"),

    (21, "The child saw the fish in the shade.",
     "Karrkay-ngku kuyu wabarr-ba nyajin.",
     "karrkay-ngku kuyu wabarr-ba nyaji-ny | child-ERG fish shade-LOC see-PAST",
     "r126_perception_child_fish_shade", 4, js(["karrkay", "kuyu", "wabarr", "nyajil"]), js([]),
     "Common-noun perceiver ERG; kuyu perceived object in shade.", "0.84"),
    (22, "I saw the fish in the shade.",
     "Ngayu kuyu wabarr-ba nyajin.",
     "ngayu kuyu wabarr-ba nyaji-ny | I fish shade-LOC see-PAST",
     "r126_perception_pronoun_fish_shade", 4, js(["ngayu", "kuyu", "wabarr", "nyajil"]), js([]),
     "Pronoun A contrast for row 21; no ERG on ngayu.", "0.84"),
    (23, "The woman saw the meat in the bag.",
     "Jalbu-ngku minya balji-nga nyajin.",
     "jalbu-ngku minya balji-nga nyaji-ny | woman-ERG meat bag-LOC see-PAST",
     "r126_perception_woman_meat_bag", 4, js(["jalbu", "minya", "balji", "nyajil"]), js([]),
     "Common-noun perceiver ERG; minya perceived object in bag.", "0.84"),
    (24, "We saw the meat in the bag.",
     "Ngana minya balji-nga nyajin.",
     "ngana minya balji-nga nyaji-ny | we meat bag-LOC see-PAST",
     "r126_perception_pronoun_meat_bag", 4, js(["ngana", "minya", "balji", "nyajil"]), js([]),
     "Pronoun A contrast for row 23.", "0.84"),
    (25, "The man saw the spear on the road.",
     "Dingkar-angka kalka baral-ba nyajin.",
     "dingkar-angka kalka baral-ba nyaji-ny | man-ERG spear road-LOC see-PAST",
     "r126_perception_man_spear_road", 4, js(["dingkar", "kalka", "baral", "nyajil"]), js([]),
     "Kalka spear object with ERG common-noun perceiver.", "0.84"),
    (26, "I saw the spear on the road.",
     "Ngayu kalka baral-ba nyajin.",
     "ngayu kalka baral-ba nyaji-ny | I spear road-LOC see-PAST",
     "r126_perception_pronoun_spear_road", 4, js(["ngayu", "kalka", "baral", "nyajil"]), js([]),
     "Pronoun A contrast for row 25.", "0.84"),
    (27, "Grandmother saw the boomerang on the ground.",
     "Babi-ngka wangal bubu-ngu nyajin.",
     "babi-ngka wangal bubu-ngu nyaji-ny | grandmother-ERG boomerang ground-LOC see-PAST",
     "r126_perception_grandmother_boomerang_ground", 4, js(["babi", "wangal", "bubu", "nyajil"]), js([]),
     "Wangal boomerang noun with ERG common-noun perceiver.", "0.84"),
    (28, "We saw the boomerang on the ground.",
     "Ngana wangal bubu-ngu nyajin.",
     "ngana wangal bubu-ngu nyaji-ny | we boomerang ground-LOC see-PAST",
     "r126_perception_pronoun_boomerang_ground", 4, js(["ngana", "wangal", "bubu", "nyajil"]), js([]),
     "Pronoun A contrast for row 27.", "0.84"),
    (29, "Mother saw the moon outside.",
     "Ngamu-ngku kija ngalkal-ba nyajin.",
     "ngamu-ngku kija ngalkal-ba nyaji-ny | mother-ERG moon outside-LOC see-PAST",
     "r126_perception_mother_moon_outside", 4, js(["ngamu", "kija", "ngalkal", "nyajil"]), js([]),
     "Kija is moon/month only; common-noun perceiver ERG.", "0.84"),
    (30, "We saw the star outside.",
     "Ngana dawar ngalkal-ba nyajin.",
     "ngana dawar ngalkal-ba nyaji-ny | we star outside-LOC see-PAST",
     "r126_perception_pronoun_star_outside", 4, js(["ngana", "dawar", "ngalkal", "nyajil"]), js([]),
     "Dawar is the safe star lexeme; pronoun A bare.", "0.84"),

    (31, "The old man asked grandmother about meat.",
     "Wulman babi-nda minya-ka babajin.",
     "wulman babi-nda minya-ka babaji-ny | old.man grandmother-LOC:pt meat-DAT ask-PAST",
     "r126_ask_old_man_grandmother_meat", 4, js(["wulman", "babi", "minya", "babaji"]), js(["wulman"]),
     "Babaji-y asker remains ABS/S; minya-ka DAT topic retained.", "0.86"),
    (32, "The young man asked grandfather about meat.",
     "Warru kami-nda minya-ka babajin.",
     "warru kami-nda minya-ka babaji-ny | young.man grandfather-LOC:pt meat-DAT ask-PAST",
     "r126_ask_young_man_grandfather_meat", 4, js(["warru", "kami", "minya", "babaji"]), js([]),
     "Ask-about frame with LOC:pt person and sourced minya-ka topic.", "0.86"),
    (33, "The woman asked mother about news.",
     "Jalbu ngamu-ndu kuku-ku babajin.",
     "jalbu ngamu-ndu kuku-ku babaji-ny | woman mother-LOC:pt news-DAT ask-PAST",
     "r126_ask_woman_mother_news", 4, js(["jalbu", "ngamu", "kuku", "babaji"]), js([]),
     "Kuku-ku DAT topic retained; no ERG on asker.", "0.86"),
    (34, "The friend asked father about the mango.",
     "Jawun nganjan-anda mayngku-ku babajin.",
     "jawun nganjan-anda mayngku-ku babaji-ny | friend father-LOC:pt mango-DAT ask-PAST",
     "r126_ask_friend_father_mango", 4, js(["jawun", "nganjan", "mayngku", "babaji"]), js(["mayngku"]),
     "Mayngku-ku is grammar-sourced and loan-logged.", "0.86"),
    (35, "The child asked the girl about meat.",
     "Karrkay maral-anda minya-ka babajin.",
     "karrkay maral-anda minya-ka babaji-ny | child girl-LOC:pt meat-DAT ask-PAST",
     "r126_ask_child_girl_meat", 4, js(["karrkay", "maral", "minya", "babaji"]), js([]),
     "Ask-about frame retained with minya-ka DAT topic.", "0.86"),

    (36, "The old man laughed at the girl.",
     "Wulman maral-anda yajarrin.",
     "wulman maral-anda yajarri-ny | old.man girl-DAT:pt laugh-PAST",
     "r126_laugh_old_man_girl", 3, js(["wulman", "maral", "yajarril"]), js(["wulman"]),
     "Yajarri-l target keeps DAT:pt suffix; no ERG on laugher.", "0.84"),
    (37, "Mother laughed at grandmother.",
     "Ngamu babi-nda yajarrin.",
     "ngamu babi-nda yajarri-ny | mother grandmother-DAT:pt laugh-PAST",
     "r126_laugh_mother_grandmother", 3, js(["ngamu", "babi", "yajarril"]), js([]),
     "DAT:pt target retained.", "0.84"),
    (38, "Father laughed at grandfather.",
     "Nganjan kami-nda yajarrin.",
     "nganjan kami-nda yajarri-ny | father grandfather-DAT:pt laugh-PAST",
     "r126_laugh_father_grandfather", 3, js(["nganjan", "kami", "yajarril"]), js([]),
     "DAT:pt target retained.", "0.84"),
    (39, "The girl laughed at mother.",
     "Maral ngamu-ndu yajarrin.",
     "maral ngamu-ndu yajarri-ny | girl mother-DAT:pt laugh-PAST",
     "r126_laugh_girl_mother", 3, js(["maral", "ngamu", "yajarril"]), js([]),
     "Uses sourced ngamu-ndu target; no jawun-unji/comitative confusion.", "0.84"),
    (40, "The friend laughed at grandmother.",
     "Jawun babi-nda yajarrin.",
     "jawun babi-nda yajarri-ny | friend grandmother-DAT:pt laugh-PAST",
     "r126_laugh_friend_grandmother", 3, js(["jawun", "babi", "yajarril"]), js([]),
     "DAT:pt target retained.", "0.84"),

    (41, "Grandmother went to the shade with the baby.",
     "Babi ngawa-nji wabarr-ba dungan.",
     "babi ngawa-nji wabarr-ba dunga-ny | grandmother baby-COMIT:pt shade-LOC go-PAST",
     "r126_comit_grandmother_baby_shade", 4, js(["babi", "ngawa", "wabarr", "dungay"]), js([]),
     "Ngawa-nji remains in the closed sourced comitative set.", "0.84"),
    (42, "The young man went to the sea with a friend.",
     "Warru jawun-unji jalun-bu dungan.",
     "warru jawun-unji jalun-bu dunga-ny | young.man friend-COMIT:pt sea-LOC go-PAST",
     "r126_comit_young_man_friend_sea", 4, js(["warru", "jawun", "jalun", "dungay"]), js([]),
     "Jawun-unji closed comitative plus jalun-bu sea LOC.", "0.84"),
    (43, "The woman went to the road with father.",
     "Jalbu nganjan-anji baral-ba dungan.",
     "jalbu nganjan-anji baral-ba dunga-ny | woman father-COMIT:pt road-LOC go-PAST",
     "r126_comit_woman_father_road", 4, js(["jalbu", "nganjan", "baral", "dungay"]), js([]),
     "Nganjan-anji closed comitative; baral-ba road LOC, no baral-muny.", "0.84"),
    (44, "The child went outside with a stick.",
     "Karrkay juku-ji ngalkal-ba dungan.",
     "karrkay juku-ji ngalkal-ba dunga-ny | child stick-COMIT outside-LOC go-PAST",
     "r126_comit_child_stick_outside", 4, js(["karrkay", "juku", "ngalkal", "dungay"]), js([]),
     "Juku-ji closed neutral comitative.", "0.84"),
    (45, "Mother went outside with clothes.",
     "Ngamu kambi-ji ngalkal-ba dungan.",
     "ngamu kambi-ji ngalkal-ba dunga-ny | mother clothes-COMIT outside-LOC go-PAST",
     "r126_comit_mother_clothes_outside", 4, js(["ngamu", "kambi", "ngalkal", "dungay"]), js([]),
     "Duplicate repair kept kambi-ji closed comitative and changed only the sourced location.", "0.84"),
    (46, "The old man is staying outside with the baby.",
     "Wulman ngalkal-ba bundanday ngawa-nji.",
     "wulman ngalkal-ba bunda-n-bunda-y ngawa-nji | old.man outside-LOC stay-REDUP-NONPAST baby-COMIT:pt",
     "r126_comit_old_man_baby_outside", 4, js(["wulman", "ngalkal", "bunday", "ngawa"]), js(["wulman"]),
     "Place-before-comitative order retained from the Batch 167 repair principle.", "0.84"),

    (47, "The man left the gun in the shade.",
     "Dingkar-angka marrkin wabarr-ba bawan.",
     "dingkar-angka marrkin wabarr-ba bawa-ny | man-ERG gun shade-LOC leave-PAST",
     "r126_object_gun_shade", 4, js(["dingkar", "marrkin", "wabarr", "bawal"]), js(["marrkin"]),
     "Marrkin loan object in retained leave-location frame.", "0.82"),
    (48, "The old woman left the shield outside.",
     "Wulbuman-angka kunjuri ngalkal-ba bawan.",
     "wulbuman-angka kunjuri ngalkal-ba bawa-ny | old.woman-ERG shield outside-LOC leave-PAST",
     "r126_object_shield_outside", 4, js(["wulbuman", "kunjuri", "ngalkal", "bawal"]), js([]),
     "Kunjuri shield object in retained leave-location frame.", "0.84"),
    (49, "The young man took the club to camp.",
     "Warru-ngku mirru bayan-ka wundin.",
     "warru-ngku mirru bayan-ka wundi-ny | young.man-ERG club camp-DAT take-PAST",
     "r126_object_club_camp_goal", 4, js(["warru", "mirru", "bayan", "wundil"]), js([]),
     "Mirru object plus sourced bayan-ka DAT goal.", "0.84"),
    (50, "I brought the axe from the house.",
     "Ngayu warrbi bayan-muny wundin.",
     "ngayu warrbi bayan-muny wundi-ny | I axe house-ABL bring-PAST",
     "r126_object_axe_house_source", 4, js(["ngayu", "warrbi", "bayan", "wundil"]), js([]),
     "Pronoun A bare; warrbi axe object plus bayan-muny source.", "0.82"),
]


def has_denied_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", 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 in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        analysis_low = analysis.lower()
        loan_values = json.loads(loans_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("r126_"):
            issues.append(f"{label}: Rule 126 batch frame must start with r126_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        for marker in CONSTRUCTION_CLITIC_MARKERS:
            if marker in low:
                issues.append(f"{label}: consolidation batch must not reuse temporal clitic marker {marker}")
        for denied in DENY:
            if has_denied_token(low, denied):
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        if "baral-muny" in low:
            issues.append(f"{label}: baral-muny is not source-logged")
        if "kadar-anji" in low:
            issues.append(f"{label}: kadar-anji is outside this batch's closed comitative set")
        if "ngunyin-ji" in low:
            issues.append(f"{label}: ngunyin-ji is attested but not admitted by Rule 126's closed-comitative policy")
        if "kija" in low and not re.search(r"\b(moon|month)\b", analysis_low):
            issues.append(f"{label}: kija is moon/month only; analysis must say moon/month")
        if "star" in english.lower() and "dawar" not in low:
            issues.append(f"{label}: star row must use dawar")
        if "kaba" in low and "rain" not in analysis_low:
            issues.append(f"{label}: kaba row must gloss rain")
        if "wangal" in low and "boomerang" not in analysis_low:
            issues.append(f"{label}: wangal must be boomerang noun, not dance")
        if "warrbi" in low and "axe" not in analysis_low:
            issues.append(f"{label}: warrbi must be axe in this batch")
        for loan in LOAN_REQUIRED:
            if has_denied_token(low, loan) and loan not in loan_values:
                issues.append(f"{label}: {loan} must be listed in loans_used")
        if "babajin" in low and re.match(r"^[A-Za-z-]+-(?:ngka|angka|ngku)\b", kuku):
            issues.append(f"{label}: babaji-y asker drifted into ergative A marking")
        if "nyaji" in analysis_low:
            first = low.split()[0].strip(".,!?")
            if first not in PRONOUN_A and not re.search(r"-(?:ngka|angka|ngku)\b", first):
                issues.append(f"{label}: common-noun perceiver with nyaji-l should be ERG-marked")
        if "yajarrin" in low and not any(suffix in low for suffix in LAUGH_TARGET_SUFFIXES):
            issues.append(f"{label}: laugh target should show sourced DAT:pt target suffix")
        if "COMIT" in analysis and not any(form in low for form in SOURCED_COMITATIVES):
            issues.append(f"{label}: comitative form is not in the closed sourced set")
        if any(form in low for form in ("kaya-nga", "ngamu-ngu")) and "POSS" not in analysis:
            issues.append(f"{label}: possessive form needs explicit POSS analysis")
        try:
            json.loads(words_used)
            json.loads(loans_used)
        except json.JSONDecodeError as exc:
            issues.append(f"{label}: JSON field failed: {exc}")
        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 126 draft: case-frame consolidation after Rule 125", "operator-guide-2026-07-07", "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

    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-pass",
        actor="claude-main",
        detail=(
            "Rule 126 source pass reused and checked the Rule 125 dictionary, Patz grammar, corpus lexeme, and "
            "duplicate evidence before authoring. No new lexical heads or morphology were admitted."
        ),
        command="rg over dictionary.yaml and grammar.md; sqlite lexeme readback and duplicate scan",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="dictionary-source-check",
        actor="claude-main",
        detail=(
            "Dictionary controls retained kuyu fish, minya meat/flesh food, kaba rain, dawar star, kija moon, "
            "warrbi axe, naybu knife, kalka spear, mirru club, kunjuri shield, wangal boomerang, and marrkin gun."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="grammar-source-check",
        actor="claude-main",
        detail=(
            "Grammar controls retained kaba dara-y weather, minya-ka/kuku-ku/mayngku-ku DAT topics, bayan-ka and "
            "bayan-muny transfer frames, common-noun ERG perception A, and closed comitatives ngawa-nji, jawun-unji, "
            "nganjan-anji, juku-ji, and kambi-ji."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="risk-controls",
        actor="claude-main",
        detail=(
            "Controls: no biyul; kija moon/month only; dawar for star; wangal noun only; warrbi axe only; naybu, "
            "marrkin, mayngku, and wulman loan-logged; no new instruments, comitatives, or possessives; no baral-muny; "
            "no ergative babaji-y askers; common-noun nyaji-l perceivers ERG-marked."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="existing-surface-scan",
        actor="claude-main",
        detail=(
            "Initial planned scan found one exact duplicate, Ngamu kambi-ji wabarr-ba dungan, already in batch-165. "
            "It was repaired to Ngamu kambi-ji ngalkal-ba dungan. The weather row was tightened from a marginal "
            "rain-from-sea source row to Kaba bayan-ba daran. Final scan found no exact duplicates."
        ),
        command="sqlite exact-surface query over planned Batch 168 rows",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="process",
        event_type="process-capture-expanded",
        actor="claude-main",
        detail=(
            "Batch 168 stores source inventory, duplicate repair, weather-frame tightening, static guards, row grouping, "
            "bank outcome, and later lint/review/close/export/docs evidence as process_events plus this process log."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    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_used,
                loans_used, evidence, "under_review", confidence,
                "project_approved_synthetic_pending_elder_verification",
                "claude-main", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 hand-authored Rule 126 case-frame consolidation rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_168.py",
        artifact_path="batch_scripts/bank_168.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 rest/weather; 11-20 object location/source/goal; 21-30 perception contrasts; 31-35 ask-about; "
            "36-40 laugh targets; 41-46 closed comitatives; 47-50 additional object transfer/leave rows."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    db.commit()

    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    avg_words = db.execute(
        "SELECT AVG(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1.0) FROM sentences WHERE batch_id=?",
        (batch_id,),
    ).fetchone()[0]
    process_events = db.execute("SELECT COUNT(*) FROM process_events WHERE batch_label=?", (LABEL,)).fetchone()[0]
    print(f"banked {LABEL}: {count} rows under_review, avg_words={avg_words:.2f}, process_events={process_events}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
