#!/usr/bin/env python3
"""Validate local Telegram capture structure without authenticating live delivery."""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import struct
import zlib
from datetime import datetime, timezone
from itertools import pairwise
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

try:
    import build_telegram_visible_unseen_chain_packet as packet_builder
    import collect_telegram_visible_unseen_chain_state as state_collector
    import verify_leo_unseen_reasoning_chain as unseen
except ImportError:  # pragma: no cover - imported as scripts.* in tests
    from scripts import build_telegram_visible_unseen_chain_packet as packet_builder
    from scripts import collect_telegram_visible_unseen_chain_state as state_collector
    from scripts import verify_leo_unseen_reasoning_chain as unseen

SCHEMA = "livingip.telegramVisibleUnseenChainReceipt.v3"
CAPTURE_SCHEMA = "livingip.telegramVisibleUnseenChainCapture.v1"
BROWSER_PROVENANCE_SCHEMA = "livingip.authenticatedChromeTelegramCaptureProvenance.v1"
SHA64_RE = re.compile(r"^[0-9a-f]{64}$")
MIN_SCREENSHOT_WIDTH = 320
MIN_SCREENSHOT_HEIGHT = 200
PREPARATION_STATUS = "prepared_external_receipt_required"
PREPARATION_TIER = "T0_local_preparation"
FAILED_PREPARATION_TIER = "T0_failed_preparation"
DEFAULT_CODEX_SESSIONS_ROOT = Path.home() / ".codex" / "sessions"
REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
DEFAULT_PACKET = packet_builder.DEFAULT_OUT
DEFAULT_PREFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-preflight-current.json"
DEFAULT_POSTFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-postflight-current.json"
DEFAULT_OUT = REPORT_DIR / "telegram-visible-unseen-chain-capture-receipt-current.json"
DEFAULT_MARKDOWN_OUT = REPORT_DIR / "telegram-visible-unseen-chain-capture-receipt-current.md"


def _load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def _sha256_file(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def _present(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, str):
        return bool(value.strip())
    if isinstance(value, (dict, list)):
        return bool(value)
    return True


def _preparation_only_fields(*, preparation_ready: bool) -> dict[str, Any]:
    return {
        "verification_scope": "caller_writable_preparation_structure",
        "preparation_ready": preparation_ready,
        "receipt_ready": False,
        "current_tier": PREPARATION_TIER if preparation_ready else FAILED_PREPARATION_TIER,
        "required_tier": "T3_live_readonly",
        "telegram_visible_messages_sent": False,
        "live_receipt_boundary": {
            "required": True,
            "verified": False,
            "accepted_by_this_api": False,
            "authority": "independently_authenticated_platform_browser_receipt",
            "local_inputs_are": "caller_writable_preparation_evidence",
        },
    }


def blank_capture_template(packet: dict[str, Any], preflight: dict[str, Any]) -> dict[str, Any]:
    target = packet.get("target") or {}
    return {
        "schema": CAPTURE_SCHEMA,
        "protocol_sha256": packet.get("protocol_sha256"),
        "preflight_state_token_sha256": preflight.get("state_token_sha256"),
        "operator_authorization_text": packet.get("explicit_operator_authorization_text"),
        "operator_authorization_captured_at_utc": "",
        "extra_messages_sent": 0,
        "capture_source": {
            "platform": "telegram",
            "transport": "authenticated_chrome_telegram_ui",
            "chat_label": target.get("chat_label"),
            "chat_id": target.get("chat_id"),
            "captured_at_utc": "",
            "captured_by": "",
            "final_screenshot": "",
            "final_accessibility": "",
            "browser_provenance": "",
            "final_capture_reuses_turn_3": False,
            "final_capture_reuse_reason": "",
        },
        "messages": [
            {
                "sequence": item["sequence"],
                "prompt_id": item["prompt_id"],
                "sent_text": item["message"],
                "reply": "",
                "sent_at_utc": "",
                "reply_at_utc": "",
                "telegram_message_id": "",
                "reply_message_id": "",
                "turn_screenshot": "",
                "turn_accessibility": "",
            }
            for item in packet.get("exact_messages", [])
        ],
    }


def _resolve_evidence(path_value: str, evidence_root: Path) -> dict[str, Any]:
    root = evidence_root.resolve()
    if not path_value.strip():
        return {
            "path": path_value,
            "exists": False,
            "contained_in_evidence_root": False,
            "bytes": 0,
            "sha256": "",
        }
    path = Path(path_value)
    if not path.is_absolute():
        path = root / path
    resolved = path.resolve()
    contained = resolved == root or root in resolved.parents
    exists = contained and resolved.is_file()
    return {
        "path": str(resolved),
        "exists": exists,
        "contained_in_evidence_root": contained,
        "bytes": resolved.stat().st_size if exists else 0,
        "sha256": _sha256_file(resolved) if exists else "",
    }


def _inspect_png(evidence: dict[str, Any]) -> dict[str, Any]:
    metadata: dict[str, Any] = {
        "valid_png": False,
        "width": 0,
        "height": 0,
        "bit_depth": 0,
        "color_type": -1,
        "crc_valid": False,
        "pixel_data_valid": False,
    }
    if not evidence.get("exists"):
        return metadata
    data = Path(str(evidence["path"])).read_bytes()
    if not data.startswith(b"\x89PNG\r\n\x1a\n"):
        return metadata
    offset = 8
    chunks: list[tuple[bytes, bytes]] = []
    crc_valid = True
    while offset + 12 <= len(data):
        length = struct.unpack(">I", data[offset : offset + 4])[0]
        chunk_end = offset + 12 + length
        if chunk_end > len(data):
            return metadata
        chunk_type = data[offset + 4 : offset + 8]
        payload = data[offset + 8 : offset + 8 + length]
        expected_crc = struct.unpack(">I", data[offset + 8 + length : chunk_end])[0]
        crc_valid = crc_valid and (zlib.crc32(chunk_type + payload) & 0xFFFFFFFF) == expected_crc
        chunks.append((chunk_type, payload))
        offset = chunk_end
        if chunk_type == b"IEND":
            break
    if not chunks or chunks[0][0] != b"IHDR" or len(chunks[0][1]) != 13:
        return metadata
    if chunks[-1][0] != b"IEND" or offset != len(data):
        return metadata
    width, height, bit_depth, color_type, compression, filtering, interlace = struct.unpack(">IIBBBBB", chunks[0][1])
    idat = b"".join(payload for chunk_type, payload in chunks if chunk_type == b"IDAT")
    channels = {2: 3, 6: 4}.get(color_type)
    pixel_data_valid = False
    if idat and channels and bit_depth == 8 and compression == 0 and filtering == 0 and interlace == 0:
        try:
            pixels = zlib.decompress(idat)
        except zlib.error:
            pixels = b""
        expected_size = height * (1 + width * channels)
        pixel_data_valid = len(pixels) == expected_size
    metadata.update(
        {
            "width": width,
            "height": height,
            "bit_depth": bit_depth,
            "color_type": color_type,
            "crc_valid": crc_valid,
            "pixel_data_valid": pixel_data_valid,
            "valid_png": crc_valid
            and pixel_data_valid
            and width >= MIN_SCREENSHOT_WIDTH
            and height >= MIN_SCREENSHOT_HEIGHT,
        }
    )
    return metadata


def _parse_aware_timestamp(value: Any) -> datetime | None:
    if not isinstance(value, str) or not value.strip():
        return None
    candidate = value.strip()
    if candidate.endswith("Z"):
        candidate = candidate[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(candidate)
    except ValueError:
        return None
    if parsed.tzinfo is None or parsed.utcoffset() is None:
        return None
    return parsed


def _all_true_section(value: Any, *, allow_empty: bool = False) -> bool:
    if not isinstance(value, dict):
        return False
    return (allow_empty or bool(value)) and all(item is True for item in value.values())


def _state_artifact_checks(
    artifact: dict[str, Any],
    *,
    phase: str,
    packet: dict[str, Any],
    packet_path: Path,
) -> dict[str, bool]:
    baseline_checks_required = phase == "postflight"
    return {
        f"{phase}_schema_supported": artifact.get("schema") == state_collector.SCHEMA,
        f"{phase}_status_and_phase_pass": artifact.get("status") == "pass" and artifact.get("phase") == phase,
        f"{phase}_packet_checks_all_pass": _all_true_section(artifact.get("packet_checks")),
        f"{phase}_remote_checks_all_pass": _all_true_section(artifact.get("remote_checks")),
        f"{phase}_baseline_checks_all_pass": _all_true_section(
            artifact.get("baseline_checks"), allow_empty=not baseline_checks_required
        ),
        f"{phase}_state_token_derivation_valid": state_collector.state_token_is_valid(artifact),
        f"{phase}_packet_file_hash_bound": artifact.get("packet_file_sha256") == _sha256_file(packet_path),
        f"{phase}_protocol_bound": artifact.get("protocol_sha256") == packet.get("protocol_sha256"),
        f"{phase}_manifest_bound": artifact.get("manifest_binding") == packet.get("manifest_binding"),
    }


def _capture_messages(capture: dict[str, Any]) -> list[dict[str, Any]]:
    messages = capture.get("messages")
    return [item for item in messages if isinstance(item, dict)] if isinstance(messages, list) else []


def _normalized_text(value: str) -> str:
    return re.sub(r"\s+", " ", value).strip().casefold()


def _accessibility_binds_message(evidence: dict[str, Any], *, sent_text: str, reply: str) -> bool:
    if not evidence.get("exists") or not evidence.get("path"):
        return False
    text = Path(str(evidence["path"])).read_text(encoding="utf-8", errors="replace")
    normalized = _normalized_text(text)
    return _normalized_text(sent_text) in normalized and _normalized_text(reply) in normalized


def _artifact_hash_manifest(
    *,
    final_screenshot: dict[str, Any],
    final_accessibility: dict[str, Any],
    turn_evidence: list[dict[str, Any]],
) -> dict[str, Any]:
    return {
        "final_screenshot": final_screenshot.get("sha256"),
        "final_accessibility": final_accessibility.get("sha256"),
        "turn_screenshots": [item["screenshot"].get("sha256") for item in turn_evidence],
        "turn_accessibility": [item["accessibility"].get("sha256") for item in turn_evidence],
    }


def _browser_provenance_checks(
    evidence: dict[str, Any],
    *,
    packet: dict[str, Any],
    preflight: dict[str, Any],
    capture: dict[str, Any],
    capture_source: dict[str, Any],
    messages: list[dict[str, Any]],
    artifact_hashes: dict[str, Any],
) -> tuple[dict[str, bool], dict[str, Any]]:
    provenance: dict[str, Any] = {}
    if evidence.get("exists"):
        try:
            value = _load_json(Path(str(evidence["path"])))
        except (OSError, UnicodeDecodeError, json.JSONDecodeError):
            value = {}
        if isinstance(value, dict):
            provenance = value
    browser = provenance.get("browser") if isinstance(provenance.get("browser"), dict) else {}
    authorization = provenance.get("authorization") if isinstance(provenance.get("authorization"), dict) else {}
    telegram = provenance.get("telegram") if isinstance(provenance.get("telegram"), dict) else {}
    provider_ids = [
        str(browser.get(key) or "") for key in ("browser_id", "session_id", "tab_id", "provider_capture_id")
    ]
    packet_serialized = json.dumps(packet, sort_keys=True)
    provenance_serialized = json.dumps(provenance, sort_keys=True)
    capture_serialized = json.dumps(capture, sort_keys=True)
    target = packet.get("target") if isinstance(packet.get("target"), dict) else {}
    page_url = str(browser.get("page_url") or "")
    parsed_url = urlparse(page_url)
    expected_message_ids = [str(item.get("telegram_message_id") or "") for item in messages]
    expected_reply_ids = [str(item.get("reply_message_id") or "") for item in messages]
    authorization_text = str(capture.get("operator_authorization_text") or "")
    checks = {
        "browser_provenance_file_present_and_contained": evidence.get("exists") is True
        and evidence.get("contained_in_evidence_root") is True,
        "browser_provenance_schema_supported": provenance.get("schema") == BROWSER_PROVENANCE_SCHEMA,
        "browser_provenance_status_and_checks_pass": provenance.get("status") == "pass"
        and _all_true_section(provenance.get("checks")),
        "browser_provenance_channel_is_chrome_extension": provenance.get("capture_channel") == "codex_chrome_extension"
        and browser.get("browser_type") == "extension",
        "browser_provenance_sha256_not_self_declared": bool(evidence.get("sha256"))
        and evidence.get("sha256") not in capture_serialized
        and evidence.get("sha256") not in provenance_serialized,
        "browser_provenance_challenge_and_protocol_bound": provenance.get("capture_challenge_nonce")
        == preflight.get("capture_challenge_nonce")
        and provenance.get("protocol_sha256") == packet.get("protocol_sha256"),
        "browser_provider_identifiers_present_and_not_from_packet": all(
            len(value) >= 8 and value not in packet_serialized for value in provider_ids
        )
        and len(set(provider_ids)) == len(provider_ids),
        "browser_observed_telegram_web_origin": parsed_url.scheme == "https"
        and parsed_url.hostname == "web.telegram.org"
        and str(target.get("chat_id") or "") in page_url,
        "browser_observed_exact_chat_identity": telegram.get("chat_id") == target.get("chat_id")
        and telegram.get("chat_label") == target.get("chat_label")
        and str(target.get("chat_label") or "").casefold() in str(browser.get("page_title") or "").casefold(),
        "browser_observed_exact_message_and_reply_ids": telegram.get("message_ids") == expected_message_ids
        and telegram.get("reply_ids") == expected_reply_ids,
        "browser_provenance_artifact_hashes_exact": provenance.get("artifacts") == artifact_hashes,
        "browser_provenance_capture_timestamp_bound": browser.get("captured_at_utc")
        == capture_source.get("captured_at_utc"),
        "authorization_provenance_claim_has_codex_user_message_shape": authorization.get("source")
        == "codex_platform_user_message"
        and all(
            len(str(authorization.get(key) or "")) >= 8 for key in ("thread_id", "user_message_id", "source_receipt_id")
        ),
        "authorization_provenance_hash_and_timestamp_bound": authorization.get("text_sha256")
        == hashlib.sha256(authorization_text.encode("utf-8")).hexdigest()
        and authorization.get("captured_at_utc") == capture.get("operator_authorization_captured_at_utc"),
        "authorization_text_not_copied_into_provenance": bool(authorization_text)
        and authorization_text not in provenance_serialized,
    }
    return checks, provenance


def _local_codex_rollout_structure_checks(
    rollout_path: Path | None,
    *,
    sessions_root: Path,
    evidence_root: Path,
    packet: dict[str, Any],
    capture: dict[str, Any],
    provenance: dict[str, Any],
    provenance_evidence: dict[str, Any],
    artifact_hashes: dict[str, Any],
    messages: list[dict[str, Any]],
) -> tuple[dict[str, bool], dict[str, Any]]:
    # Codex session JSONL is same-user writable. These checks establish internal
    # consistency for external review, never an independently authenticated receipt.
    root = sessions_root.resolve()
    resolved = rollout_path.resolve() if rollout_path is not None else Path()
    contained = bool(rollout_path) and resolved.is_file() and root in resolved.parents
    outside_evidence_root = bool(rollout_path) and evidence_root.resolve() not in resolved.parents
    events: list[dict[str, Any]] = []
    jsonl_valid = contained
    if contained:
        try:
            with resolved.open(encoding="utf-8") as handle:
                for line in handle:
                    if not line.strip():
                        continue
                    value = json.loads(line)
                    if not isinstance(value, dict):
                        jsonl_valid = False
                        continue
                    events.append(value)
        except (OSError, UnicodeDecodeError, json.JSONDecodeError):
            jsonl_valid = False
            events = []

    operator_context = packet.get("operator_context") if isinstance(packet.get("operator_context"), dict) else {}
    authorization = provenance.get("authorization") if isinstance(provenance.get("authorization"), dict) else {}
    browser = provenance.get("browser") if isinstance(provenance.get("browser"), dict) else {}
    thread_id = str(operator_context.get("codex_thread_id") or "")
    authorization_text = str(capture.get("operator_authorization_text") or "")
    user_message_id = str(authorization.get("user_message_id") or "")
    matching_user_events: list[dict[str, Any]] = []
    browser_events: dict[str, dict[str, Any]] = {}
    for event in events:
        payload = event.get("payload") if isinstance(event.get("payload"), dict) else {}
        if event.get("type") == "response_item" and payload.get("type") == "message" and payload.get("role") == "user":
            content = payload.get("content") if isinstance(payload.get("content"), list) else []
            text = "".join(
                str(item.get("text") or "")
                for item in content
                if isinstance(item, dict) and item.get("type") == "input_text"
            )
            metadata = (
                payload.get("internal_chat_message_metadata_passthrough")
                if isinstance(payload.get("internal_chat_message_metadata_passthrough"), dict)
                else {}
            )
            if text == authorization_text and metadata.get("turn_id") == user_message_id:
                matching_user_events.append(event)
        if event.get("type") == "event_msg" and payload.get("type") == "mcp_tool_call_end":
            call_id = str(payload.get("call_id") or "")
            if call_id:
                browser_events[call_id] = event

    user_event = matching_user_events[0] if len(matching_user_events) == 1 else {}
    session_meta_ids = [
        str((event.get("payload") or {}).get("id") or "")
        for event in events
        if event.get("type") == "session_meta" and isinstance(event.get("payload"), dict)
    ]
    user_event_sha256 = (
        hashlib.sha256(json.dumps(user_event, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest()
        if user_event
        else ""
    )
    turn_call_ids = provenance.get("turn_capture_call_ids")
    turn_call_ids = [str(value) for value in turn_call_ids] if isinstance(turn_call_ids, list) else []
    final_call_id = str(provenance.get("final_capture_call_id") or "")
    reuse_final = (capture.get("capture_source") or {}).get("final_capture_reuses_turn_3") is True
    call_id_shape_valid = (
        len(turn_call_ids) == 3
        and all(len(value) >= 8 for value in turn_call_ids)
        and len(set(turn_call_ids)) == 3
        and len(final_call_id) >= 8
        and (
            (reuse_final and final_call_id == turn_call_ids[2])
            or (not reuse_final and final_call_id not in turn_call_ids)
        )
    )
    expected_call_ids = set(turn_call_ids + ([final_call_id] if final_call_id else []))
    selected_events = {call_id: browser_events.get(call_id, {}) for call_id in expected_call_ids}

    def browser_event_parts(event: dict[str, Any]) -> tuple[dict[str, Any], str, datetime | None]:
        payload = event.get("payload") if isinstance(event.get("payload"), dict) else {}
        result = payload.get("result") if isinstance(payload.get("result"), dict) else {}
        ok = result.get("Ok") if isinstance(result.get("Ok"), dict) else {}
        meta = ok.get("_meta") if isinstance(ok.get("_meta"), dict) else {}
        surface = meta.get("codex/toolSurface") if isinstance(meta.get("codex/toolSurface"), dict) else {}
        content = ok.get("content") if isinstance(ok.get("content"), list) else []
        result_text = "\n".join(
            str(item.get("text") or "") for item in content if isinstance(item, dict) and item.get("type") == "text"
        )
        return surface, result_text, _parse_aware_timestamp(event.get("timestamp"))

    event_parts = {call_id: browser_event_parts(event) for call_id, event in selected_events.items()}
    browser_id = str(browser.get("browser_id") or "")
    tab_id = str(browser.get("tab_id") or "")
    all_events_are_chrome = bool(expected_call_ids) and all(
        event
        and parts[0].get("backend") == "chrome"
        and parts[0].get("kind") == "browserUse"
        and parts[0].get("browserId") == browser_id
        and tab_id in (parts[0].get("openTabIds") or [])
        for call_id, event in selected_events.items()
        for parts in [event_parts[call_id]]
    )
    all_events_invoked_capture_apis = (
        bool(expected_call_ids)
        and all(
            event
            and "screenshot("
            in str((((event.get("payload") or {}).get("invocation") or {}).get("arguments") or {}).get("code") or "")
            and "domSnapshot("
            in str((((event.get("payload") or {}).get("invocation") or {}).get("arguments") or {}).get("code") or "")
            and ".url("
            in str((((event.get("payload") or {}).get("invocation") or {}).get("arguments") or {}).get("code") or "")
            and ".title("
            in str((((event.get("payload") or {}).get("invocation") or {}).get("arguments") or {}).get("code") or "")
            for event in selected_events.values()
        )
        and len(selected_events) == len(expected_call_ids)
    )
    turn_receipts_bound = len(turn_call_ids) == len(messages) == 3 and all(
        call_id in event_parts
        and all(
            value in event_parts[call_id][1]
            for value in (
                artifact_hashes["turn_screenshots"][index],
                artifact_hashes["turn_accessibility"][index],
                str(message.get("telegram_message_id") or ""),
                str(message.get("reply_message_id") or ""),
                str(browser.get("page_url") or ""),
                str(browser.get("page_title") or ""),
                str((packet.get("target") or {}).get("chat_id") or ""),
            )
        )
        for index, (call_id, message) in enumerate(zip(turn_call_ids, messages, strict=True))
    )
    final_receipt_text = event_parts.get(final_call_id, ({}, "", None))[1]
    final_receipt_bound = bool(final_receipt_text) and all(
        value in final_receipt_text
        for value in (
            str(artifact_hashes.get("final_screenshot") or ""),
            str(artifact_hashes.get("final_accessibility") or ""),
            str(provenance_evidence.get("sha256") or ""),
            *(str(item.get("telegram_message_id") or "") for item in messages),
            *(str(item.get("reply_message_id") or "") for item in messages),
        )
    )
    event_times = [event_parts.get(call_id, ({}, "", None))[2] for call_id in turn_call_ids]
    final_event_at = event_parts.get(final_call_id, ({}, "", None))[2]
    send_times = [_parse_aware_timestamp(item.get("sent_at_utc")) for item in messages]
    reply_times = [_parse_aware_timestamp(item.get("reply_at_utc")) for item in messages]
    browser_event_order_valid = all(
        value is not None for value in (*event_times, final_event_at, *send_times, *reply_times)
    )
    if browser_event_order_valid:
        browser_event_order_valid = bool(
            reply_times[0] < event_times[0] < send_times[1]
            and reply_times[1] < event_times[1] < send_times[2]
            and reply_times[2] < event_times[2]
            and (
                (reuse_final and final_event_at == event_times[2])
                or (not reuse_final and event_times[2] < final_event_at)
            )
        )
    authorization_event_at = _parse_aware_timestamp(user_event.get("timestamp")) if user_event else None
    capture_source = capture.get("capture_source") if isinstance(capture.get("capture_source"), dict) else {}
    checks = {
        "codex_rollout_log_has_expected_local_sessions_shape": contained and thread_id in resolved.name,
        "codex_rollout_log_is_separate_from_evidence_root": outside_evidence_root,
        "codex_rollout_log_jsonl_valid": jsonl_valid and bool(events),
        "codex_rollout_session_meta_matches_thread": session_meta_ids == [thread_id],
        "codex_rollout_has_exact_single_authorization_event": len(matching_user_events) == 1,
        "codex_authorization_event_bound_to_provenance": authorization.get("thread_id") == thread_id
        and authorization.get("source_receipt_id") == user_event_sha256
        and user_event.get("timestamp") == capture.get("operator_authorization_captured_at_utc"),
        "codex_rollout_capture_call_ids_valid": call_id_shape_valid,
        "codex_rollout_capture_events_are_chrome_backend": all_events_are_chrome,
        "codex_rollout_capture_events_invoked_screenshot_and_dom_snapshot": all_events_invoked_capture_apis,
        "codex_rollout_turn_receipts_bind_artifacts_and_telegram_ids": turn_receipts_bound,
        "codex_rollout_final_receipt_binds_provenance_artifacts_and_all_ids": final_receipt_bound,
        "codex_rollout_capture_event_order_valid": browser_event_order_valid,
        "codex_rollout_final_event_timestamp_bound": final_event_at is not None
        and capture_source.get("captured_at_utc") == (selected_events.get(final_call_id) or {}).get("timestamp")
        and browser.get("captured_at_utc") == (selected_events.get(final_call_id) or {}).get("timestamp"),
        "codex_rollout_authorization_timestamp_is_aware": authorization_event_at is not None,
    }
    evidence = {
        "path": str(resolved) if rollout_path else "",
        "sha256": _sha256_file(resolved) if contained else "",
        "thread_id": thread_id,
        "authorization_turn_id": user_message_id,
        "turn_capture_call_ids": turn_call_ids,
        "final_capture_call_id": final_call_id,
        "turn_capture_timestamps": [(selected_events.get(call_id) or {}).get("timestamp") for call_id in turn_call_ids],
        "final_capture_timestamp": (selected_events.get(final_call_id) or {}).get("timestamp"),
    }
    return checks, evidence


def _timeline_checks(
    preflight: dict[str, Any],
    capture: dict[str, Any],
    postflight: dict[str, Any],
    provenance: dict[str, Any],
    messages: list[dict[str, Any]],
) -> tuple[dict[str, bool], dict[str, Any]]:
    capture_source = capture.get("capture_source") if isinstance(capture.get("capture_source"), dict) else {}
    browser = provenance.get("browser") if isinstance(provenance.get("browser"), dict) else {}
    authorization = provenance.get("authorization") if isinstance(provenance.get("authorization"), dict) else {}
    preflight_at = _parse_aware_timestamp(preflight.get("generated_at_utc"))
    authorization_at = _parse_aware_timestamp(capture.get("operator_authorization_captured_at_utc"))
    send_times = [_parse_aware_timestamp(item.get("sent_at_utc")) for item in messages]
    reply_times = [_parse_aware_timestamp(item.get("reply_at_utc")) for item in messages]
    final_capture_at = _parse_aware_timestamp(capture_source.get("captured_at_utc"))
    postflight_at = _parse_aware_timestamp(postflight.get("generated_at_utc"))
    browser_capture_at = _parse_aware_timestamp(browser.get("captured_at_utc"))
    authorization_provenance_at = _parse_aware_timestamp(authorization.get("captured_at_utc"))
    timestamps = [
        preflight_at,
        authorization_at,
        *send_times,
        *reply_times,
        final_capture_at,
        postflight_at,
        browser_capture_at,
        authorization_provenance_at,
    ]
    valid = len(messages) == 3 and all(value is not None for value in timestamps)
    strict_turn_order = False
    preflight_before_authorization = False
    authorization_before_first_send = False
    final_after_reply3 = False
    postflight_after_reply3 = False
    if valid:
        assert preflight_at is not None
        assert authorization_at is not None
        assert final_capture_at is not None
        assert postflight_at is not None
        ordered_turns = [
            send_times[0],
            reply_times[0],
            send_times[1],
            reply_times[1],
            send_times[2],
            reply_times[2],
        ]
        strict_turn_order = all(left < right for left, right in pairwise(ordered_turns))
        preflight_before_authorization = preflight_at < authorization_at
        authorization_before_first_send = authorization_at < send_times[0]
        final_after_reply3 = reply_times[2] < final_capture_at
        postflight_after_reply3 = reply_times[2] < postflight_at
    checks = {
        "timestamps_valid_and_timezone_aware": valid,
        "preflight_before_captured_authorization": preflight_before_authorization,
        "captured_authorization_before_first_send": authorization_before_first_send,
        "strict_send_reply_turn_order": strict_turn_order,
        "final_capture_after_third_reply": final_after_reply3,
        "postflight_after_third_reply": postflight_after_reply3,
    }
    timeline = {
        "preflight": preflight.get("generated_at_utc"),
        "authorization": capture.get("operator_authorization_captured_at_utc"),
        "turns": [
            {"sent_at_utc": item.get("sent_at_utc"), "reply_at_utc": item.get("reply_at_utc")} for item in messages
        ],
        "final_capture": capture_source.get("captured_at_utc"),
        "postflight": postflight.get("generated_at_utc"),
    }
    return checks, timeline


def _subject_match(postflight: dict[str, Any]) -> dict[str, Any]:
    subject = (postflight.get("remote") or {}).get("subject_readback") or {}
    matches = subject.get("matches") if isinstance(subject, dict) else []
    return matches[0] if isinstance(matches, list) and len(matches) == 1 else {}


def _semantic_checks(replies: list[str], *, subject_ref: str, subject_match: dict[str, Any]) -> dict[str, bool]:
    if len(replies) != 3:
        return {
            "visible_db_object_selected_without_operator_id": False,
            "visible_weak_support_explained": False,
            "same_subject_survived_challenge": False,
            "assumption_support_and_falsifier_separated": False,
            "multiple_review_only_candidates_proposed": False,
            "objection_revised_candidate": False,
            "resolving_source_named": False,
            "review_approval_apply_boundary_explicit": False,
            "reply_claimed_no_mutation": False,
            "independent_db_object_matches_visible_subject": False,
        }
    first, second, third = replies
    first_refs = unseen._short_refs(first)
    second_refs = unseen._short_refs(second)
    shared_terms = unseen._shared_subject_terms(first, second)
    selected = subject_ref[:8].casefold() if subject_ref else ""
    visible_subject = bool(selected and (selected in first.casefold() or selected in second.casefold()))
    same_subject = bool(
        visible_subject
        and (
            (selected in first.casefold() and selected in second.casefold())
            or first_refs & second_refs
            or len(shared_terms) >= 2
        )
    )
    row = subject_match.get("row") if isinstance(subject_match.get("row"), dict) else {}
    row_id = str(row.get("id") or "").casefold()
    proposals = unseen._contains(second, "proposal a", "proposal b") or (
        unseen._contains_any(second, "two proposals", "two replacement claims") and "1" in second and "2" in second
    )
    return {
        "visible_db_object_selected_without_operator_id": unseen._contains_any(first, "claim", "belief", "axiom")
        and unseen._contains_any(first, "database", "db", "public.")
        and unseen._contains_any(first, "confidence", "support", "evidence"),
        "visible_weak_support_explained": unseen._contains_any(
            first,
            "nothing supports",
            "zero evidence",
            "no evidence",
            "no claim-evidence",
            "no_source_pointer",
            "no external primary source",
        ),
        "same_subject_survived_challenge": same_subject,
        "assumption_support_and_falsifier_separated": unseen._contains_any(second, "assumption")
        and unseen._contains_any(second, "observed support", "evidence")
        and unseen._contains_any(second, "falsifier", "counterexample", "would falsify"),
        "multiple_review_only_candidates_proposed": proposals
        and unseen._contains_any(second, "review", "staged", "proposal")
        and unseen._contains_any(
            second,
            "nothing applied",
            "not live knowledge",
            "staged only",
            "not an apply",
        ),
        "objection_revised_candidate": unseen._contains_any(third, "revised proposal", "revision")
        and unseen._contains(third, "mechanism", "outcome"),
        "resolving_source_named": unseen._contains_any(
            third, "what would resolve", "source would resolve", "evidence would resolve"
        )
        and unseen._contains_any(
            third,
            "study",
            "dataset",
            "source",
            "randomized",
            "quasi-experimental",
            "replication",
            "ostrom",
        ),
        "review_approval_apply_boundary_explicit": unseen._contains_any(
            third, "review", "pending_review", "human review"
        )
        and unseen._contains_any(third, "approve", "approved", "approval")
        and unseen._contains_any(third, "apply", "applied_at", "canonical"),
        "reply_claimed_no_mutation": unseen._contains_any(
            third,
            "not an apply",
            "nothing staged or changed",
            "canonical unchanged",
            "staged intent, not knowledge",
            "proposal (not an apply)",
        ),
        "independent_db_object_matches_visible_subject": bool(
            visible_subject and row_id and row_id.startswith(selected)
        ),
    }


def verify_capture(
    packet: dict[str, Any],
    preflight: dict[str, Any],
    capture: dict[str, Any] | None,
    postflight: dict[str, Any] | None,
    *,
    packet_path: Path,
    evidence_root: Path,
    codex_rollout_log: Path | None = None,
    codex_sessions_root: Path | None = None,
) -> dict[str, Any]:
    if capture is None:
        packet_integrity = state_collector.packet_checks(packet)
        preflight_integrity = _state_artifact_checks(
            preflight,
            phase="preflight",
            packet=packet,
            packet_path=packet_path,
        )
        ready = all(packet_integrity.values()) and all(preflight_integrity.values())
        return {
            "schema": SCHEMA,
            "generated_at_utc": datetime.now(timezone.utc).isoformat(),
            "mode": "telegram_visible_unseen_chain_preparation_verifier",
            "status": "awaiting_action_time_authorization" if ready else "fail",
            **_preparation_only_fields(preparation_ready=ready),
            "mutates_db": False,
            "packet_path": str(packet_path),
            "preflight_path": str(DEFAULT_PREFLIGHT),
            "postflight_path": str(DEFAULT_POSTFLIGHT),
            "capture_template": blank_capture_template(packet, preflight),
            "checks": {
                **packet_integrity,
                **preflight_integrity,
                "action_time_authorization_present": False,
                "capture_present": False,
                "postflight_present": False,
            },
            "claim_ceiling": {
                "proven": (
                    "The caller-supplied packet and preflight are structurally ready for external review."
                    if ready
                    else "No readiness claim; packet or preflight integrity validation failed."
                ),
                "not_proven": [
                    "independently authenticated platform or browser receipt",
                    "Telegram-visible message delivery",
                    "Telegram-visible Leo replies",
                    "post-send fingerprint equality",
                ],
            },
        }

    messages = _capture_messages(capture)
    expected = packet.get("exact_messages") or []
    capture_source = capture.get("capture_source") if isinstance(capture.get("capture_source"), dict) else {}
    replies = [str(item.get("reply") or "") for item in messages]
    ids = [str(item.get("telegram_message_id") or "") for item in messages]
    reply_ids = [str(item.get("reply_message_id") or "") for item in messages]
    screenshot = _resolve_evidence(str(capture_source.get("final_screenshot") or ""), evidence_root)
    screenshot["png"] = _inspect_png(screenshot)
    accessibility = _resolve_evidence(str(capture_source.get("final_accessibility") or ""), evidence_root)
    turn_evidence = [
        {
            "prompt_id": item.get("prompt_id"),
            "screenshot": _resolve_evidence(str(item.get("turn_screenshot") or ""), evidence_root),
            "accessibility": _resolve_evidence(str(item.get("turn_accessibility") or ""), evidence_root),
        }
        for item in messages
    ]
    for evidence in turn_evidence:
        evidence["screenshot"]["png"] = _inspect_png(evidence["screenshot"])
    browser_provenance_evidence = _resolve_evidence(str(capture_source.get("browser_provenance") or ""), evidence_root)
    artifact_hashes = _artifact_hash_manifest(
        final_screenshot=screenshot,
        final_accessibility=accessibility,
        turn_evidence=turn_evidence,
    )
    postflight = postflight or {}
    subject_ref = str(postflight.get("subject_ref") or "")
    subject_match = _subject_match(postflight)
    semantics = _semantic_checks(replies, subject_ref=subject_ref, subject_match=subject_match)
    pre_fingerprint = (preflight.get("remote") or {}).get("fingerprint_after") or {}
    post_fingerprint = (postflight.get("remote") or {}).get("fingerprint_after") or {}
    pre_service = (preflight.get("remote") or {}).get("service_after") or {}
    post_service = (postflight.get("remote") or {}).get("service_after") or {}
    service_keys = ("MainPID", "NRestarts", "ExecMainStartTimestamp")
    handler_binding = packet.get("handler_receipt_binding") or {}
    handler_path = Path(str(handler_binding.get("path") or ""))
    if not handler_path.is_absolute():
        handler_path = evidence_root / handler_path
    expected_ids = [str(item.get("prompt_id") or "") for item in expected]
    actual_ids = [str(item.get("prompt_id") or "") for item in messages]
    packet_integrity = state_collector.packet_checks(packet)
    preflight_integrity = _state_artifact_checks(
        preflight,
        phase="preflight",
        packet=packet,
        packet_path=packet_path,
    )
    postflight_integrity = _state_artifact_checks(
        postflight,
        phase="postflight",
        packet=packet,
        packet_path=packet_path,
    )
    browser_checks, browser_provenance = _browser_provenance_checks(
        browser_provenance_evidence,
        packet=packet,
        preflight=preflight,
        capture=capture,
        capture_source=capture_source,
        messages=messages,
        artifact_hashes=artifact_hashes,
    )
    codex_rollout_checks, codex_rollout_evidence = _local_codex_rollout_structure_checks(
        codex_rollout_log,
        sessions_root=codex_sessions_root or DEFAULT_CODEX_SESSIONS_ROOT,
        evidence_root=evidence_root,
        packet=packet,
        capture=capture,
        provenance=browser_provenance,
        provenance_evidence=browser_provenance_evidence,
        artifact_hashes=artifact_hashes,
        messages=messages,
    )
    timeline_checks, timeline = _timeline_checks(
        preflight,
        capture,
        postflight,
        browser_provenance,
        messages,
    )
    turn_screenshot_hashes = [item["screenshot"]["sha256"] for item in turn_evidence]
    turn_accessibility_hashes = [item["accessibility"]["sha256"] for item in turn_evidence]
    final_matches_earlier_turn = (
        screenshot.get("sha256") in turn_screenshot_hashes[:2]
        or accessibility.get("sha256") in turn_accessibility_hashes[:2]
    )
    final_matches_turn_three = bool(
        len(turn_evidence) == 3
        and (
            screenshot.get("sha256") == turn_screenshot_hashes[2]
            or accessibility.get("sha256") == turn_accessibility_hashes[2]
        )
    )
    final_reuse_documented = capture_source.get("final_capture_reuses_turn_3") is True and _present(
        capture_source.get("final_capture_reuse_reason")
    )
    final_reuse_policy_satisfied = not final_matches_earlier_turn and (
        (final_matches_turn_three and final_reuse_documented)
        or (
            not final_matches_turn_three
            and capture_source.get("final_capture_reuses_turn_3") is False
            and not _present(capture_source.get("final_capture_reuse_reason"))
        )
    )
    checks = {
        **packet_integrity,
        **preflight_integrity,
        **postflight_integrity,
        **browser_checks,
        **codex_rollout_checks,
        **timeline_checks,
        "capture_schema_supported": capture.get("schema") == CAPTURE_SCHEMA,
        "packet_ready": packet.get("ready_to_request_action_time_authorization") is True,
        "packet_file_hash_matches_preflight": _sha256_file(packet_path) == preflight.get("packet_file_sha256"),
        "handler_receipt_binding_unchanged": handler_path.is_file()
        and _sha256_file(handler_path) == handler_binding.get("sha256"),
        "protocol_bound_across_all_artifacts": capture.get("protocol_sha256")
        == preflight.get("protocol_sha256")
        == postflight.get("protocol_sha256")
        == packet.get("protocol_sha256"),
        "preflight_state_token_bound_to_capture_and_postflight": capture.get("preflight_state_token_sha256")
        == preflight.get("state_token_sha256")
        == postflight.get("baseline_state_token_sha256"),
        "preflight_challenge_bound_to_postflight": bool(
            SHA64_RE.fullmatch(str(preflight.get("capture_challenge_nonce") or ""))
        )
        and preflight.get("capture_challenge_nonce") == postflight.get("capture_challenge_nonce"),
        "exact_action_time_authorization_captured": capture.get("operator_authorization_text")
        == packet.get("explicit_operator_authorization_text")
        and _present(capture.get("operator_authorization_captured_at_utc")),
        "authenticated_chrome_transport_declared": capture_source.get("platform") == "telegram"
        and capture_source.get("transport") == "authenticated_chrome_telegram_ui",
        "bot_api_not_substituted": "bot_api" not in json.dumps(capture_source).casefold(),
        "exact_destination_matches": capture_source.get("chat_label") == (packet.get("target") or {}).get("chat_label")
        and capture_source.get("chat_id") == (packet.get("target") or {}).get("chat_id"),
        "exact_three_messages_and_no_extras": len(messages) == len(expected) == 3
        and capture.get("extra_messages_sent") == 0,
        "prompt_ids_and_sequence_exact": actual_ids == expected_ids
        and [item.get("sequence") for item in messages] == [1, 2, 3],
        "sent_text_exact": [item.get("sent_text") for item in messages] == [item.get("message") for item in expected],
        "visible_replies_nonempty": len(replies) == 3 and all(reply.strip() for reply in replies),
        "message_and_reply_ids_present_unique": all(ids)
        and all(reply_ids)
        and len(set(ids)) == 3
        and len(set(reply_ids)) == 3
        and set(ids).isdisjoint(reply_ids),
        "capture_metadata_present": all(
            _present(capture_source.get(key)) for key in ("captured_at_utc", "captured_by")
        ),
        "all_evidence_paths_contained_in_root": all(
            item.get("contained_in_evidence_root") is True
            for item in (
                screenshot,
                accessibility,
                browser_provenance_evidence,
                *(turn["screenshot"] for turn in turn_evidence),
                *(turn["accessibility"] for turn in turn_evidence),
            )
        ),
        "visual_evidence_files_present": screenshot["exists"]
        and screenshot["bytes"] > 0
        and accessibility["exists"]
        and accessibility["bytes"] > 0
        and len(turn_evidence) == 3
        and all(
            item["screenshot"]["exists"]
            and item["screenshot"]["bytes"] > 0
            and item["accessibility"]["exists"]
            and item["accessibility"]["bytes"] > 0
            for item in turn_evidence
        ),
        "screenshots_are_valid_png_with_metadata": screenshot["png"]["valid_png"] is True
        and len(turn_evidence) == 3
        and all(item["screenshot"]["png"]["valid_png"] is True for item in turn_evidence),
        "per_turn_screenshot_hashes_distinct_nonempty": len(turn_screenshot_hashes) == 3
        and all(SHA64_RE.fullmatch(value) for value in turn_screenshot_hashes)
        and len(set(turn_screenshot_hashes)) == 3,
        "per_turn_accessibility_hashes_distinct_nonempty": len(turn_accessibility_hashes) == 3
        and all(SHA64_RE.fullmatch(value) for value in turn_accessibility_hashes)
        and len(set(turn_accessibility_hashes)) == 3,
        "final_capture_reuse_policy_satisfied": final_reuse_policy_satisfied,
        "turn_accessibility_binds_exact_messages_and_replies": len(turn_evidence) == len(messages) == 3
        and all(
            _accessibility_binds_message(
                evidence["accessibility"],
                sent_text=str(message.get("sent_text") or ""),
                reply=str(message.get("reply") or ""),
            )
            for evidence, message in zip(turn_evidence, messages, strict=True)
        ),
        "canonical_fingerprint_unchanged_from_preflight": bool(
            pre_fingerprint.get("fingerprint_sha256")
            and pre_fingerprint.get("fingerprint_sha256") == post_fingerprint.get("fingerprint_sha256")
        ),
        "gateway_process_unchanged_from_preflight": all(
            pre_service.get(key) == post_service.get(key) for key in service_keys
        ),
        **semantics,
    }
    preparation_outcomes = {
        "caller_bundle_three_turn_chain_structurally_consistent": all(
            checks[key]
            for key in (
                "authenticated_chrome_transport_declared",
                "browser_provenance_channel_is_chrome_extension",
                "codex_rollout_has_exact_single_authorization_event",
                "codex_rollout_capture_events_are_chrome_backend",
                "codex_rollout_turn_receipts_bind_artifacts_and_telegram_ids",
                "codex_rollout_final_receipt_binds_provenance_artifacts_and_all_ids",
                "codex_rollout_capture_event_order_valid",
                "browser_observed_telegram_web_origin",
                "browser_observed_exact_chat_identity",
                "browser_observed_exact_message_and_reply_ids",
                "authorization_provenance_hash_and_timestamp_bound",
                "exact_destination_matches",
                "exact_three_messages_and_no_extras",
                "sent_text_exact",
                "visible_replies_nonempty",
                "strict_send_reply_turn_order",
                "screenshots_are_valid_png_with_metadata",
                "per_turn_screenshot_hashes_distinct_nonempty",
                "per_turn_accessibility_hashes_distinct_nonempty",
                "final_capture_reuse_policy_satisfied",
                "visual_evidence_files_present",
                "turn_accessibility_binds_exact_messages_and_replies",
            )
        ),
        "caller_bundle_db_grounded_challenge_structurally_consistent": all(
            checks[key]
            for key in (
                "visible_db_object_selected_without_operator_id",
                "visible_weak_support_explained",
                "same_subject_survived_challenge",
                "assumption_support_and_falsifier_separated",
                "independent_db_object_matches_visible_subject",
            )
        ),
        "caller_bundle_review_only_revision_structurally_consistent": all(
            checks[key]
            for key in (
                "multiple_review_only_candidates_proposed",
                "objection_revised_candidate",
                "resolving_source_named",
                "review_approval_apply_boundary_explicit",
                "reply_claimed_no_mutation",
            )
        ),
        "caller_bundle_canonical_state_claim_structurally_consistent": checks[
            "canonical_fingerprint_unchanged_from_preflight"
        ],
    }
    preparation_ready = all(checks.values()) and all(preparation_outcomes.values())
    return {
        "schema": SCHEMA,
        "generated_at_utc": datetime.now(timezone.utc).isoformat(),
        "mode": "telegram_visible_unseen_chain_preparation_verifier",
        "status": PREPARATION_STATUS if preparation_ready else "fail",
        **_preparation_only_fields(preparation_ready=preparation_ready),
        "capture_bundle_present": True,
        "production_apply_executed": False,
        "mutates_db": False,
        "packet_path": str(packet_path),
        "checks": checks,
        "preparation_outcomes": preparation_outcomes,
        "selected_subject": {
            "ref": subject_ref,
            "table": subject_match.get("table"),
            "row_id": (subject_match.get("row") or {}).get("id"),
        },
        "evidence": {
            "final_screenshot": screenshot,
            "final_accessibility": accessibility,
            "turn_evidence": turn_evidence,
            "browser_provenance": browser_provenance_evidence,
            "browser_provider_identifiers": browser_provenance.get("browser"),
            "codex_rollout_provenance": codex_rollout_evidence,
            "timeline": timeline,
            "final_capture_reuses_turn_3": capture_source.get("final_capture_reuses_turn_3"),
            "final_capture_reuse_reason": capture_source.get("final_capture_reuse_reason"),
            "telegram_message_ids": ids,
            "telegram_reply_ids": reply_ids,
            "preflight_state_token_sha256": preflight.get("state_token_sha256"),
            "postflight_state_token_sha256": postflight.get("state_token_sha256"),
            "canonical_fingerprint_sha256": post_fingerprint.get("fingerprint_sha256"),
        },
        "claim_ceiling": {
            "proven": (
                "The caller-supplied capture bundle is structurally consistent, including its chronology, evidence, "
                "semantic, and unchanged-state claims. It is ready for an independently authenticated platform or "
                "browser receipt outside this verifier; no live activity is authenticated here."
                if preparation_ready
                else "No preparation claim; at least one structural, semantic, binding, or state check failed."
            ),
            "not_proven": [
                "T3 live-readonly Telegram proof",
                "Telegram-visible message delivery or replies",
                "canonical proposal staging or apply",
                "agent-to-agent review",
                "GCP runtime parity for the Telegram-visible chain",
            ],
        },
    }


def write_json(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")


def write_markdown(path: Path, data: dict[str, Any]) -> None:
    lines = [
        "# Telegram-Visible Unseen Chain Capture Receipt",
        "",
        f"Generated UTC: `{data['generated_at_utc']}`",
        f"Status: `{data['status']}`",
        f"Preparation ready: `{data['preparation_ready']}`",
        f"Receipt ready: `{data['receipt_ready']}`",
        f"Current tier: `{data['current_tier']}`",
        f"Required tier: `{data['required_tier']}`",
        f"Telegram-visible messages sent: `{data['telegram_visible_messages_sent']}`",
        f"Independent live receipt verified: `{data['live_receipt_boundary']['verified']}`",
        f"Mutates DB: `{data['mutates_db']}`",
        "",
        "## Checks",
        "",
    ]
    lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(data.get("checks", {}).items()))
    if data.get("capture_template"):
        lines.extend(
            [
                "",
                "## Awaiting Authorized Capture",
                "",
                "The packet and preflight are structurally ready. No Telegram message has been sent by this verifier.",
                "A future T3 receipt requires an independently authenticated platform/browser authority outside this verifier.",
            ]
        )
    lines.extend(
        [
            "",
            "## Trust Boundary",
            "",
            "Local files, capture artifacts, and Codex rollout JSONL are caller-writable preparation evidence only. "
            "This verifier cannot accept or mint a live receipt.",
            "",
            "## Claim Ceiling",
            "",
            data["claim_ceiling"]["proven"],
            "",
        ]
    )
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("\n".join(lines), encoding="utf-8")


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--packet", type=Path, default=DEFAULT_PACKET)
    parser.add_argument("--preflight", type=Path, default=DEFAULT_PREFLIGHT)
    parser.add_argument("--capture-json", type=Path)
    parser.add_argument("--postflight", type=Path, default=DEFAULT_POSTFLIGHT)
    parser.add_argument("--evidence-root", type=Path, default=Path("."))
    parser.add_argument(
        "--codex-rollout-log",
        type=Path,
        help="Local Codex rollout JSONL for preparation-structure checks; this never authenticates a live receipt",
    )
    parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
    parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv)
    packet = _load_json(args.packet)
    preflight = _load_json(args.preflight)
    capture = _load_json(args.capture_json) if args.capture_json else None
    postflight = _load_json(args.postflight) if capture is not None and args.postflight.exists() else None
    receipt = verify_capture(
        packet,
        preflight,
        capture,
        postflight,
        packet_path=args.packet,
        evidence_root=args.evidence_root,
        codex_rollout_log=args.codex_rollout_log,
    )
    write_json(args.out, receipt)
    write_markdown(args.markdown_out, receipt)
    print(
        json.dumps(
            {
                "out": str(args.out),
                "markdown_out": str(args.markdown_out),
                "status": receipt["status"],
                "receipt_ready": receipt["receipt_ready"],
            },
            indent=2,
            sort_keys=True,
        )
    )
    return 0 if receipt["status"] in {PREPARATION_STATUS, "awaiting_action_time_authorization"} else 2


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