#!/usr/bin/env python3
import hashlib
import json
import os
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
RUN = ROOT / "work/story/run-002"
AUTH = RUN / "authority"
OPTIONS = RUN / "creative-options.json"
GATE = RUN / "creative-options-validation.json"
OUTPUTS = {
    "story_brief": AUTH / "story-brief.json",
    "characters": AUTH / "characters.json",
    "ledger": AUTH / "ledger.json",
    "outline": AUTH / "outline.json",
    "identity_registry": AUTH / "identity-registry.json",
    "reveal_ledger": AUTH / "reveal-ledger.json",
}


def sha256(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def atomic_json(path, value):
    path.parent.mkdir(parents=True, exist_ok=True)
    part = path.with_suffix(path.suffix + ".part")
    data = (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    with open(part, "xb") as handle:
        handle.write(data)
        handle.flush()
        os.fsync(handle.fileno())
    os.replace(part, path)


def main():
    if any(path.exists() for path in OUTPUTS.values()):
        raise RuntimeError("run-002 authority outputs must be virgin")
    options = json.loads(OPTIONS.read_text(encoding="utf-8"))
    gate = json.loads(GATE.read_text(encoding="utf-8"))
    selected = next(row for row in options["options"] if row["option_id"] == options["selected_option_id"])
    if selected["option_id"] != "B" or gate.get("status") != "PASS" or gate.get("verified") is not True:
        raise RuntimeError("selected option B is not terminal Gate 2 PASS")
    if gate.get("options_sha256") != sha256(OPTIONS):
        raise RuntimeError("Creative Options hash drift")

    now = datetime.now(timezone.utc).isoformat()
    common = {
        "run_id": "run-002",
        "status": "locked_pre_prose",
        "selected_option_id": "B",
        "creative_options_sha256": sha256(OPTIONS),
        "creative_options_validation_sha256": sha256(GATE),
        "locked_at": now,
    }

    brief = {
        **common,
        "project_id": "006-Chiec-Ghe-Thu-Bay-Trong-Dem-Doan-Vien",
        "canonical_title": "Chúng Ta Không Cưới Để Thay Ca",
        "length_class": "AS-Standard",
        "tts_words_per_minute": 231,
        "target_words": [9800, 10800],
        "target_minutes": [42.42, 46.75],
        "story_family_primary": "Cưới trước yêu sau",
        "story_family_secondary": "không có",
        "family_selected_before_premise": True,
        "central_names": ["Tống Dao Quỳnh", "Vệ Hoài Túc"],
        "pov": "Ngôi thứ nhất giới hạn - Tống Dao Quỳnh",
        "setting": "Trùng Khánh đương đại hư cấu; khu Bạch Thạch cuối hè nóng khô; không mưa làm motif",
        "premise": selected["premise"],
        "central_question": "Khi hai gia đình đều yêu họ vì họ luôn thay được một ca trống, Dao Quỳnh và Hoài Túc có dám ngừng cho người khác mượn bạn đời để tự sống cuộc hôn nhân của mình không?",
        "theme": "Đáng tin không có nghĩa là luôn có thể bị điều đi; tình yêu bắt đầu khi mỗi người tự chịu phần nghĩa vụ của mình và không đem thời gian của bạn đời đi hứa.",
        "story_engine": "Mười tám ngày dọn nhà với hai gói cứu trợ đối xứng. Mỗi gói giải quyết tiền hoặc chỗ ở nhưng định giá người phối ngẫu như lao động thay ca. Hai lựa chọn cao trào diễn ra riêng tư và song song.",
        "relationship_engine": "Hai người đã chăm nhau qua bình giữ nhiệt, đồng phục và máy giặt nhưng luôn dùng việc làm thay cho quyết định chung. Dao Quỳnh từng có quyền với quỹ; Hoài Túc có chìa khóa nhà miễn phí; mỗi người phải tự từ bỏ lợi thế sai của mình.",
        "external_goal": "Trong mười tám ngày, trả phần nợ bảo lãnh và tìm chỗ ở không buộc một người bỏ nghề hoặc chăm người thân thay gia đình kia.",
        "wrong_belief": "Dao Quỳnh tin giá trị của mình nằm ở việc gánh được việc trước khi ai phải hỏi; nếu cần bàn bạc hoặc từ chối, cô không còn đáng tin.",
        "active_mistake": "Dao Quỳnh dùng quỹ khẩn cấp chung bảo lãnh quầy hàng em trai, rồi hứa Hoài Túc sẽ vào quầy thay em mà không hỏi anh.",
        "antagonistic_force": "Mạng nghĩa vụ hai gia đình được duy trì bằng nhu cầu thật, lòng tốt, lời khen và thói quen mặc định; không cá nhân nào là phản diện.",
        "real_loss": "Quỹ sáu tháng bị thu, căn hộ gia hạn mất, Hoài Túc bỏ kỳ kiểm kê cuối quý và mất thưởng; họ sống tách ba đêm. Không lời xin lỗi nào trả lại các tổn thất ấy.",
        "midpoint": "Căn hộ miễn phí nhà Vệ yêu cầu Dao Quỳnh bỏ ca sáng chăm cha chồng; tiền cứu trợ nhà Tống yêu cầu Hoài Túc vào quầy thay em trai. Hai giải pháp hóa ra cùng đem người phối ngẫu đi thay ca.",
        "climax": "Ở hai nơi khác nhau, Dao Quỳnh chấm dứt bảo lãnh và từ chối tiền có điều kiện; Hoài Túc trả chìa khóa, xóa tên vợ khỏi lịch chăm cha và nhận phần thời gian của mình. Không ai biết người kia cũng đang từ chối đường lui.",
        "ending": "Ba mươi ngày sau trong căn áp mái, máy giặt rung lệch. Cả hai cùng giữ; Dao Quỳnh đi ngủ cho ca mình, Hoài Túc kê chân máy. Khi thức dậy, cô hong đôi giày bụi của anh. Hai việc nối nhau trong một nhà, không ai thay ca cho phần của người kia.",
        "motifs": ["máy giặt cũ rung lệch", "bình giữ nhiệt đỏ", "hai chùm chìa khóa", "thùng carton ghi tên phòng"],
        "content_limits": [
            "nhân vật trưởng thành", "không tình dục trực diện", "không bạo lực đồ họa",
            "không mẹ chồng hoặc em trai phản diện", "không hào môn/liên hôn/cổ phần",
            "không evidence engine hoặc forum công khai", "không bỏ nghề để chứng minh tình yêu",
            "không proposal, permission formula hoặc ending hai chỗ ngồi",
        ],
        "draft_production": {
            "mode": "sequential_single_owner",
            "owner": "Robin",
            "parts": [
                {"part": 1, "path": "work/story/run-002/part-01.txt", "beats": [1, 2, 3, 4, 5, 6]},
                {"part": 2, "path": "work/story/run-002/part-02.txt", "beats": [7, 8, 9, 10, 11, 12]},
                {"part": 3, "path": "work/story/run-002/part-03.txt", "beats": [13, 14, 15, 16, 17, 18]},
                {"part": 4, "path": "work/story/run-002/part-04.txt", "beats": [19, 20, 21, 22, 23, 24]},
            ],
            "aggregate": "work/story/run-002/candidate.txt",
            "assembly_contract": "exact part bytes in order; each part ends one LF; add one LF between parts; aggregate ends one LF",
            "locked_before_drafting": True,
        },
    }

    characters = {
        **common,
        "pov_owner": "Tống Dao Quỳnh",
        "characters": [
            {
                "name": "Tống Dao Quỳnh", "age": 28, "role": "nữ chính; người vợ đã cưới tám tháng; con cả nhà họ Tống",
                "occupation": "trưởng ca sáng bộ phận giặt là khách sạn; nghề tạo thói quen lấp ca nhưng không giải quyết xung đột",
                "external_goal": "trả phần nợ và tìm nhà trong 18 ngày", "emotional_need": "nói đúng khả năng và chịu phần mình",
                "wrong_belief": brief["wrong_belief"], "strength": "nhanh, thực tế, nhớ nhu cầu nhỏ",
                "flaw": "đem tiền và thời gian chồng đi cam kết", "voice": "câu ngắn, đếm bằng ca và mẻ giặt, hài khô",
                "final_action": "chấm dứt bảo lãnh, nhận lịch trả nợ và từ chối tiền cứu trợ có điều kiện",
            },
            {
                "name": "Vệ Hoài Túc", "age": 31, "role": "nam chính; người chồng đã cưới tám tháng; con một nhà họ Vệ",
                "occupation": "nhân viên kiểm kê hàng ca chiều-đêm", "goal": "chăm cha bằng thời gian của mình mà không biến vợ thành ca chăm mặc định",
                "wrong_belief": "im lặng có thể tránh làm vợ khó xử", "strength": "kiên nhẫn, sửa đồ tốt, giữ việc mình nhận",
                "flaw": "để gia đình điền tên vợ vào lịch rồi mới sửa", "voice": "ít câu, cụ thể, không diễn thuyết đạo đức",
                "reciprocal_cost": "bỏ kỳ kiểm kê cuối quý, mất thưởng và nhận quãng đi làm dài hơn",
                "final_action": "trả chìa khóa nhà miễn phí, xóa tên vợ khỏi lịch chăm cha",
            },
            {"name": "Tống Chí Duy", "age": 24, "role": "em trai Dao Quỳnh; chủ quầy đồ ăn sáng", "goal": "giữ quầy", "harm": "xin chị bảo lãnh và muốn anh rể vào thay", "consequence": "đóng quầy, bán thiết bị, đi làm thuê", "voice": "nhanh, lạc quan quá mức"},
            {"name": "Tống Đức Khang", "age": 56, "role": "cha Dao Quỳnh", "goal": "giữ con cái không mang tiếng thất bại", "failure": "đề nghị trả nợ nếu Hoài Túc vào quầy", "voice": "nói bằng phương án và danh dự"},
            {"name": "Vệ Quang Nghiêm", "age": 60, "role": "cha Hoài Túc, đang phục hồi đầu gối", "goal": "đi lại độc lập", "failure": "thuận theo lịch có tên con dâu", "adaptation": "dùng dịch vụ đưa đón hai buổi", "voice": "chậm, tự trọng"},
            {"name": "Vệ Như Tịnh", "age": 57, "role": "mẹ Hoài Túc", "goal": "chồng không lỡ phục hồi", "failure": "đưa nhà trống kèm lịch chăm đã điền tên Dao Quỳnh", "voice": "nhanh, biến lời nhờ thành lịch"},
            {"name": "Kha Mẫn Chi", "age": 39, "role": "chủ căn hộ", "goal": "nhận đủ tiền gia hạn đúng hạn", "function": "cho mốc 18 ngày sau khi ký người thuê mới", "voice": "rõ số tiền và ngày"},
            {"name": "Lạc Thế Bình", "age": 44, "role": "quản lý kiểm kê", "goal": "khóa sổ đủ người", "function": "cắt thưởng khi Hoài Túc bỏ kỳ kiểm kê", "voice": "ngắn, chỉ nói phạm vi việc"},
        ],
        "name_rules": {
            "full_names_required_on_first_appearance": True,
            "supporting_name_check_path": "work/story/run-002/supporting-name-check.json",
            "forbidden_standalone_aliases": ["Quỳnh", "Túc", "Duy", "Khang", "Nghiêm", "Tịnh", "Chi", "Bình"],
        },
    }

    timeline = [
        ("D-18", [2, 3], "nhịp hai ca, quỹ chung, Chí Duy xin bảo lãnh"),
        ("D-16", [1, 4, 5], "cold open/catch-up exact; quỹ bị thu, gia hạn trượt, mốc dọn nhà"),
        ("D-14", [6, 7], "chìa khóa nhà Vệ; thú nhận bảo lãnh"),
        ("D-12", [8, 9], "quầy đóng; lịch phục hồi có tên Dao Quỳnh"),
        ("D-10", [10, 11, 12], "gói trả nợ nhà Tống; Hoài Túc sửa lịch cha"),
        ("D-8", [13, 14], "Dao Quỳnh hứa phần chồng; ba đêm tách ở bắt đầu"),
        ("D-5", [15, 16], "hai gói cứu trợ lộ cùng cơ chế; Hoài Túc mất thưởng"),
        ("D-3", [17, 18], "xem căn áp mái; chia nhau về xử lý gia đình mình"),
        ("D-1", [19, 20], "hai lựa chọn cao trào song song và riêng tư"),
        ("D0", [21, 22], "gặp lại, công khai nguồn lực thật, ký nhà và dọn"),
        ("D+30", [23, 24], "hậu quả còn tồn tại; ending máy giặt"),
    ]
    ledger = {
        **common,
        "timeline": [{"day": day, "beats": beats, "events": events} for day, beats, events in timeline],
        "knowledge": [
            {"fact": "quỹ chung dùng bảo lãnh", "audience": "seed 2, partial 4, full 7", "Dao Quỳnh": "biết trước truyện", "Hoài Túc": "biết tiền mất beat 4, biết nguyên nhân beat 7"},
            {"fact": "nhà Vệ có điều kiện chăm cha", "audience": "seed 6, partial 9, full 15", "Dao Quỳnh": "thấy lịch beat 9", "Hoài Túc": "thấy lịch beat 11"},
            {"fact": "nhà Tống muốn Hoài Túc vào quầy", "audience": "seed 10, partial 13, full 14", "Dao Quỳnh": "hứa beat 13", "Hoài Túc": "biết beat 14"},
            {"fact": "hai người đã yêu qua trách nhiệm", "audience": "seed 3, partial 12/18, payoff 24", "rule": "không dùng lời thú nhận để giải nợ hoặc nhà"},
        ],
        "relationship": [
            {"beat": 3, "before": "vợ chồng lịch sự", "turn": "chăm nhau đúng chi tiết cơ thể/ca", "after": "attraction hiện nhưng chưa gọi tên"},
            {"beat": 7, "before": "Dao Quỳnh giữ quyền thông tin", "turn": "thú nhận bảo lãnh", "after": "niềm tin vỡ"},
            {"beat": 14, "before": "Hoài Túc còn ở nhà", "turn": "biết mình bị hứa vào quầy", "after": "tách ở ba đêm"},
            {"beat": 18, "before": "hai gói cứu trợ có lợi", "turn": "mỗi người tự xử lý gia đình mình", "after": "choice không khán giả"},
            {"beat": 21, "before": "không biết choice của nhau", "turn": "gặp lại với hợp đồng đã chấm dứt và chìa khóa đã trả", "after": "có thể tiếp tục hôn nhân"},
            {"beat": 24, "before": "một tháng ở căn áp mái", "turn": "mỗi người làm đúng phần trong cùng nhà", "after": "hôn nhân thành đời sống chung"},
        ],
        "props": [
            {"prop": "máy giặt cũ", "first_appearance": 1, "payoff": 24, "cannot_support": "không phải vật chứng tình yêu"},
            {"prop": "bình giữ nhiệt đỏ", "first_appearance": 2, "payoff": 18, "cannot_support": "không thay lời xin lỗi"},
            {"prop": "chìa khóa nhà Vệ", "first_appearance": 6, "payoff": 20, "cannot_support": "không đại diện gia đình phê duyệt"},
            {"prop": "thùng carton", "first_appearance": 5, "payoff": 22, "cannot_support": "không dùng như checklist đạo đức"},
        ],
        "continuity_invariants": [
            "timeline 18 ngày phải tính đúng", "cold open 1 và catch-up 4 cùng exact D-16 state",
            "Hoài Túc không biết full bảo lãnh trước beat 7", "Dao Quỳnh không biết điều kiện bỏ ca trước beat 15",
            "Hoài Túc không biết bị hứa vào quầy trước beat 14", "không ai biết choice climax của người kia trước beat 21",
            "ba đêm tách ở và tiền/nhà/thưởng mất không được xóa", "không forum, evidence engine, career exoneration hoặc system reform",
            "không bỏ nghề làm bằng chứng tình yêu", "đạo cụ domestic không thành evidence",
        ],
    }

    beat_data = [
        (1, "Cold open: máy giặt dừng giữa mẻ", "D-16, 05:12: quỹ bị thu, chủ nhà đã ký người mới; Dao Quỳnh chưa thú nhận.", "Quỹ nào biến mất và họ sẽ ở đâu?"),
        (2, "Mười tám ngày trước", "Thiết lập hôn nhân tám tháng, hai ca lệch, quỹ chung; Chí Duy xin bảo lãnh.", "Seed quỹ và thói quen hứa thay."),
        (3, "Những việc nhỏ", "Nước ấm, túi áo vá và máy giặt cho thấy attraction qua chăm sóc.", "Mini-payoff tình cảm."),
        (4, "Catch-up exact cold open", "Exact D-16 05:12; mở rộng cuộc gọi chủ nhà và khoản gia hạn trượt.", "Loop nguyên nhân và 18 ngày."),
        (5, "Mười tám ngày dọn nhà", "Chia thùng; Dao Quỳnh mất quyền điều phối mặc định.", "Power shift một."),
        (6, "Chùm chìa khóa thứ ba", "Như Tịnh đưa nhà trống gần nơi phục hồi của Quang Nghiêm.", "False victory có nhà miễn phí."),
        (7, "Lời thú nhận quỹ chung", "Dao Quỳnh nói đủ bảo lãnh; Hoài Túc tách tiền và chưa tha thứ.", "Consequence sống qua lời xin lỗi."),
        (8, "Quầy hàng đóng cửa", "Chí Duy bán thiết bị; Dao Quỳnh vẫn hứa tìm người vận hành.", "Seed sai lầm thứ hai."),
        (9, "Tên cô trên năm buổi sáng", "Dao Quỳnh thấy lịch phục hồi đã điền tên mình.", "Nhà miễn phí có giá lao động."),
        (10, "Gói trả nợ nhà Tống", "Đức Khang hứa trả nợ nếu quầy được cứu bằng Hoài Túc.", "Gói cứu trợ đối xứng."),
        (11, "Hoài Túc sửa lịch cha", "Anh gạch tên vợ và nhận hai buổi trong thời gian mình có.", "Agency không nói thay."),
        (12, "Một đêm cùng nhà", "Họ ăn mì lúc 02:00 nhưng dùng chăm sóc để né quyết định.", "Attraction và resistance tăng."),
        (13, "Dao Quỳnh hứa phần chồng", "Cô nói Hoài Túc có thể vào quầy ba tháng mà không hỏi.", "Sai lầm lặp bằng thời gian."),
        (14, "Ba đêm không bình giữ nhiệt", "Hoài Túc biết, chuyển ra kho nhỏ ba đêm.", "Real separation."),
        (15, "Hai giải pháp một cái giá", "Nhà Vệ đòi Dao Quỳnh bỏ ca; nhà Tống đòi Hoài Túc vào quầy.", "Midpoint reframe."),
        (16, "Khoản thưởng không còn", "Hoài Túc bỏ kỳ kiểm kê để tự dọn/chăm cha và mất thưởng.", "Reciprocal irreversible cost."),
        (17, "Căn phòng tầng áp mái", "Họ xem căn nóng, rẻ, ở điểm giữa nhưng chưa đủ tiền.", "Shared realistic option."),
        (18, "Chia nhau về nhà mình", "Mỗi người tự trả lời gia đình, không hứa kết quả.", "Setup choices độc lập."),
        (19, "Dao Quỳnh thôi bảo lãnh", "Cô nhận trả nợ, từ chối tiền có điều kiện; Chí Duy đi làm thuê.", "Climax choice một."),
        (20, "Hoài Túc trả chìa khóa", "Anh xóa tên vợ, trả nhà và nhận phần chăm cha của mình.", "Climax choice hai."),
        (21, "Hai bàn tay trống", "Họ gặp với hợp đồng đã đóng và chìa khóa đã trả, công khai nguồn lực thật.", "Recognition không double speech."),
        (22, "Dọn trước giờ bàn giao", "Ký căn áp mái, trả phí chuyển máy, gạch tên phòng cũ.", "Immediate payoff, cost còn."),
        (23, "Ba mươi ngày trả đúng phần", "Nợ, quãng đường, xe phục hồi và việc làm thuê còn tồn tại.", "Consequence persistence."),
        (24, "Máy giặt không còn ai thay ca", "Máy rung; mỗi người hoàn tất phần mình rồi để lại một việc chăm nhau.", "Domestic romantic ending."),
    ]
    outline = {
        **common,
        "canonical_title": brief["canonical_title"],
        "story_family_primary": brief["story_family_primary"],
        "story_family_secondary": brief["story_family_secondary"],
        "teaser_source_beat": 4,
        "teaser_contract": {
            "words": [90, 160], "same_event_catchup_required": True,
            "state_vector": ["D-16 05:12", "bếp căn hộ Bạch Thạch", "máy giặt dừng", "hai vợ chồng có mặt", "quỹ bị thu", "chủ nhà đã ký người mới", "18 ngày", "bảo lãnh chưa thú nhận"],
        },
        "beats": [{"beat": n, "title": title, "event": event, "retention": retention} for n, title, event, retention in beat_data],
        "retention_map": {
            "0-45s": "máy giặt dừng, quỹ mất, căn nhà thuộc người sau", "0-2m": "hôn nhân tám tháng, hai ca, 18 ngày",
            "2-6m": "chăm sóc đời thường rồi mất nhà", "6-12m": "nhà miễn phí và thú nhận bảo lãnh",
            "12-20m": "hai gia đình điền người phối ngẫu vào ca trống", "midpoint": "hai cứu trợ cùng một cơ chế",
            "late_act2": "tách ở, mất thưởng, căn áp mái chưa đủ", "climax": "hai từ chối song song",
            "ending": "máy giặt và hai phần trách nhiệm nối nhau",
        },
        "open_loops": [
            {"id": "L1", "question": "Quỹ mất vì gì?", "opened": 1, "closed": 7},
            {"id": "L2", "question": "Họ ở đâu sau 18 ngày?", "opened": 1, "closed": 22},
            {"id": "L3", "question": "Nhà Vệ có miễn phí?", "opened": 6, "closed": 15},
            {"id": "L4", "question": "Dao Quỳnh có hứa phần chồng nữa?", "opened": 8, "closed": 19},
            {"id": "L5", "question": "Hoài Túc chọn nhà gia đình hay hôn nhân tự chịu phí?", "opened": 11, "closed": 20},
            {"id": "L6", "question": "Họ chăm nhau vì nghĩa vụ hay yêu?", "opened": 3, "closed": 24},
        ],
        "climax_contribution": [
            {"character": "Tống Dao Quỳnh", "must_do": "tự nhận nợ, không điều phối chồng", "cost": "mất cứu trợ và vai chị cứu hộ"},
            {"character": "Vệ Hoài Túc", "must_do": "trả chìa khóa và tự chăm cha", "cost": "mất nhà miễn phí, thưởng và thời gian"},
            {"character": "Tống Chí Duy", "must_do": "đóng quầy và đi làm thuê", "cost": "mất quầy"},
            {"character": "Vệ Như Tịnh", "must_do": "tự mua hai buổi xe", "cost": "mất phương án tiện nhất"},
        ],
    }

    identity = {
        **common,
        "canonical_title": brief["canonical_title"],
        "pov": "Tống Dao Quỳnh",
        "world_fictional": True,
        "visual_identity": [
            {"name": "Tống Dao Quỳnh", "age": 28, "appearance": "phụ nữ Trung Quốc trưởng thành, tóc đen buộc thấp, áo lao động xanh nhạt và tạp dề xám", "must_not": "không váy cưới, không thiếu niên"},
            {"name": "Vệ Hoài Túc", "age": 31, "appearance": "đàn ông Trung Quốc trưởng thành, tóc đen ngắn, dáng gầy chắc, đồng phục kiểm kê xanh than", "must_not": "không vest tổng tài, không vệ sĩ"},
            {"name": "Tống Chí Duy", "age": 24, "appearance": "đàn ông Trung Quốc trẻ trưởng thành, áo quầy ăn vàng đất đã bạc", "must_not": "không biểu cảm phản diện"},
            {"name": "Vệ Như Tịnh", "age": 57, "appearance": "phụ nữ Trung Quốc trung niên, tóc ngắn, tay giữ lịch và túi thuốc", "must_not": "không mẹ chồng ác hoặc hào môn"},
        ],
    }

    reveals = {
        **common,
        "reveals": [
            {"id": "R1", "fact": "quỹ chung dùng bảo lãnh", "seed_beat": 2, "partial_beat": 4, "full_reveal_beat": 7, "provenance": "giao dịch thu tiền rồi Dao Quỳnh tự thú nhận", "cannot_support": ["không chứng minh Chí Duy lừa", "không tự giải quyết hôn nhân"]},
            {"id": "R2", "fact": "nhà Vệ gắn với lịch chăm cha", "seed_beat": 6, "partial_beat": 9, "full_reveal_beat": 15, "provenance": "chìa khóa, lịch có tên, Như Tịnh nói điều kiện", "cannot_support": ["không biến Như Tịnh thành âm mưu", "không cấp quyền quyết thay vợ"]},
            {"id": "R3", "fact": "nhà Tống muốn Hoài Túc vào quầy", "seed_beat": 10, "partial_beat": 13, "full_reveal_beat": 14, "provenance": "Đức Khang nêu, Dao Quỳnh hứa, Chí Duy nói với Hoài Túc", "cannot_support": ["không biến lời hứa thành đồng ý của chồng"]},
            {"id": "R4", "fact": "Hoài Túc sửa lịch nhưng chưa trả chìa khóa", "seed_beat": 11, "partial_beat": 15, "full_reveal_beat": 20, "provenance": "gạch tên vợ rồi hoàn tất bằng trả chìa khóa", "cannot_support": ["không xóa lỗi im lặng ban đầu"]},
            {"id": "R5", "fact": "họ đã yêu qua trách nhiệm", "seed_beat": 3, "partial_beat": 12, "full_reveal_beat": 24, "provenance": "nước ấm, vá áo, thuốc tay, bình giữ nhiệt và máy giặt", "cannot_support": ["không giải nợ/nhà", "không thay choice climax", "không đóng bằng lời mời"]},
        ],
        "reveal_invariants": ["seed không giải thích đủ provenance", "reveal là hậu quả đời thường, không evidence IDs", "R5 chỉ payoff sau hai climax choices"],
    }

    documents = {
        "story_brief": brief,
        "characters": characters,
        "ledger": ledger,
        "outline": outline,
        "identity_registry": identity,
        "reveal_ledger": reveals,
    }
    for key, path in OUTPUTS.items():
        atomic_json(path, documents[key])
    for key, path in OUTPUTS.items():
        parsed = json.loads(path.read_text(encoding="utf-8"))
        if parsed != documents[key]:
            raise RuntimeError(f"authority readback mismatch: {key}")

    supporting = {
        "status": "PASS", "verified": True, "run_id": "run-002",
        "corpus_inventory_sha256": sha256(RUN / "corpus-inventory.json"), "corpus_project_count": 17,
        "method": "exact full-name and two-token given-name search across 17 hash-verified extracted canonical prose",
        "names": [
            {"full_name": "Tống Chí Duy", "given_name": "Chí Duy", "full_name_hits": 0, "given_name_hits": 0},
            {"full_name": "Kha Mẫn Chi", "given_name": "Mẫn Chi", "full_name_hits": 0, "given_name_hits": 0},
            {"full_name": "Lạc Thế Bình", "given_name": "Thế Bình", "full_name_hits": 0, "given_name_hits": 0},
            {"full_name": "Vệ Như Tịnh", "given_name": "Như Tịnh", "full_name_hits": 0, "given_name_hits": 0},
            {"full_name": "Tống Đức Khang", "given_name": "Đức Khang", "full_name_hits": 0, "given_name_hits": 0},
            {"full_name": "Vệ Quang Nghiêm", "given_name": "Quang Nghiêm", "full_name_hits": 0, "given_name_hits": 0},
        ],
        "generated_at": now,
    }
    atomic_json(RUN / "supporting-name-check.json", supporting)
    print(json.dumps({
        "status": "locked_pre_prose", "authority_files": 6,
        "authority_hashes": {key: sha256(path) for key, path in OUTPUTS.items()},
        "supporting_name_check_sha256": sha256(RUN / "supporting-name-check.json"),
    }, ensure_ascii=False))


if __name__ == "__main__":
    main()
