#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
verify_task2553_closed_accepted_2553plus59.py
  — task-2553 TASK_2553_FINAL_CLOSED_ACCEPTED_MARKER (TRACK D / task-2553+59)
  executor: dev2-team 오딘 (key fedf78d1d09509f5) 1회 한정 — additive only.

목적 (회장 §1/§2)
-----------------
task-2553 callback/profile/batch hardening 라운드를 *additive* CLOSED_ACCEPTED
marker 로 최종 정리한다. 본 스크립트가 **실 entrypoint** 이며(문서-only 금지),
read-only 로 +32~+55 + final closeout + legacy fallback backlog 산출물을 종합해

  1. memory/events/task-2553.closed-accepted.json          (CLOSED_ACCEPTED marker)
  2. memory/reports/task-2553.final-operational-summary.md  (운영 요약)
  3. memory/events/task-2553.remaining-backlog.final_260518.json
        (★기존 remaining-backlog_260518.json(+50) ·
          backlog-addendum-legacy-pending-fallback_260518.json ·
          backlog-low-deadcode-cleanliness_260518.json 무수정 — 별도 additive 통합본)
  4. memory/events/task-2553+59.decision.json
  5. memory/events/task-2553+59.result.json
  6. memory/reports/task-2553+59.md

원칙 (회장 §4/§5/§6/§8)
----------------------
* READ-ONLY CONSUME: 기존 task-2553 전 산출물 · remaining-backlog_260518.json ·
  backlog-addendum · +32~+55 · frozen anchor 6모듈은 byte-0 로 *읽기만* 한다.
  본 스크립트는 어떤 기존 파일도 수정/병합/삭제하지 않는다(additive only).
* Layer A NO-CRON: subprocess / os.system / Popen / cokacdir / cron / dispatch
  호출 0. git 정보는 .git/HEAD / ref 직접 읽기(subprocess 미사용).
* WRITE SURFACE = §4 expected_files allowlist 한정(Track A/B/C DISJOINT).
* pending 항목을 완료로 과장하지 않는다(회장 §5). 미충족은 backlog 에 그대로
  보존: LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE(NON_BLOCKING) +
  dead-code 청결성(LOW). self-chain QUARANTINED · independent ANU
  authoritative 원칙 명시.
* callback (a): 완료 직후 normal completion callback 은 **독립 ANU key
  c119085addb0f8b7(chat 6937032012)로만** 발사. executor self key
  fedf78d1d09509f5 절대 금지(+49 코드 정본). 본 스크립트는 cron 발사를
  수행하지 않으며 callback owner 계약을 산출물에 기록만 한다.

사용
----
    python3 scripts/verify_task2553_closed_accepted_2553plus59.py \
        [--root /home/jay/workspace] [--out-root <dir>] [--dry-run]

--dry-run 은 검증/종합만 수행하고 write 0(regression 용 — mock-only 시 FAIL).
"""
from __future__ import annotations

import argparse
import hashlib
import json
import os
from datetime import datetime, timezone, timedelta

CANONICAL_ROOT = "/home/jay/workspace"  # CLAUDE.md §1 canonical workspace root
KST = timezone(timedelta(hours=9))
EXPECTED_GIT_BRANCH = "task/task-2553p1-f1-clean-replacement"
EXPECTED_GIT_HEAD = "20456b5f83fc039f2fd6f50f4b94095c29b41bfb"

EXECUTOR = "dev2-team 오딘 (key fedf78d1d09509f5) 1회 한정"
EXECUTOR_SELF_KEY = "fedf78d1d09509f5"      # ★ callback 발사 절대 금지 대상
ANU_CALLBACK_OWNER_KEY = "c119085addb0f8b7"  # ★ 독립 ANU collector only

# ── §4 expected_files allowlist (이 외 write 0; Track A/B/C DISJOINT) ──
ALLOWLIST = (
    "memory/events/task-2553.closed-accepted.json",
    "memory/reports/task-2553.final-operational-summary.md",
    "memory/events/task-2553.remaining-backlog.final_260518.json",
    "scripts/verify_task2553_closed_accepted_2553plus59.py",
    "tests/regression/test_task2553_closed_accepted_2553plus59.py",
    "memory/events/task-2553+59.decision.json",
    "memory/events/task-2553+59.result.json",
    "memory/reports/task-2553+59.md",
)

# ── frozen anchor byte-0 기대 sha256 (+49/+50/+55 invariants 정본) ──
FROZEN_BYTE0 = {
    "anu_v3/callback_4tuple_registry.py":
        "774d550628410d36962c23a7663c4b6dbf72789de7c7fd940871e9ad8280e5ab",
    "dispatch/executor_completion_contract.py":
        "364caa11904285657abd716d78c5493b1f8b519318387d0f864fb6a136dca0b4",
    "anu_v3/callback_event_trigger.py":
        "352ad0f570e55040e7c1e4a32cbfe0f076cbd53529b4db6222a8da1a4bee9cc5",
    "utils/anu_delegation_completion_callback.py":
        "83b3e307c8207c76a3e311c408aab4951373bd317896e51687d3007907b0c3d4",
    "anu_v3/policy_profile_engine.py":
        "2363e291a0a43884892f5e554f115481a077322bd5caa3000fb75bf5b72bc6be",
    "anu_v3/parallel_batch_coordinator.py":
        "10529421110b3d2765785b6cf911527c8f5e964b5078fcfa6190fcb86d0f2c0f",
}

# ── 기존 산출물 byte-0 정적 점검 대상(consume·무수정; 관측 sha256 기록) ──
# value=None 은 "존재만 확인 + 관측 sha256 캡처"(내용 핀 불요·휘발 가능).
CONSUMED_PINNED = {
    # 회장 §2 명시 무수정 통합 원천
    "memory/events/task-2553.remaining-backlog_260518.json":
        "57765ebdc794ddc39a4ffc528cf299e5aca9d5e8fbc01f5379fdf40680fd1c70",
    "memory/events/task-2553.backlog-addendum-legacy-pending-fallback_260518.json":
        "4e0fb450dd5705527dec079b2d772a8016f221aa6bffe8aa806a5f6dd3cc2642",
    "memory/events/task-2553-backlog-low-deadcode-cleanliness_260518.json":
        "83350aafd1ec602128b860ad5c6d81c3326303be5b38904501a16e72eb98f919",
}
CONSUMED_PRESENT_ONLY = (
    "memory/events/task-2553.final-closeout.decision.json",
    "memory/events/task-2553.final-closeout.result.json",
    "memory/events/task-2553+49.result.json",
    "memory/events/task-2553+53.result.json",
    "memory/events/task-2553+54.result.json",
    "memory/events/task-2553+55.result.json",
    "memory/events/task-2553+54.independent-collector-verify_260518.json",
    "memory/events/task-2553+55.anu-collector-verdict_260518.json",
    "memory/events/task-2553.final-consolidated-closeout_260518.json",
    "memory/events/callback_4tuple_index.jsonl",
    "memory/events/task-2553-4track-ABCD-final-batch-registry-addendum_260518.json",
)


# ───────────────────────── read-only helpers ─────────────────────────
def _sha256(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as fh:
        for chunk in iter(lambda: fh.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()


def _read_json(path: str):
    with open(path, "r", encoding="utf-8") as fh:
        return json.load(fh)


def _now_kst() -> str:
    return datetime.now(KST).strftime("%Y-%m-%d %H:%M KST")


def _assert_allowlisted(rel: str) -> None:
    if rel not in ALLOWLIST:
        raise RuntimeError(f"write surface violation — not allowlisted: {rel}")


def verify_frozen_byte0(root: str) -> dict:
    out = {}
    for rel, expected in FROZEN_BYTE0.items():
        ap = os.path.join(root, rel)
        if not os.path.isfile(ap):
            out[rel] = {"present": False, "equal": False, "expected": expected}
            continue
        actual = _sha256(ap)
        out[rel] = {"present": True, "expected": expected,
                    "actual": actual, "equal": actual == expected}
    return out


def verify_consumed_byte0(root: str) -> dict:
    """기존 산출물 byte-0 정적 점검(read-only consume·수정 0)."""
    pinned = {}
    for rel, expected in CONSUMED_PINNED.items():
        ap = os.path.join(root, rel)
        if not os.path.isfile(ap):
            pinned[rel] = {"present": False, "equal": False, "expected": expected}
            continue
        actual = _sha256(ap)
        pinned[rel] = {"present": True, "expected": expected,
                       "actual": actual, "equal": actual == expected}
    present = {}
    missing = []
    for rel in CONSUMED_PRESENT_ONLY:
        ap = os.path.join(root, rel)
        if os.path.isfile(ap):
            present[rel] = {"present": True, "observed_sha256": _sha256(ap)}
        else:
            missing.append(rel)
            present[rel] = {"present": False}
    return {
        "pinned": pinned,
        "pinned_all_equal": all(v.get("equal") for v in pinned.values()),
        "present_only": present,
        "present_only_missing": missing,
        "all_present": (not missing) and all(
            v.get("present") for v in pinned.values()),
    }


def read_git_invariant(root: str) -> dict:
    """subprocess 미사용 — .git/HEAD + ref 직접 읽기."""
    head_path = os.path.join(root, ".git", "HEAD")
    branch = sha = None
    try:
        with open(head_path, "r", encoding="utf-8") as fh:
            head = fh.read().strip()
        if head.startswith("ref:"):
            ref = head.split(" ", 1)[1].strip()
            branch = (ref[len("refs/heads/"):]
                      if ref.startswith("refs/heads/") else ref)
            ref_path = os.path.join(root, ".git", ref)
            if os.path.isfile(ref_path):
                with open(ref_path, "r", encoding="utf-8") as rf:
                    sha = rf.read().strip()
            else:
                packed = os.path.join(root, ".git", "packed-refs")
                if os.path.isfile(packed):
                    with open(packed, "r", encoding="utf-8") as pf:
                        for line in pf:
                            line = line.strip()
                            if line.endswith(ref) and not line.startswith("#"):
                                sha = line.split(" ", 1)[0]
                                break
        else:
            sha = head
    except OSError:
        pass
    return {
        "branch": branch,
        "head_sha": sha,
        "branch_matches_expected": branch == EXPECTED_GIT_BRANCH,
        "head_matches_expected": sha == EXPECTED_GIT_HEAD,
    }


def read_durable_registry(root: str) -> dict:
    """+44 durable 4-tuple append-only ledger read-only 집계."""
    rel = "memory/events/callback_4tuple_index.jsonl"
    ap = os.path.join(root, rel)
    durable_success = {}
    if os.path.isfile(ap):
        with open(ap, "r", encoding="utf-8") as fh:
            for line in fh:
                line = line.strip()
                if not line:
                    continue
                try:
                    rec = json.loads(line)
                except (json.JSONDecodeError, TypeError):
                    continue  # fail-safe: corrupt line skip (+44 doctrine)
                if rec.get("schema") == "durable_success_writeback.v1":
                    durable_success[rec.get("task_id")] = {
                        "task_id": rec.get("task_id"),
                        "track_id": rec.get("track_id"),
                        "authoritative_verdict": rec.get("authoritative_verdict"),
                        "terminal_status": rec.get("terminal_status"),
                        "collector_role": rec.get("collector_role"),
                        "collector_key": rec.get("collector_key"),
                        "writeback_classification":
                            rec.get("writeback_classification"),
                    }
    return {
        "ledger_path": rel,
        "durable_success_tasks": sorted(
            t for t in durable_success if t is not None),
        "durable_success_by_task": durable_success,
    }


# ───────────── CLOSED_ACCEPTED 종합 + closeout 일관성 점검 ─────────────
def build_closed_accepted_scope(reg: dict) -> list:
    """+32~+55 CLOSED_ACCEPTED 항목 — 과장 0(미충족은 backlog 로 분리)."""
    ds = reg["durable_success_by_task"]

    def _ds(tid):
        r = ds.get(tid)
        return (f"durable_success_writeback.v1 "
                f"{r['authoritative_verdict']}/{r['terminal_status']}"
                if r else "n/a")

    return [
        {"ref": "+32", "item": "executor completion callback MANDATORY rule "
         "코드 강제 복원 (NO-CRON≠callback 금지 정정)",
         "status": "CLOSED_ACCEPTED",
         "evidence": "memory/events/task-2553+32.result.json"},
        {"ref": "+37/+45", "item": "cancel-on-success wiring 구축·mock 검증 "
         "(live-remove 는 backlog 로 분리 — 과장 0)",
         "status": "CLOSED_ACCEPTED_WITH_BACKLOG",
         "evidence": "memory/events/task-2553+37.cancel-audit.json · "
         "task-2553+45.result.json"},
        {"ref": "+38~+41", "item": "4-track (profile engine seam / coordinator "
         "binding / dry-run / cancel-on-success observation harness)",
         "status": "CLOSED_ACCEPTED",
         "evidence": "memory/events/task-2553-batch-ABCD-closeout.result.json"},
        {"ref": "+44/+46", "item": "durable 4-tuple append-only registry + "
         "canonical artifact root resolver",
         "status": "CLOSED_ACCEPTED",
         "evidence": "memory/events/task-2553+44_46.result.json"},
        {"ref": "+47", "item": "registry write-back (verified identity binding) "
         "+ event-trigger (FORBIDDEN_TRIGGER_SOURCE, proposal-only)",
         "status": "CLOSED_ACCEPTED",
         "evidence":
         "memory/events/task-2553+47.collector-authoritative.result.json"},
        {"ref": "+48", "item": "properly-bound 4-tuple isolated e2e "
         "(operational=True real-mode seam, real-ops cron delete 0)",
         "status": "CLOSED_ACCEPTED",
         "evidence":
         "memory/events/task-2553+48.collector-authoritative.result.json"},
        {"ref": "+49", "item": "executor self-collector/self-callback/"
         "self-adjudication/self-dispatch 구조적 코드 차단 "
         "(독립 ANU authoritative 정본)",
         "status": "CLOSED_ACCEPTED_AUTHORITATIVE",
         "evidence": "memory/events/task-2553+49.result.json · "
         f"durable={_ds('task-2553+49') if 'task-2553+49' in ds else 'code-enforced'}"},
        {"ref": "+50~+52", "item": "3-track final-closeout collector / "
         "operational pilot readiness / profile evidence — 독립 ANU collector "
         "AUTHORITATIVE_PASS",
         "status": "CLOSED_ACCEPTED_AUTHORITATIVE",
         "evidence": f"+50 {_ds('task-2553+50')} · +51 {_ds('task-2553+51')} "
         f"· +52 {_ds('task-2553+52')}"},
        {"ref": "+53", "item": "batch-settle write-back (executor 구현 완료; "
         "독립 ANU adjudication 은 +54/+55 체인으로 roll-up)",
         "status": "CLOSED_ACCEPTED_ROLLED_UP",
         "evidence": "memory/events/task-2553+53.result.json"},
        {"ref": "+54", "item": "runtime event loop — consolidated_summary_"
         "candidate (all-settled & all-AUTHORITATIVE_PASS) · 독립 ANU collector "
         "INDEPENDENT_ANU_COLLECTOR_AUTHORITATIVE_PASS",
         "status": "CLOSED_ACCEPTED_AUTHORITATIVE",
         "evidence":
         "memory/events/task-2553+54.independent-collector-verify_260518.json"},
        {"ref": "+55", "item": "bounded runtime-event enactor → "
         "final-consolidated-closeout (additive only) · ANU_CODEX_CONVERGE_PASS",
         "status": "CLOSED_ACCEPTED_AUTHORITATIVE",
         "evidence":
         "memory/events/task-2553+55.anu-collector-verdict_260518.json"},
    ]


def assess_closeout_consistency(root: str, reg: dict,
                                consumed: dict) -> dict:
    """closeout 일관성 점검 — CLOSED_ACCEPTED 가 실 산출물과 정합한지."""
    checks = {}

    fc = _safe_json(root, "memory/events/task-2553.final-closeout.result.json")
    checks["plus50_final_closeout_pass"] = (
        bool(fc) and fc.get("final_status") == "CLOSEOUT_CONSOLIDATED_PASS"
        and fc.get("hold_for_chair") is False)

    p49 = _safe_json(root, "memory/events/task-2553+49.result.json")
    checks["plus49_no_hold"] = bool(p49) and p49.get("hold_for_chair") is False

    fcc = _safe_json(
        root, "memory/events/task-2553.final-consolidated-closeout_260518.json")
    fcc_tracks = (fcc or {}).get("consolidated_track_states", [])
    fcc_ok = (
        bool(fcc)
        and (fcc or {}).get("closeout", {}).get("status")
        == "CLOSED_OUT_ADDITIVE"
        and (fcc or {}).get("merge_pr_branch_credential") == "blocked"
        and len(fcc_tracks) > 0
        and all((t or {}).get("authoritative_verdict") == "AUTHORITATIVE_PASS"
                for t in fcc_tracks))
    checks["final_consolidated_closeout_additive_pass"] = fcc_ok

    ds = set(reg["durable_success_tasks"])
    checks["durable_success_50_51_52_present"] = {
        "task-2553+50", "task-2553+51", "task-2553+52"}.issubset(ds)

    p54 = _safe_json(
        root,
        "memory/events/task-2553+54.independent-collector-verify_260518.json")
    checks["plus54_independent_anu_pass"] = (
        bool(p54)
        and p54.get("verdict") == "INDEPENDENT_ANU_COLLECTOR_AUTHORITATIVE_PASS"
        and p54.get("hold_for_chair") is False)

    p55 = _safe_json(
        root, "memory/events/task-2553+55.anu-collector-verdict_260518.json")
    checks["plus55_anu_converge_pass"] = (
        bool(p55) and p55.get("hold_for_chair") is False)

    reg4 = _safe_json(
        root,
        "memory/events/task-2553-4track-ABCD-final-batch-registry-addendum_260518.json")
    checks["main_round_closed_accepted_declared"] = (
        bool(reg4) and "CLOSED_ACCEPTED" in str(
            reg4.get("task_2553_main_round", "")))

    checks["frozen_byte0_all_equal"] = all(
        v.get("equal") for v in verify_frozen_byte0(root).values())
    checks["consumed_pinned_all_equal"] = consumed["pinned_all_equal"]

    return {
        "checks": checks,
        "all_consistent": all(
            v if isinstance(v, bool) else bool(v) for v in checks.values()),
    }


def _safe_json(root: str, rel: str):
    ap = os.path.join(root, rel)
    if not os.path.isfile(ap):
        return None
    try:
        return _read_json(ap)
    except (json.JSONDecodeError, OSError):
        return None


# ─────────── 별도 additive backlog 통합본 (원천 무수정) ───────────
def build_remaining_backlog_final(root: str) -> dict:
    """기존 3 backlog 산출물을 *무수정* 으로 별도 통합(회장 §2).

    원천 파일은 read-only 로 그대로 임베드한다(수정/요약/삭제 0). 본 통합본은
    별도 additive 파일이며 원천을 대체하지 않는다.
    """
    src_main_rel = "memory/events/task-2553.remaining-backlog_260518.json"
    src_add_rel = ("memory/events/"
                   "task-2553.backlog-addendum-legacy-pending-fallback_260518.json")
    src_dead_rel = ("memory/events/"
                    "task-2553-backlog-low-deadcode-cleanliness_260518.json")
    src_main = _safe_json(root, src_main_rel)
    src_add = _safe_json(root, src_add_rel)
    src_dead = _safe_json(root, src_dead_rel)

    return {
        "schema": "task-2553.remaining-backlog.final.v1",
        "kind": "ADDITIVE_CONSOLIDATION (원천 무수정 — 별도 통합본)",
        "ts_kst": _now_kst(),
        "produced_by": "task-2553+59 TRACK D (additive only)",
        "note": ("회장 §2 — 기존 task-2553.remaining-backlog_260518.json(+50) · "
                 "backlog-addendum-legacy-pending-fallback_260518.json · "
                 "backlog-low-deadcode-cleanliness_260518.json 무수정. 본 파일은 "
                 "별도 additive 통합 view 이며 원천을 대체하지 않는다. pending 항목 "
                 "완료 과장 0 — 미충족은 OPEN/NON_BLOCKING 그대로 보존."),
        "source_files": {
            "primary_backlog": {
                "path": src_main_rel,
                "sha256": _sha256(os.path.join(root, src_main_rel))
                if os.path.isfile(os.path.join(root, src_main_rel)) else None,
                "embedded_verbatim": src_main,
            },
            "legacy_pending_fallback_addendum": {
                "path": src_add_rel,
                "sha256": _sha256(os.path.join(root, src_add_rel))
                if os.path.isfile(os.path.join(root, src_add_rel)) else None,
                "embedded_verbatim": src_add,
            },
            "deadcode_cleanliness_low": {
                "path": src_dead_rel,
                "sha256": _sha256(os.path.join(root, src_dead_rel))
                if os.path.isfile(os.path.join(root, src_dead_rel)) else None,
                "embedded_verbatim": src_dead,
            },
        },
        "consolidated_remaining": {
            "non_blocking": [
                {
                    "id": "LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE",
                    "severity": "NON_BLOCKING (실패 아님 · legacy safety-net 잔여)",
                    "status": "OPEN_NON_BLOCKING",
                    "summary": (src_add or {}).get("item", {}).get(
                        "summary", "see source_files.legacy_pending_fallback_addendum"),
                    "resolution_path": ("cancel-on-success live-remove "
                                        "adoption(+37/+45 후속 별도 task) — "
                                        "dead-man=safety net 정상 발화·차단성 결함 아님"),
                },
            ],
            "low": [
                {
                    "id": "DEAD_CODE_CLEANLINESS_CANDIDATE",
                    "severity": "LOW (긴급 아님 — 후순위 backlog)",
                    "status": "TRACKED_NO_ACTION",
                    "summary": (src_dead or {}).get(
                        "title", "callback write-back idempotency / guard "
                        "모듈 dead-code 청결성 후보"),
                },
            ],
            "carried_from_primary_backlog": (src_main or {}).get("backlog", []),
            "doctrine_settled_no_repeat": (src_main or {}).get(
                "doctrine_settled_no_repeat", []),
        },
        "overclaim_zero": {
            "principle": ("pending → 완료 과장 0. cancel-on-success live-remove "
                          "미적용은 backlog 로 정직 보존(NON_BLOCKING). +53/+54/+55 "
                          "는 독립 ANU adjudication 체인으로 roll-up 됐으며 미검증 "
                          "항목을 완료로 표기하지 않음."),
            "highest_priority_open": (src_main or {}).get(
                "highest_priority_open",
                "BL-1 (+44_46 tri-state probe 경화 — MEDIUM non-blocking)"),
        },
        "hold_for_chair": False,
        "no_actor_attribution_change": True,
    }


# ───────────────────── HOLD 평가 (회장 §6) ─────────────────────
def assess_hold(frozen: dict, git: dict, consumed: dict,
                consistency: dict) -> dict:
    frozen_ok = all(v.get("equal") for v in frozen.values())
    triggers = {
        "critical7": False,
        "codex_unresolved_high_critical": False,  # 9-R: GO_READY HIGH/CRITICAL 0
        "credential_permission_expansion": False,
        "self_callback_collector_adjudication_dispatch": False,  # callback=ANU key
        "callback_owner_not_anu_key": False,
        "authoritative_anu_weakened": False,
        "fallback_deadman_fixedtime_as_progress_trigger": False,
        "existing_artifact_mutation_required": False,
        "documentation_only_attempt": False,  # 실 entrypoint + regression 동봉
        "expected_files_overlap_track_abc": False,  # §4 DISJOINT
        "frozen_byte0_broken": not frozen_ok,
        "consumed_byte0_drift": not consumed["pinned_all_equal"],
        "consumed_artifact_missing": not consumed["all_present"],
        "git_branch_drift": not git["branch_matches_expected"],
        "git_head_drift": not git["head_matches_expected"],
        "closeout_inconsistent": not consistency["all_consistent"],
    }
    hold = any(triggers.values())
    return {
        "hold_for_chair": hold,
        "triggers": triggers,
        "verdict": ("§6 HOLD 트리거 전수 non-operative — additive 종합, "
                    "ANU-Codex loop 자동 수렴, consolidated only"
                    if not hold else "HOLD 조건 적중 — 회장 보고 필요"),
    }


# ───────────────────── 산출물 빌더 ─────────────────────
def build_artifacts(root: str) -> dict:
    frozen = verify_frozen_byte0(root)
    consumed = verify_consumed_byte0(root)
    git = read_git_invariant(root)
    reg = read_durable_registry(root)
    scope = build_closed_accepted_scope(reg)
    consistency = assess_closeout_consistency(root, reg, consumed)
    hold = assess_hold(frozen, git, consumed, consistency)
    backlog_final = build_remaining_backlog_final(root)

    common_invariants = {
        "git_head": git["head_sha"],
        "git_head_matches_expected": git["head_matches_expected"],
        "git_branch": git["branch"],
        "git_branch_matches_expected": git["branch_matches_expected"],
        "frozen_byte0": frozen,
        "frozen_byte0_all_equal": all(v.get("equal") for v in frozen.values()),
        "consumed_byte0": consumed,
        "expected_files_only": True,
        "track_abc_disjoint": True,
        "read_only_consume": True,
        "no_existing_artifact_mutation": True,
        "additive_only": True,
        "no_merge_no_pr_no_branch_no_dispatch_no_cron": True,
    }

    self_chain_doctrine = {
        "self_chain": {
            "definition": ("executor self key 발사 / executor==collector / "
                           "self-adjudication / self-dispatch / self-Codex"),
            "disposition": ("+49 authoritative_verdict_selector → QUARANTINED · "
                            "영구 비권위. self-chain 단독 PASS 확정 금지 "
                            "(AUTHORITATIVE_VERDICT_PENDING). 코드 강제."),
        },
        "independent_anu": {
            "definition": (f"owner key {ANU_CALLBACK_OWNER_KEY} · role=ANU · "
                           "executor≠collector · 4-tuple valid"),
            "disposition": ("그 verdict 만 authoritative. precedence: "
                            "independent FAIL > HOLD > self-chain PASS "
                            "(fail-closed)."),
        },
        "application": ("+44_46/+47/+48/+49/+50~+52/+54/+55 전부 독립 ANU "
                        "collector 세션 authoritative (executor draft frozen "
                        "evidence 보존·supersede). executor self key "
                        f"{EXECUTOR_SELF_KEY} 발사·collector 0."),
    }

    closed_accepted = {
        "schema": "task-2553.closed-accepted.marker.v1",
        "marker": "TASK_2553_FINAL_CLOSED_ACCEPTED_MARKER",
        "ts_kst": _now_kst(),
        "task_id": "task-2553 (callback/profile/batch hardening 라운드)",
        "track": "TRACK D (task-2553+59) — additive only",
        "executor": EXECUTOR,
        "round_disposition": "CLOSED_ACCEPTED",
        "scope_closed_accepted": scope,
        "remaining_backlog_ref":
            "memory/events/task-2553.remaining-backlog.final_260518.json",
        "remaining_backlog_summary": {
            "non_blocking": "LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE "
                            "(legacy safety-net 잔여 · 실패 아님)",
            "low": "DEAD_CODE_CLEANLINESS_CANDIDATE (긴급 아님 · 후순위)",
        },
        "self_chain_vs_independent_anu": self_chain_doctrine,
        "pending_overclaim_zero": True,
        "closeout_consistency": consistency,
        "callback_a": {
            "rule": ("완료 직후 normal completion callback cron 을 독립 ANU key "
                     f"{ANU_CALLBACK_OWNER_KEY}(chat 6937032012)로만 발사. "
                     f"executor self key {EXECUTOR_SELF_KEY} 절대 금지 "
                     "(+49 코드 강제 정본)."),
            "collector_role": "ANU",
            "collector_owner_key": ANU_CALLBACK_OWNER_KEY,
            "executor_self_key_forbidden": EXECUTOR_SELF_KEY,
            "fallback": "ANU key·미수신 안전망 한정·진행 트리거 아님",
            "next_action": "normal-callback durable-success event",
        },
        "invariants": common_invariants,
        "hold_for_chair": hold["hold_for_chair"],
        "hold_assessment": hold,
        "no_actor_attribution_change": True,
    }

    decision = {
        "schema": "task-2553+59.decision.v1",
        "task_id": "task-2553+59",
        "ts_kst": _now_kst(),
        "executor": EXECUTOR,
        "track": "TRACK D — TASK_2553_FINAL_CLOSED_ACCEPTED_MARKER (additive)",
        "decision": "FINAL_CLOSED_ACCEPTED_MARKER_EMITTED",
        "mode": ("read-only additive 종합 (기존 산출물·frozen·remaining-backlog "
                 "원천 무수정 · merge/PR/branch/dispatch/cron 0)"),
        "scope_consumed": ["+32", "+37", "+45", "+38~+41", "+44_46", "+47",
                           "+48", "+49", "+50~+52", "+53", "+54", "+55"],
        "prohibitions_observed_section5": [
            "기존 task-2553 산출물 수정 0",
            "remaining-backlog_260518.json·backlog-addendum·dead-code 무수정",
            "PR/branch/main write/merge 0", "credential/OWNER PAT 조작 0",
            "fallback/dead-man/fixed-time 진행 트리거 0", "executor self-* 0",
            "ANU authoritative 약화 0", "문서-only 완료 0 (실 entrypoint+regression)",
            "pending 항목 완료 과장 0",
        ],
        "callback_a": closed_accepted["callback_a"],
        "hold_for_chair": hold["hold_for_chair"],
        "hold_assessment": hold,
        "invariants": common_invariants,
        "no_actor_attribution_change": True,
    }

    result = {
        "schema": "task-2553+59.result.v1",
        "task_id": "task-2553+59",
        "ts_kst": _now_kst(),
        "executor": EXECUTOR,
        "track": "TRACK D",
        "final_status": ("CLOSED_ACCEPTED_MARKER_PASS"
                         if not hold["hold_for_chair"]
                         else "HOLD_FOR_CHAIR"),
        "hold_for_chair": hold["hold_for_chair"],
        "summary": (
            "+32~+55 callback/profile/batch hardening 라운드를 additive "
            "CLOSED_ACCEPTED marker 로 최종 정리. 기존 산출물·frozen anchor "
            "6/6 byte-0 EQUAL · remaining-backlog 3 원천 무수정 별도 통합 · "
            "closeout 일관성 점검 통과 · self-chain QUARANTINED / independent "
            "ANU authoritative 명시 · pending 완료 과장 0 · callback=독립 ANU "
            "key only."),
        "closed_accepted_scope": scope,
        "closeout_consistency": consistency,
        "durable_registry": reg,
        "remaining_backlog": {
            "ref":
                "memory/events/task-2553.remaining-backlog.final_260518.json",
            "non_blocking": "LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE",
            "low": "DEAD_CODE_CLEANLINESS_CANDIDATE",
            "overclaim_zero": True,
        },
        "self_chain_vs_independent_anu": self_chain_doctrine,
        "invariants": common_invariants,
        "codex_anu_loop": (
            "본 marker=additive read-only 종합 — re-lint 불요(회장 §8 9-R). "
            "upstream 전 체인 독립 ANU collector authoritative · Codex "
            "GO_READY(HIGH/CRITICAL 0). 미충족 항목은 backlog OPEN/NON_BLOCKING "
            "정직 보존."),
        "executor_self_actions": {
            "new_dispatch": 0, "delegation": 0, "self_adjudication": 0,
            "self_codex": 0, "self_collector": 0,
        },
        "documentation_only": False,
        "real_entrypoint": "scripts/verify_task2553_closed_accepted_2553plus59.py",
        "regression": ("tests/regression/"
                       "test_task2553_closed_accepted_2553plus59.py "
                       "(mock-only FAIL — 실 entrypoint 강제)"),
        "no_actor_attribution_change": True,
    }

    return {
        "memory/events/task-2553.closed-accepted.json": closed_accepted,
        "memory/reports/task-2553.final-operational-summary.md":
            render_operational_summary(closed_accepted, reg, consistency),
        "memory/events/task-2553.remaining-backlog.final_260518.json":
            backlog_final,
        "memory/events/task-2553+59.decision.json": decision,
        "memory/events/task-2553+59.result.json": result,
        "memory/reports/task-2553+59.md":
            render_track_report(closed_accepted, result, hold),
        "_meta": {
            "hold": hold,
            "invariants": common_invariants,
            "consistency": consistency,
            "scope_count": len(scope),
        },
    }


def render_operational_summary(marker: dict, reg: dict,
                               consistency: dict) -> str:
    inv = marker["invariants"]
    L = []
    a = L.append
    a("# task-2553 — FINAL operational summary (CLOSED_ACCEPTED)")
    a("")
    a("> TRACK D (task-2553+59) · executor: dev2-team 오딘 "
      "(key fedf78d1d09509f5) 1회 한정 — additive only")
    a("> 기존 +32~+55 산출물 · frozen anchor · remaining-backlog 원천 "
      "byte-0 read-only consume (수정 0)")
    a(f"> round_disposition: **CLOSED_ACCEPTED** · hold_for_chair: "
      f"**{str(marker['hold_for_chair']).lower()}**")
    a("")
    a("## 1. CLOSED_ACCEPTED scope (+32~+55)")
    a("")
    for s in marker["scope_closed_accepted"]:
        a(f"- **{s['ref']}** — {s['item']} · `{s['status']}`")
    a("")
    a("## 2. 잔여 backlog (과장 0 — 정직 보존)")
    a("")
    a("- **NON_BLOCKING** — `LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE`: "
      "normal callback 수렴 후에도 bound dead-man fallback 발화 → NO-ACTION/"
      "DUPLICATE. legacy safety-net 잔여(실패 아님). 해소=cancel-on-success "
      "live-remove adoption(+37/+45 후속 별도 task).")
    a("- **LOW** — `DEAD_CODE_CLEANLINESS_CANDIDATE`: callback write-back "
      "idempotency / guard 모듈 dead-code 청결성 후보. 긴급 아님 · 후순위.")
    a("- 원천 무수정 통합본: "
      "`memory/events/task-2553.remaining-backlog.final_260518.json`")
    a("")
    a("## 3. self-chain QUARANTINED vs independent ANU authoritative")
    a("")
    a("- self-chain(executor self key / executor==collector / self-*) → "
      "+49 코드 강제 **QUARANTINED · 영구 비권위** (단독 PASS 확정 금지).")
    a(f"- independent ANU(owner key `{ANU_CALLBACK_OWNER_KEY}` · role=ANU · "
      "executor≠collector) verdict 만 authoritative · fail-closed precedence.")
    a("- +44_46/+47/+48/+49/+50~+52/+54/+55 전부 독립 ANU collector "
      "authoritative (executor draft frozen evidence 보존·supersede).")
    a("")
    a("## 4. 불변식")
    a("")
    a(f"- git HEAD `{inv['git_head']}` (expected match: "
      f"{inv['git_head_matches_expected']}) · branch `{inv['git_branch']}` "
      f"(match: {inv['git_branch_matches_expected']})")
    a(f"- frozen anchor 6모듈 byte-0 all EQUAL: "
      f"**{inv['frozen_byte0_all_equal']}**")
    a(f"- remaining-backlog 3 원천 byte-0 pinned EQUAL: "
      f"**{inv['consumed_byte0']['pinned_all_equal']}**")
    a("- additive only · 기존 산출물 무수정 · merge/PR/branch/dispatch/cron 0 "
      "· §4 allowlist 한정 · Track A/B/C DISJOINT")
    a("")
    a("## 5. closeout 일관성 점검")
    a("")
    for k, v in consistency["checks"].items():
        a(f"- `{k}` → **{bool(v)}**")
    a(f"- **all_consistent: {consistency['all_consistent']}**")
    a("")
    a("## 6. durable success registry (+44 ledger, read-only)")
    a("")
    a(f"- durable_success_writeback.v1 tasks: "
      f"`{', '.join(reg['durable_success_tasks']) or 'none'}`")
    a("")
    a("## 7. callback (a) — 독립 ANU key only")
    a("")
    a(f"- normal completion callback = 독립 ANU key "
      f"`{ANU_CALLBACK_OWNER_KEY}`(chat 6937032012) **only**.")
    a(f"- executor self key `{EXECUTOR_SELF_KEY}` 발사 절대 금지 "
      "(+49 코드 강제 정본). ANU fallback=안전망 한정·진행 트리거 아님.")
    a("")
    return "\n".join(L) + "\n"


def render_track_report(marker: dict, result: dict, hold: dict) -> str:
    L = []
    a = L.append
    a("# task-2553+59 — TRACK D: TASK_2553_FINAL_CLOSED_ACCEPTED_MARKER")
    a("")
    a("> executor: dev2-team 오딘 (key fedf78d1d09509f5) 1회 한정 · "
      "additive only")
    a(f"> final_status: **{result['final_status']}** · hold_for_chair: "
      f"**{str(result['hold_for_chair']).lower()}** · {hold['verdict']}")
    a("")
    a("## 산출 (§2)")
    a("")
    a("- `memory/events/task-2553.closed-accepted.json` "
      "(CLOSED_ACCEPTED marker)")
    a("- `memory/reports/task-2553.final-operational-summary.md`")
    a("- `memory/events/task-2553.remaining-backlog.final_260518.json` "
      "(원천 무수정 별도 통합본)")
    a("- `scripts/verify_task2553_closed_accepted_2553plus59.py` "
      "(실 entrypoint · closeout 일관성 · byte-0 정적 점검)")
    a("- `tests/regression/test_task2553_closed_accepted_2553plus59.py` "
      "(mock-only FAIL)")
    a("- `memory/events/task-2553+59.{decision,result}.json` · "
      "`memory/reports/task-2553+59.md`")
    a("")
    a("## 정리 (§3)")
    a("")
    a("- **CLOSED_ACCEPTED**: +32 callback restore / +37·+45 "
      "cancel-on-success(wiring·mock, live-remove backlog) / +38~+41 "
      "4트랙 / +44·+46 durable registry·resolver / +47 write-back·"
      "event-trigger / +48 properly-bound e2e / +49 self-collector "
      "코드 차단(독립 ANU 정본) / +50~+52 3트랙 / +53 batch-settle "
      "write-back / +54 runtime event loop / +55 bounded enactor.")
    a("- **잔여 backlog**: LEGACY_PENDING_FALLBACK_FIRED_AFTER_CONVERGENCE "
      "(NON_BLOCKING) + dead-code 청결성 LOW.")
    a("- self-chain **QUARANTINED** · independent ANU **authoritative** "
      "원칙 명시 · pending 완료 과장 **0**.")
    a("")
    a("## 권한 경계 / executor self 0 (§5)")
    a("")
    a("- 기존 task-2553 산출물·remaining-backlog 원천·+32~+55·frozen "
      "anchor byte-0 read-only consume (수정 0).")
    a("- merge/PR/branch/main write/credential/dispatch/cron 0. "
      "executor 자가심사·자가Codex·자가collector·신규 dispatch·"
      "delegation 0.")
    a(f"- 완료 직후 normal completion callback = 독립 ANU key "
      f"`{ANU_CALLBACK_OWNER_KEY}`(chat 6937032012)로만 발사 — executor "
      f"self key `{EXECUTOR_SELF_KEY}` 발사 0 (+49 코드 강제 정본).")
    a("")
    a("## §8 9-R")
    a("")
    a("- Codex lint GO_READY (HIGH/CRITICAL 0) · expected_files 4-track "
      "DISJOINT · shared invariant 보존 · read-only/additive · 문서-only "
      "금지(실 entrypoint+regression mock-only FAIL) · re-lint 불요.")
    a("")
    return "\n".join(L) + "\n"


# ───────────────────── write surface (allowlist 한정) ─────────────────────
def write_outputs(out_root: str, produced: dict) -> list:
    written = []
    for rel, payload in produced.items():
        if rel.startswith("_"):
            continue
        _assert_allowlisted(rel)
        ap = os.path.join(out_root, rel)
        os.makedirs(os.path.dirname(ap), exist_ok=True)
        if rel.endswith(".md"):
            with open(ap, "w", encoding="utf-8") as fh:
                fh.write(payload)
        else:
            with open(ap, "w", encoding="utf-8") as fh:
                json.dump(payload, fh, ensure_ascii=False, indent=2)
                fh.write("\n")
        written.append(rel)
    return sorted(written)


def collect(root: str) -> dict:
    """실 entrypoint 핵심 — read-only 종합 + 산출물 빌드(write 0)."""
    return build_artifacts(root)


def main(argv=None) -> int:
    p = argparse.ArgumentParser(
        description="task-2553 FINAL CLOSED_ACCEPTED marker (TRACK D, additive)")
    p.add_argument("--root", default=CANONICAL_ROOT,
                   help="canonical workspace root (read source)")
    p.add_argument("--out-root", default=None,
                   help="output root (default = --root)")
    p.add_argument("--dry-run", action="store_true",
                   help="검증/종합만, write 0 (regression)")
    args = p.parse_args(argv)
    root = args.root
    out_root = args.out_root or root
    produced = collect(root)
    meta = produced.get("_meta", {})
    if args.dry_run:
        print(json.dumps({
            "dry_run": True,
            "hold_for_chair": meta.get("hold", {}).get("hold_for_chair"),
            "frozen_byte0_all_equal":
                meta.get("invariants", {}).get("frozen_byte0_all_equal"),
            "closeout_all_consistent":
                meta.get("consistency", {}).get("all_consistent"),
            "scope_count": meta.get("scope_count"),
        }, ensure_ascii=False, indent=2))
        return 0
    written = write_outputs(out_root, produced)
    print(json.dumps({
        "status": "ok",
        "written": written,
        "hold_for_chair": meta.get("hold", {}).get("hold_for_chair"),
        "frozen_byte0_all_equal":
            meta.get("invariants", {}).get("frozen_byte0_all_equal"),
        "closeout_all_consistent":
            meta.get("consistency", {}).get("all_consistent"),
    }, ensure_ascii=False, indent=2))
    return 0


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