#!/usr/bin/env python3
"""Data-driven, attended ephemeral credential custody lifecycle.

The command never prints or persists bearer values. Live projection requires a
current Whitehat broker-readiness receipt in State Hub and an exact attended
confirmation. Cleanup is scoped only by the validated projection contract and
receipt.
"""

from __future__ import annotations

import argparse
import json
import os
import secrets
import stat
import subprocess
import sys
import tempfile
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Callable


SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPT_DIR))

from custody_contract import (  # noqa: E402
    CLEANUP_INTERFACE,
    PROJECTION_INTERFACE,
    WORKPLAN_ID,
    ContractError,
    canonical_json,
    contract_digest,
    current_broker_receipt,
    digest,
    load_json,
    parse_time,
    resource_names,
    validate_cleanup_receipt,
    validate_projection_contract,
    validate_projection_receipt,
)
from remote_exec import RemoteExecutionError, run_remote  # noqa: E402


DEFAULT_API_BASE = os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000")


class ProcedureError(RuntimeError):
    pass


def utc_now() -> datetime:
    return datetime.now(UTC)


def rfc3339(value: datetime) -> str:
    return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def safe_run(
    command: list[str],
    *,
    label: str,
    env: dict[str, str] | None = None,
    input_text: str | None = None,
    allow_missing: bool = False,
    runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
) -> subprocess.CompletedProcess[str]:
    completed = runner(
        command,
        text=True,
        input=input_text,
        capture_output=True,
        env=env,
        check=False,
    )
    if completed.returncode and not allow_missing:
        raise ProcedureError(f"{label} failed (exit {completed.returncode})")
    return completed


def secure_unlink(path: Path) -> None:
    if not path.exists():
        return
    size = path.stat().st_size
    with path.open("r+b", buffering=0) as handle:
        handle.write(b"\0" * size)
        handle.flush()
        os.fsync(handle.fileno())
    path.unlink()


def write_receipt(path: Path, value: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    temporary = Path(temporary_name)
    try:
        os.fchmod(descriptor, 0o600)
        with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
            handle.write(json.dumps(value, indent=2, sort_keys=True) + "\n")
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(temporary, path)
        os.chmod(path, 0o600)
    finally:
        if temporary.exists():
            temporary.unlink()


class Operator:
    def __init__(self, contract: dict[str, Any], token_file: Path) -> None:
        mode = stat.S_IMODE(token_file.stat().st_mode) if token_file.exists() else None
        if mode != 0o600:
            raise ProcedureError(f"OpenBao token file must exist with mode 0600: {token_file}")
        token = token_file.read_text(encoding="utf-8").splitlines()[0].strip()
        if not token:
            raise ProcedureError("OpenBao token file is empty")
        self.contract = contract
        self.remote = contract["authority"]["remote"]
        self.bao_env = dict(
            os.environ,
            BAO_ADDR=os.environ.get("BAO_ADDR", "https://bao.coulomb.social"),
            BAO_TOKEN=token,
        )

    def bao(
        self,
        args: list[str],
        *,
        label: str,
        input_text: str | None = None,
        allow_missing: bool = False,
    ) -> subprocess.CompletedProcess[str]:
        return safe_run(
            ["bao", *args],
            label=label,
            env=self.bao_env,
            input_text=input_text,
            allow_missing=allow_missing,
        )

    def kubectl(
        self,
        args: list[str],
        *,
        label: str,
        input_text: str | None = None,
        allow_missing: bool = False,
    ) -> subprocess.CompletedProcess[str]:
        try:
            return run_remote(
                self.remote,
                ["kubectl", *args],
                label=label,
                input_text=input_text,
                allow_missing=allow_missing,
            )
        except RemoteExecutionError as exc:
            raise ProcedureError(str(exc)) from exc

    def bao_exists(self, args: list[str], *, label: str) -> bool:
        result = self.bao(args, label=label, allow_missing=True)
        if result.returncode == 0:
            return True
        error = result.stderr.lower()
        if "404" in error or "no value found" in error or "no policy named" in error:
            return False
        raise ProcedureError(f"{label} failed without a confirmed not-found response")

    def kubectl_exists(self, args: list[str], *, label: str) -> bool:
        result = self.kubectl(args, label=label, allow_missing=True)
        return result.returncode == 0 and bool(result.stdout.strip())

    def registry(self) -> list[dict[str, Any]]:
        authority = self.contract["authority"]
        result = self.bao(
            ["kv", "get", f"-field={authority['registry_field']}", authority["registry_path"]],
            label="read sender registry",
        )
        try:
            value = json.loads(result.stdout)
        except json.JSONDecodeError as exc:
            raise ProcedureError("sender registry is not valid JSON") from exc
        if not isinstance(value, list):
            raise ProcedureError("sender registry must be a list")
        return value

    def write_registry(self, registry: list[dict[str, Any]]) -> None:
        authority = self.contract["authority"]
        with tempfile.TemporaryDirectory(prefix="railiance-custody-registry-") as directory:
            root = Path(directory)
            os.chmod(root, 0o700)
            document = root / "registry.json"
            document.write_text(canonical_json(registry), encoding="utf-8")
            os.chmod(document, 0o600)
            try:
                self.bao(
                    [
                        "kv",
                        "patch",
                        authority["registry_path"],
                        f"{authority['registry_field']}=@{document}",
                    ],
                    label="write sender registry",
                )
            finally:
                secure_unlink(document)


def identity_names(contract: dict[str, Any]) -> set[str]:
    return {item["sender_name"] for item in contract["identities"]}


def token_paths(contract: dict[str, Any]) -> dict[str, str]:
    authority = contract["authority"]
    prefix = authority["kv_prefix"].strip("/")
    return {
        item["handle"]: f"{authority['kv_mount']}/{prefix}/{item['handle']}"
        for item in contract["identities"]
    }


def add_temporary_identities(
    registry: list[dict[str, Any]], contract: dict[str, Any], values: dict[str, str]
) -> list[dict[str, Any]]:
    names = identity_names(contract)
    present = {str(item.get("name")) for item in registry if isinstance(item, dict)}
    if present & names:
        raise ProcedureError(f"temporary identities already exist: {sorted(present & names)}")
    updated = list(registry)
    expires = contract["window"]["expires_at"]
    for identity in contract["identities"]:
        updated.append(
            {
                "name": identity["sender_name"],
                "tokens": [values[identity["handle"]]],
                "sources": ["whitehat-security"],
                "tenants": [identity["tenant"]],
                "may_write": True,
                "may_read": True,
                "expires_at": expires,
            }
        )
    return updated


def remove_temporary_identities(
    registry: list[dict[str, Any]], contract: dict[str, Any]
) -> tuple[list[dict[str, Any]], list[str]]:
    names = identity_names(contract)
    removed = sorted(
        str(item.get("name"))
        for item in registry
        if isinstance(item, dict) and item.get("name") in names
    )
    updated = [
        item
        for item in registry
        if not isinstance(item, dict) or item.get("name") not in names
    ]
    return updated, removed


def build_policy(contract: dict[str, Any]) -> str:
    paths = token_paths(contract)
    blocks: list[str] = []
    for path in paths.values():
        mount, relative = path.split("/", 1)
        blocks.extend(
            [
                f'path "{mount}/data/{relative}" {{',
                '  capabilities = ["read"]',
                "}",
                "",
                f'path "{mount}/metadata/{relative}" {{',
                '  capabilities = ["read"]',
                "}",
                "",
            ]
        )
    return "\n".join(blocks).rstrip() + "\n"


def build_manifest(contract: dict[str, Any], lease_id: str) -> str:
    names = resource_names(contract)
    authority = contract["authority"]
    runner = contract["runner"]
    engagement = contract["engagement_id"]
    annotations = {
        "custody.railiance/engagement": engagement,
        "custody.railiance/lease-id": lease_id,
        "custody.railiance/contract-sha256": contract_digest(contract),
    }
    document = {
        "apiVersion": "v1",
        "kind": "List",
        "items": [
            {
                "apiVersion": "external-secrets.io/v1",
                "kind": "ClusterSecretStore",
                "metadata": {"name": names["store"], "annotations": annotations},
                "spec": {
                    "provider": {
                        "vault": {
                            "server": "http://openbao.openbao.svc:8200",
                            "path": authority["kv_mount"],
                            "version": "v2",
                            "auth": {
                                "kubernetes": {
                                    "mountPath": "kubernetes",
                                    "role": names["role"],
                                    "serviceAccountRef": {
                                        "name": authority["eso_service_account"],
                                        "namespace": authority["eso_namespace"],
                                    },
                                }
                            },
                        }
                    },
                    "conditions": [{"namespaces": [runner["namespace"]]}],
                },
            },
            {
                "apiVersion": "external-secrets.io/v1",
                "kind": "ExternalSecret",
                "metadata": {
                    "name": names["external_secret"],
                    "namespace": runner["namespace"],
                    "annotations": annotations,
                },
                "spec": {
                    "refreshInterval": "1m",
                    "secretStoreRef": {"kind": "ClusterSecretStore", "name": names["store"]},
                    "target": {
                        "name": runner["secret_name"],
                        "creationPolicy": "Owner",
                        "deletionPolicy": "Delete",
                    },
                    "data": [
                        {
                            "secretKey": identity["handle"],
                            "remoteRef": {
                                "key": f"{authority['kv_prefix'].strip('/')}/{identity['handle']}",
                                "property": "token",
                            },
                        }
                        for identity in sorted(contract["identities"], key=lambda item: item["handle"])
                    ],
                },
            },
        ],
    }
    return canonical_json(document)


def time_state(contract: dict[str, Any], now: datetime) -> str:
    starts = parse_time(contract["window"]["starts_at"], "window.starts_at")
    cutoff = parse_time(contract["window"]["projection_cutoff"], "window.projection_cutoff")
    if now < starts:
        return "before-window"
    if now > cutoff:
        return "projection-cutoff-passed"
    return "projection-window-open"


def target_ready(operator: Operator, contract: dict[str, Any]) -> str:
    target = contract["target"]
    image = operator.kubectl(
        [
            "-n",
            target["namespace"],
            "get",
            "deploy",
            target["deployment"],
            "-o",
            f"jsonpath={{.spec.template.spec.containers[?(@.name==\"{target['container']}\")].image}}",
        ],
        label="read target image",
    ).stdout.strip()
    if not image.endswith("@" + target["image_digest"]):
        raise ProcedureError("target is not on the contract-approved image digest")
    ready = operator.kubectl(
        [
            "-n",
            target["namespace"],
            "get",
            "deploy",
            target["deployment"],
            "-o",
            "jsonpath={.status.readyReplicas}/{.status.replicas}",
        ],
        label="read target readiness",
    ).stdout.strip()
    if ready != "1/1":
        raise ProcedureError(f"target is not 1/1 Ready (observed {ready or 'unknown'})")
    return ready


def resource_presence(operator: Operator, contract: dict[str, Any]) -> dict[str, Any]:
    names = resource_names(contract)
    runner = contract["runner"]
    paths = token_paths(contract)
    identities = sorted(
        identity_names(contract)
        & {
            str(item.get("name"))
            for item in operator.registry()
            if isinstance(item, dict)
        }
    )
    exact_paths = sorted(
        handle
        for handle, path in paths.items()
        if operator.bao_exists(
            ["kv", "metadata", "get", "-format=json", path],
            label=f"check exact {handle} KV metadata",
        )
    )
    resources: list[str] = []
    checks = (
        ("store", ["get", "clustersecretstore", names["store"], "-o", "name"]),
        (
            "external_secret",
            ["-n", runner["namespace"], "get", "externalsecret", names["external_secret"], "-o", "name"],
        ),
        (
            "mounted_secret",
            ["-n", runner["namespace"], "get", "secret", runner["secret_name"], "-o", "name"],
        ),
    )
    for label, args in checks:
        if operator.kubectl_exists(args, label=f"check exact {label}"):
            resources.append(label)
    return {
        "temporary_identities_present": identities,
        "exact_token_paths_present": exact_paths,
        "projection_resources_present": resources,
    }


def live_preflight(
    operator: Operator,
    contract: dict[str, Any],
    *,
    api_base: str = DEFAULT_API_BASE,
    now: datetime | None = None,
) -> dict[str, Any]:
    current = (now or utc_now()).astimezone(UTC)
    operator.bao(["status", "-format=json"], label="read OpenBao status")
    target = target_ready(operator, contract)
    runner = contract["runner"]
    authority = contract["authority"]
    operator.kubectl(["get", "ns", runner["namespace"], "-o", "name"], label="verify runner namespace")
    operator.kubectl(
        ["-n", authority["eso_namespace"], "get", "sa", authority["eso_service_account"], "-o", "name"],
        label="verify ESO service account",
    )
    presence = resource_presence(operator, contract)
    broker_ready = True
    broker_reason = None
    try:
        current_broker_receipt(contract, api_base, now=current)
    except ContractError as exc:
        broker_ready = False
        broker_reason = str(exc)
    return {
        "interface": "railiance.custody-preflight",
        "version": 1,
        "engagement_id": contract["engagement_id"],
        "projection_contract_digest": contract_digest(contract),
        "time_state": time_state(contract, current),
        "broker_ready": broker_ready,
        "broker_gate": "pass" if broker_ready else "blocked",
        "broker_reason": broker_reason,
        "target_image_matches": True,
        "target_ready": target,
        "openbao_initialized": True,
        "openbao_sealed": False,
        **presence,
        "secret_values_observed": False,
    }


def write_exact_values(operator: Operator, contract: dict[str, Any], values: dict[str, str]) -> None:
    with tempfile.TemporaryDirectory(prefix="railiance-custody-values-") as directory:
        root = Path(directory)
        os.chmod(root, 0o700)
        files: list[Path] = []
        try:
            for handle, path in token_paths(contract).items():
                document = root / handle
                document.write_text(values[handle], encoding="utf-8")
                os.chmod(document, 0o600)
                files.append(document)
                operator.bao(
                    [
                        "kv",
                        "put",
                        path,
                        f"token=@{document}",
                        f"engagement_id={contract['engagement_id']}",
                        f"expires_at={contract['window']['expires_at']}",
                    ],
                    label=f"write exact {handle} KV path",
                )
        finally:
            for document in files:
                secure_unlink(document)


def configure_projection(operator: Operator, contract: dict[str, Any], lease_id: str) -> None:
    names = resource_names(contract)
    authority = contract["authority"]
    runner = contract["runner"]
    operator.bao(
        ["policy", "write", names["policy"], "-"],
        label="write exact ESO policy",
        input_text=build_policy(contract),
    )
    operator.bao(
        [
            "write",
            f"auth/kubernetes/role/{names['role']}",
            f"bound_service_account_names={authority['eso_service_account']}",
            f"bound_service_account_namespaces={authority['eso_namespace']}",
            f"policies={names['policy']}",
            "ttl=5m",
            "max_ttl=15m",
        ],
        label="write exact ESO Kubernetes role",
    )
    operator.kubectl(
        ["apply", "-f", "-"],
        label="apply exact projection manifest",
        input_text=build_manifest(contract, lease_id),
    )
    operator.kubectl(
        ["wait", "--for=condition=Ready", f"clustersecretstore/{names['store']}", "--timeout=90s"],
        label="wait for exact store",
    )
    operator.kubectl(
        [
            "-n",
            runner["namespace"],
            "wait",
            "--for=condition=Ready",
            f"externalsecret/{names['external_secret']}",
            "--timeout=90s",
        ],
        label="wait for exact ExternalSecret",
    )
    keys = operator.kubectl(
        [
            "-n",
            runner["namespace"],
            "get",
            "secret",
            runner["secret_name"],
            "-o",
            'go-template={{range $k, $_ := .data}}{{$k}}{{"\\n"}}{{end}}',
        ],
        label="verify projected key names",
    ).stdout.splitlines()
    expected = sorted(identity["handle"] for identity in contract["identities"])
    if sorted(keys) != expected:
        raise ProcedureError(f"projected Secret has unexpected key names: {sorted(keys)}")


def force_sender_sync_and_restart(operator: Operator, contract: dict[str, Any]) -> None:
    target = contract["target"]
    namespace = target["namespace"]
    external_secret = target["sender_external_secret"]
    before = operator.kubectl(
        ["-n", namespace, "get", "secret", external_secret, "-o", "jsonpath={.metadata.resourceVersion}"],
        label="read sender Secret resource version",
    ).stdout.strip()
    operator.kubectl(
        [
            "-n",
            namespace,
            "annotate",
            "externalsecret",
            external_secret,
            f"force-sync={int(time.time())}",
            "--overwrite",
        ],
        label="force sender registry sync",
    )
    deadline = time.monotonic() + 120
    while time.monotonic() < deadline:
        current = operator.kubectl(
            ["-n", namespace, "get", "secret", external_secret, "-o", "jsonpath={.metadata.resourceVersion}"],
            label="poll sender Secret resource version",
        ).stdout.strip()
        if current and current != before:
            break
        time.sleep(2)
    else:
        raise ProcedureError("sender Secret did not refresh within 120 seconds")
    operator.kubectl(
        ["-n", namespace, "rollout", "restart", f"deploy/{target['deployment']}"],
        label="restart sender registry reader",
    )
    operator.kubectl(
        ["-n", namespace, "rollout", "status", f"deploy/{target['deployment']}", "--timeout=120s"],
        label="wait for target rollout",
    )


def delete_projection(operator: Operator, contract: dict[str, Any]) -> None:
    names = resource_names(contract)
    runner = contract["runner"]
    operator.kubectl(
        ["-n", runner["namespace"], "delete", "externalsecret", names["external_secret"], "--ignore-not-found"],
        label="delete exact ExternalSecret",
    )
    operator.kubectl(
        ["-n", runner["namespace"], "delete", "secret", runner["secret_name"], "--ignore-not-found"],
        label="delete exact mounted Secret",
    )
    operator.kubectl(
        ["delete", "clustersecretstore", names["store"], "--ignore-not-found"],
        label="delete exact store",
    )
    for handle, path in token_paths(contract).items():
        if operator.bao_exists(
            ["kv", "metadata", "get", "-format=json", path],
            label=f"check exact {handle} metadata before delete",
        ):
            operator.bao(["kv", "metadata", "delete", path], label=f"delete exact {handle} KV path")
    if operator.bao_exists(["read", f"auth/kubernetes/role/{names['role']}"] , label="check exact role"):
        operator.bao(["delete", f"auth/kubernetes/role/{names['role']}"] , label="delete exact role")
    if operator.bao_exists(["policy", "read", names["policy"]], label="check exact policy"):
        operator.bao(["policy", "delete", names["policy"]], label="delete exact policy")


def verify_cleanup_absent(operator: Operator, contract: dict[str, Any]) -> None:
    operator.bao(["status", "-format=json"], label="verify OpenBao after cleanup")
    operator.kubectl(["get", "ns", contract["runner"]["namespace"], "-o", "name"], label="verify Kubernetes after cleanup")
    presence = resource_presence(operator, contract)
    if any(presence.values()):
        raise ProcedureError(f"engagement resources remain after cleanup: {presence}")


def cleanup_scope(operator: Operator, contract: dict[str, Any]) -> dict[str, Any]:
    registry = operator.registry()
    updated, removed = remove_temporary_identities(registry, contract)
    if removed:
        operator.write_registry(updated)
        force_sender_sync_and_restart(operator, contract)
    delete_projection(operator, contract)
    verify_cleanup_absent(operator, contract)
    target_ready(operator, contract)
    return {
        "removed_identities": removed,
        "removed_resources": sorted(
            [
                *token_paths(contract).values(),
                *resource_names(contract).values(),
            ]
        ),
    }


def transactional(
    steps: list[Callable[[], None]], rollback: Callable[[], Any]
) -> None:
    try:
        for step in steps:
            step()
    except Exception as original:
        try:
            rollback()
        except Exception as cleanup_error:
            raise ProcedureError(
                "projection failed and exact cleanup could not be proven"
            ) from cleanup_error
        raise original


def resource_uids(
    operator: Operator, contract: dict[str, Any], *, require_all: bool = True
) -> dict[str, str]:
    names = resource_names(contract)
    runner = contract["runner"]
    requests = {
        "store": [
            "get", "clustersecretstore", names["store"], "--ignore-not-found",
            "-o", "jsonpath={.metadata.uid}",
        ],
        "external_secret": [
            "-n", runner["namespace"], "get", "externalsecret", names["external_secret"],
            "--ignore-not-found",
            "-o", "jsonpath={.metadata.uid}",
        ],
        "mounted_secret": [
            "-n", runner["namespace"], "get", "secret", runner["secret_name"],
            "--ignore-not-found",
            "-o", "jsonpath={.metadata.uid}",
        ],
    }
    result = {
        key: operator.kubectl(args, label=f"read exact {key} UID").stdout.strip()
        for key, args in requests.items()
    }
    if require_all and any(not value for value in result.values()):
        raise ProcedureError("projection resource UID evidence is incomplete")
    return {key: value for key, value in result.items() if value}


def verify_receipt_resource_scope(
    operator: Operator, contract: dict[str, Any], receipt: dict[str, Any]
) -> dict[str, str]:
    present = resource_uids(operator, contract, require_all=False)
    expected = receipt["resources"]["uids"]
    mismatched = {
        key: value
        for key, value in present.items()
        if expected.get(key) != value
    }
    if mismatched:
        raise ProcedureError(
            "live projection resource UID differs from the cleanup receipt; refusing deletion"
        )
    return present


def project(
    operator: Operator,
    contract: dict[str, Any],
    *,
    api_base: str = DEFAULT_API_BASE,
    now: datetime | None = None,
    lease_id: str | None = None,
) -> dict[str, Any]:
    current = (now or utc_now()).astimezone(UTC)
    if time_state(contract, current) != "projection-window-open":
        raise ProcedureError("projection is outside the approved projection window")
    # This is intentionally before token generation and every mutation.
    broker_receipt = current_broker_receipt(contract, api_base, now=current)
    preflight = live_preflight(operator, contract, api_base=api_base, now=current)
    if not preflight["broker_ready"]:
        raise ProcedureError("consumer broker is not ready")
    if any(
        preflight[key]
        for key in (
            "temporary_identities_present",
            "exact_token_paths_present",
            "projection_resources_present",
        )
    ):
        raise ProcedureError("cleanup is required before a new projection")
    active_lease = lease_id or f"custody:{secrets.token_hex(16)}"
    values = {identity["handle"]: secrets.token_urlsafe(48) for identity in contract["identities"]}
    if len(set(values.values())) != len(values):
        raise ProcedureError("credential generator returned duplicate values")
    try:
        transactional(
            [
                lambda: write_exact_values(operator, contract, values),
                lambda: operator.write_registry(
                    add_temporary_identities(operator.registry(), contract, values)
                ),
                lambda: configure_projection(operator, contract, active_lease),
                lambda: force_sender_sync_and_restart(operator, contract),
            ],
            lambda: cleanup_scope(operator, contract),
        )
    finally:
        values.clear()
    projected_at = rfc3339(utc_now())
    base = {
        "interface": PROJECTION_INTERFACE,
        "version": 1,
        "workplan_id": WORKPLAN_ID,
        "state": "projected",
        "lease_id": active_lease,
        "engagement_id": contract["engagement_id"],
        "target": {
            "id": contract["target"]["id"],
            "revision": contract["target"]["revision"],
            "image_digest": contract["target"]["image_digest"],
        },
        "projection_contract_digest": contract_digest(contract),
        "broker_receipt_digest": digest(broker_receipt),
        "projected_at": projected_at,
        "expires_at": contract["window"]["expires_at"],
        "identities": sorted(
            (
                {
                    "handle": item["handle"],
                    "role": item["role"],
                    "sender_name": item["sender_name"],
                    "mount_path": item["mount_path"],
                }
                for item in contract["identities"]
            ),
            key=lambda item: item["handle"],
        ),
        "resources": {
            "names": resource_names(contract),
            "uids": resource_uids(operator, contract),
        },
        "cleanup_authority": "railiance-platform",
        "secret_values_observed": False,
    }
    receipt = {**base, "receipt_id": f"sha256:{digest(base)}"}
    validate_projection_receipt(receipt, contract)
    return receipt


def projection_status(
    operator: Operator, contract: dict[str, Any], receipt: dict[str, Any]
) -> dict[str, Any]:
    validate_projection_receipt(receipt, contract)
    presence = resource_presence(operator, contract)
    counts = [
        len(presence["temporary_identities_present"]),
        len(presence["exact_token_paths_present"]),
        len(presence["projection_resources_present"]),
    ]
    state = "active" if counts == [2, 2, 3] else "absent" if counts == [0, 0, 0] else "partial"
    uid_match = False
    present_uids = resource_uids(operator, contract, require_all=False)
    uid_match = bool(present_uids) and all(
        receipt["resources"]["uids"].get(key) == value
        for key, value in present_uids.items()
    )
    if present_uids and not uid_match:
        state = "mismatched"
    return {
        "interface": "railiance.custody-projection-status",
        "version": 1,
        "engagement_id": contract["engagement_id"],
        "lease_id": receipt["lease_id"],
        "state": state,
        "resource_uids_match": uid_match,
        **presence,
        "target_ready": target_ready(operator, contract),
        "secret_values_observed": False,
    }


def cleanup(
    operator: Operator, contract: dict[str, Any], projection: dict[str, Any]
) -> dict[str, Any]:
    validate_projection_receipt(projection, contract)
    verify_receipt_resource_scope(operator, contract, projection)
    result = cleanup_scope(operator, contract)
    receipt = {
        "interface": CLEANUP_INTERFACE,
        "version": 1,
        "workplan_id": WORKPLAN_ID,
        "state": "cleaned",
        "lease_id": projection["lease_id"],
        "engagement_id": contract["engagement_id"],
        "projection_receipt_id": projection["receipt_id"],
        "cleaned_at": rfc3339(utc_now()),
        "removed_resources": result["removed_resources"],
        "target_ready": True,
        "secret_values_observed": False,
    }
    validate_cleanup_receipt(receipt, projection, contract)
    return receipt


def assert_confirm(value: str | None, expected: str) -> None:
    if value != expected:
        raise ProcedureError(f"live command requires --confirm {expected}")


def assert_expired_cleanup(contract: dict[str, Any], now: datetime) -> None:
    expires = parse_time(contract["window"]["expires_at"], "window.expires_at")
    if now.astimezone(UTC) <= expires:
        raise ProcedureError("cleanup-expired refuses before receipt expiry")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "command",
        choices=["validate", "render", "preflight", "project", "status", "cleanup", "cleanup-expired"],
    )
    parser.add_argument("--contract", type=Path, required=True)
    parser.add_argument("--receipt", type=Path)
    parser.add_argument("--receipt-out", type=Path)
    parser.add_argument("--token-file", type=Path, default=Path.home() / ".local/openbao/platform-admin.token")
    parser.add_argument("--state-hub", default=DEFAULT_API_BASE)
    parser.add_argument("--confirm")
    args = parser.parse_args()
    try:
        contract = validate_projection_contract(load_json(args.contract))
        if args.command == "validate":
            result = {
                "valid": True,
                "projection_contract_digest": contract_digest(contract),
                "resource_names": resource_names(contract),
                "secret_values_observed": False,
            }
        elif args.command == "render":
            result = {
                "policy": build_policy(contract),
                "manifest": json.loads(build_manifest(contract, "custody:" + "0" * 32)),
                "secret_values_observed": False,
            }
        else:
            projection = None
            if args.command in {"status", "cleanup", "cleanup-expired"}:
                if not args.receipt:
                    raise ProcedureError(f"{args.command} requires --receipt")
                projection = validate_projection_receipt(load_json(args.receipt), contract)
            if args.command == "cleanup-expired":
                assert_expired_cleanup(contract, utc_now())
            if args.command == "project":
                # Reject an unready consumer before reading platform-admin
                # custody material. project() repeats this immutable gate.
                current_broker_receipt(contract, args.state_hub)
            operator = Operator(contract, args.token_file)
            if args.command == "preflight":
                result = live_preflight(operator, contract, api_base=args.state_hub)
            elif args.command == "project":
                assert_confirm(args.confirm, f"{contract['engagement_id']}:attended")
                result = project(operator, contract, api_base=args.state_hub)
            else:
                assert projection is not None
                if args.command == "status":
                    result = projection_status(operator, contract, projection)
                else:
                    if args.command == "cleanup-expired":
                        assert_confirm(args.confirm, f"{contract['engagement_id']}:expired")
                    else:
                        assert_confirm(args.confirm, f"{contract['engagement_id']}:cleanup")
                    result = cleanup(operator, contract, projection)
        if args.receipt_out:
            write_receipt(args.receipt_out, result)
        print(json.dumps(result, indent=2, sort_keys=True))
        return 0
    except (ContractError, OSError, ProcedureError, RemoteExecutionError) as exc:
        print(f"custody projection failed: {exc}", file=sys.stderr)
        return 1


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