#!/usr/bin/python3
# Sentinel Download Guard
# Program: Sentinel Download Guard backend/CLI foundation
# Version: 1.0.0
#
# Local-first download quarantine, risk classification, release/abandon workflow,
# and AEGIS-readable status contract.
#
# Design doctrine:
# - No telemetry.
# - No cloud scanning.
# - Never auto-open downloads.
# - Browser downloads should enter quarantine first.
# - Release only by explicit user action.

from __future__ import annotations

import argparse
import datetime as _dt
import hashlib
import html
import json
import mimetypes
import os
from pathlib import Path
import random
import re
import shutil
import stat
import sys
import secrets
import subprocess
import tempfile
import traceback
from typing import Any, Dict, List, Optional, Tuple

VERSION = "1.0.1"
PROGRAM_ID = "sentinel-download-guard"
PACKAGE = PROGRAM_ID
SCHEMA_POLICY = "sentinel.download_guard.policy.v1"
SCHEMA_STATUS = "sentinel.download_guard.status.v1"
SCHEMA_ITEM = "sentinel.download_guard.item.v1"
SCHEMA_AEGIS = "sentinel.download_guard.aegis_status.v1"

DANGEROUS_EXTENSIONS = {
    ".exe", ".msi", ".com", ".scr", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".jse", ".wsf",
    ".jar", ".sh", ".bash", ".zsh", ".fish", ".run", ".bin", ".appimage",
    ".deb", ".rpm", ".pkg", ".dmg", ".desktop", ".service", ".timer",
    ".dll", ".so", ".dylib", ".ko", ".sys",
}
CAUTION_EXTENSIONS = {
    ".zip", ".7z", ".rar", ".tar", ".gz", ".bz2", ".xz", ".tgz", ".tbz", ".txz",
    ".iso", ".img", ".qcow2", ".vdi", ".vmdk",
    ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp",
    ".svg", ".html", ".htm", ".xhtml", ".xml",
    ".py", ".pl", ".rb", ".php", ".lua",
}
NORMAL_EXTENSIONS = {
    ".txt", ".md", ".csv", ".json", ".yaml", ".yml",
    ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tif", ".tiff",
    ".mp3", ".ogg", ".wav", ".flac", ".mp4", ".mkv", ".webm",
}

UNSAFE_RELEASE_DIRS = {
    "/", "/bin", "/sbin", "/usr", "/usr/bin", "/usr/sbin", "/etc", "/boot", "/dev",
    "/proc", "/sys", "/run", "/var", "/lib", "/lib64", "/tmp",
}

def now_iso() -> str:
    return _dt.datetime.now(_dt.timezone.utc).astimezone().isoformat(timespec="seconds")

def xdg_config_home() -> Path:
    return Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")).expanduser()

def xdg_data_home() -> Path:
    return Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")).expanduser()

def xdg_state_home() -> Path:
    return Path(os.environ.get("XDG_STATE_HOME", Path.home() / ".local" / "state")).expanduser()

def config_dir() -> Path:
    return xdg_config_home() / PROGRAM_ID

def data_dir() -> Path:
    return xdg_data_home() / PROGRAM_ID

def state_dir() -> Path:
    return xdg_state_home() / PROGRAM_ID

def default_policy_path() -> Path:
    return config_dir() / "policy.json"

def default_quarantine_dir() -> Path:
    return data_dir() / "quarantine"

def default_metadata_dir() -> Path:
    return data_dir() / "metadata"

def default_aegis_status_path() -> Path:
    return state_dir() / "aegis" / "download_guard_status.json"

def expand_path(value: str) -> Path:
    # Do not resolve here: resolving would silently follow symlinked policy paths.
    return Path(os.path.expandvars(os.path.expanduser(value)))


def _path_has_symlink_component(path: Path) -> bool:
    p = Path(path).expanduser()
    cur = Path(p.anchor) if p.is_absolute() else Path.cwd()
    parts = p.parts[1:] if p.is_absolute() else p.parts
    for part in parts:
        cur = cur / part
        try:
            if cur.is_symlink():
                return True
        except OSError:
            return True
    return False


def secure_chmod_700(path: Path) -> None:
    try:
        if path.exists() and path.is_dir() and not _path_has_symlink_component(path):
            os.chmod(path, 0o700, follow_symlinks=False)
    except Exception:
        pass

def mkdirp(path: Path) -> None:
    if _path_has_symlink_component(path):
        raise RuntimeError(f"unsafe symlinked directory refused: {path}")
    path.mkdir(parents=True, exist_ok=True)
    # Download Guard stores quarantine files, metadata, and audit state. Keep
    # created directories private on clean Debian as well as SentinelOS.
    secure_chmod_700(path)


def _safe_regular_file(path: Path) -> bool:
    try:
        st = path.lstat()
        return stat.S_ISREG(st.st_mode) and not path.is_symlink()
    except Exception:
        return False


def _safe_atomic_write_text(path: Path, text: str, mode: int = 0o600) -> None:
    if path.exists() and path.is_symlink():
        raise RuntimeError(f"refusing to overwrite symlink: {path}")
    if _path_has_symlink_component(path.parent):
        raise RuntimeError(f"refusing to write through symlinked parent: {path.parent}")
    mkdirp(path.parent)
    fd, tmp_name = tempfile.mkstemp(prefix=path.name + ".", suffix=".tmp", dir=str(path.parent), text=True)
    tmp = Path(tmp_name)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as fh:
            fh.write(text)
        os.chmod(tmp, mode)
        os.replace(tmp, path)
    finally:
        try:
            if tmp.exists(): tmp.unlink()
        except Exception:
            pass

def json_out(obj: Any) -> None:
    print(json.dumps(obj, indent=2, sort_keys=False))

def atomic_write_json(path: Path, obj: Any) -> None:
    mkdirp(path.parent)
    _safe_atomic_write_text(path, json.dumps(obj, indent=2, sort_keys=False) + "\n")

def default_policy() -> Dict[str, Any]:
    return {
        "schema": SCHEMA_POLICY,
        "program": PROGRAM_ID,
        "version": VERSION,
        "local_first": True,
        "telemetry": False,
        "cloud_scanning": False,
        "never_auto_open_downloads": True,
        "browser_session_downloads_supported": True,
        "direct_fetch_enabled": False,
        "quarantine_dir": str(default_quarantine_dir()),
        "metadata_dir": str(default_metadata_dir()),
        "release_dir": str(Path.home() / "Downloads"),
        "release_collision_policy": "append_counter",
        "require_confirmation_for": ["caution", "dangerous", "blocked", "unknown"],
        "allow_force_release_blocked": False,
        "audit_metadata_only": True,
        "aegis_status_enabled": True,
        "aegis_status_path": str(default_aegis_status_path()),
        "browser_state_path": str(state_dir() / "browser" / "download_state.json"),
        "active_downloads_path": str(state_dir() / "browser" / "active_downloads.json"),
        "completion_events_path": str(state_dir() / "browser" / "completion_events.jsonl"),
        "history_limit": 500,
        "allow_open_released_file": True,
        "allow_open_release_folder": True,
        "browser_icon_states": {
            "idle": "yellow",
            "active": "blue",
            "waiting_user": "yellow",
            "released": "yellow",
            "complete": "lime_pastel",
            "error": "red"
        },
        "risk_policy": {
            "dangerous_extensions": sorted(DANGEROUS_EXTENSIONS),
            "caution_extensions": sorted(CAUTION_EXTENSIONS),
            "normal_extensions": sorted(NORMAL_EXTENSIONS),
            "double_extension_warning": True,
            "hidden_file_warning": True,
            "filename_control_character_block": True,
        },
        "release_path_policy": {
            "default_release_dir": "~/Downloads",
            "blocked_release_dirs": sorted(UNSAFE_RELEASE_DIRS),
            "create_release_dir_if_missing": True,
        },
        "aegis_capabilities": {
            "download_guard_status": True,
            "inspect_file": True,
            "register_download": True,
            "list_quarantine": True,
            "release_download": True,
            "abandon_download": True,
            "set_release_dir": True,
            "browser_handoff": True,
            "metadata_only_audit": True,
        }
    }

def load_policy(init_if_missing: bool = True) -> Dict[str, Any]:
    path = default_policy_path()
    if not path.exists():
        pol = default_policy()
        if init_if_missing:
            atomic_write_json(path, pol)
        return pol
    try:
        pol = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        # Do not silently overwrite corrupted policy.
        raise RuntimeError(f"Could not parse policy file: {path}")
    changed = False
    base = default_policy()
    for key, val in base.items():
        if key not in pol:
            pol[key] = val
            changed = True
    if changed and init_if_missing:
        atomic_write_json(path, pol)
    return pol

def init_policy() -> Dict[str, Any]:
    pol = load_policy(init_if_missing=False) if default_policy_path().exists() else default_policy()
    # Re-assert essential directories and schema without erasing custom release_dir.
    base = default_policy()
    for k, v in base.items():
        pol.setdefault(k, v)
    ensure_dirs(pol)
    atomic_write_json(default_policy_path(), pol)
    return {
        "ok": True,
        "action": "init_policy",
        "policy_path": str(default_policy_path()),
        "policy": pol,
    }

def ensure_dirs(policy: Optional[Dict[str, Any]] = None) -> None:
    pol = policy or load_policy()
    mkdirp(expand_path(pol["quarantine_dir"]))
    mkdirp(expand_path(pol["metadata_dir"]))
    mkdirp(default_policy_path().parent)
    mkdirp(state_dir() / "logs")
    mkdirp(default_aegis_status_path().parent)
    rel = expand_path(pol.get("release_dir") or str(Path.home() / "Downloads"))
    if pol.get("release_path_policy", {}).get("create_release_dir_if_missing", True):
        mkdirp(rel)

def sanitize_filename(name: str) -> str:
    if not name:
        name = "download"
    name = name.replace("\\", "/").split("/")[-1].strip()
    name = re.sub(r"[\x00-\x1f\x7f]", "_", name)
    name = name.replace("/", "_")
    if name in ("", ".", ".."):
        name = "download"
    return name[:180]

def generate_id() -> str:
    ts = _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%dT%H%M%S")
    return f"dlg-{ts}-{random.randrange(16**8):08x}"

def sha256_file(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()

def double_extension(filename: str) -> Optional[Tuple[str, str]]:
    parts = filename.lower().split(".")
    if len(parts) >= 3:
        return ("." + parts[-2], "." + parts[-1])
    return None

def detect_file_magic(path: Path) -> Dict[str, Any]:
    """Small local file-header classifier. No network/cloud scanning."""
    try:
        data = path.read_bytes()[:512]
    except Exception as exc:
        return {"kind": "unreadable", "description": type(exc).__name__}
    if data.startswith(b"MZ"):
        return {"kind": "pe_executable", "description": "Windows/PE executable magic MZ"}
    if data.startswith(b"\x7fELF"):
        return {"kind": "elf_executable", "description": "Linux ELF executable magic"}
    if data.startswith(b"#!"):
        return {"kind": "script_shebang", "description": "Script shebang"}
    if data.startswith(b"%PDF-"):
        return {"kind": "pdf", "description": "PDF document"}
    if data.startswith(b"PK\x03\x04"):
        return {"kind": "zip_family", "description": "ZIP/JAR/OOXML archive family"}
    if data.startswith(b"\x89PNG\r\n\x1a\n"):
        return {"kind": "png", "description": "PNG image"}
    if data.startswith(b"\xff\xd8\xff"):
        return {"kind": "jpeg", "description": "JPEG image"}
    if data.startswith(b"GIF87a") or data.startswith(b"GIF89a"):
        return {"kind": "gif", "description": "GIF image"}
    if data.startswith(b"!<arch>\n"):
        return {"kind": "debian_ar_or_static_archive", "description": "ar archive; used by .deb packages"}
    return {"kind": "unknown", "description": "Unknown or unrecognised header"}


def extension_magic_mismatch(filename: str, magic: Dict[str, Any]) -> Optional[str]:
    ext = Path(filename).suffix.lower()
    kind = magic.get("kind", "unknown")
    safe_map = {
        ".pdf": {"pdf"},
        ".png": {"png"},
        ".jpg": {"jpeg"}, ".jpeg": {"jpeg"},
        ".gif": {"gif"},
        ".zip": {"zip_family"}, ".jar": {"zip_family"},
        ".deb": {"debian_ar_or_static_archive"},
        ".sh": {"script_shebang"},
        ".run": {"elf_executable", "script_shebang"},
        ".appimage": {"elf_executable"},
        ".exe": {"pe_executable"},
    }
    if ext in safe_map and kind not in safe_map[ext] and kind != "unknown":
        return f"Extension {ext} does not match file header: {magic.get('description', kind)}"
    if ext in NORMAL_EXTENSIONS and kind in {"pe_executable", "elf_executable", "script_shebang"}:
        return f"Normal-looking extension {ext} has executable/script header: {magic.get('description', kind)}"
    return None


def classify_file(path: Path, original_filename: Optional[str] = None, source_url: Optional[str] = None) -> Dict[str, Any]:
    filename = sanitize_filename(original_filename or path.name)
    ext = Path(filename).suffix.lower()
    reasons: List[str] = []
    risk = "unknown"

    if any(ord(ch) < 32 or ord(ch) == 127 for ch in filename):
        return {
            "risk_level": "blocked",
            "risk_reasons": ["Filename contains control characters"],
            "detected_extension": ext,
            "detected_magic": detect_file_magic(path),
        }

    dbl = double_extension(filename)
    if dbl:
        inner, outer = dbl
        if outer in DANGEROUS_EXTENSIONS or inner in DANGEROUS_EXTENSIONS:
            risk = "dangerous"
            reasons.append(f"Double-extension pattern detected: {inner}{outer}")

    if filename.startswith("."):
        if risk == "unknown":
            risk = "caution"
        reasons.append("Hidden filename")

    if ext in DANGEROUS_EXTENSIONS:
        risk = "dangerous"
        reasons.append(f"{ext} files can execute code, install software, or modify the system")
    elif ext in CAUTION_EXTENSIONS:
        if risk not in ("dangerous", "blocked"):
            risk = "caution"
        reasons.append(f"{ext} files can contain active content, archives, scripts, installers, or embedded risk")
    elif ext in NORMAL_EXTENSIONS:
        if risk == "unknown":
            risk = "normal"
        reasons.append(f"{ext} is normally a low-to-moderate risk file type")
    else:
        if risk == "unknown":
            risk = "unknown"
        reasons.append("Unknown or uncommon file type")

    magic = detect_file_magic(path)
    mismatch = extension_magic_mismatch(filename, magic)
    if magic.get("kind") in {"pe_executable", "elf_executable", "script_shebang"}:
        risk = "dangerous"
        reasons.append("Executable/script file header detected: " + magic.get("description", magic.get("kind", "unknown")))
    if mismatch:
        if risk not in ("dangerous", "blocked"):
            risk = "caution"
        reasons.append(mismatch)

    if source_url:
        u = source_url.lower()
        if u.startswith("http://"):
            if risk == "normal":
                risk = "caution"
            reasons.append("Source URL is plain HTTP, not HTTPS")
        if "download" in u and any(token in u for token in ["?file=", "redirect", "url="]):
            if risk == "normal":
                risk = "caution"
            reasons.append("Source URL appears to use a redirect/download parameter")

    return {
        "risk_level": risk,
        "risk_reasons": reasons,
        "detected_extension": ext,
        "detected_magic": detect_file_magic(path),
    }

def guess_mime(path: Path, filename: str) -> str:
    guessed, _ = mimetypes.guess_type(filename)
    return guessed or "application/octet-stream"

def metadata_path(item_id: str, pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy["metadata_dir"]) / f"{item_id}.json"

def read_item(item_id: str, pol: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    path = metadata_path(item_id, pol)
    if not path.exists():
        raise FileNotFoundError(f"No quarantine metadata found for item id: {item_id}")
    return json.loads(path.read_text(encoding="utf-8"))

def write_item(item: Dict[str, Any], pol: Optional[Dict[str, Any]] = None) -> None:
    atomic_write_json(metadata_path(item["id"], pol), item)

def inspect_file(file_path: str, source_url: Optional[str] = None, suggested_filename: Optional[str] = None) -> Dict[str, Any]:
    path = expand_path(file_path)
    if not path.exists() or not path.is_file():
        return {"ok": False, "error": "file_not_found", "path": str(path)}
    filename = sanitize_filename(suggested_filename or path.name)
    st = path.stat()
    risk = classify_file(path, filename, source_url)
    return {
        "ok": True,
        "schema": "sentinel.download_guard.inspect.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "path": str(path),
        "original_filename": filename,
        "size_bytes": st.st_size,
        "mime_type": guess_mime(path, filename),
        "detected_magic": risk.get("detected_magic", detect_file_magic(path)),
        "sha256": sha256_file(path),
        "source_url": source_url or "",
        "origin_domain": origin_from_url(source_url or ""),
        **risk,
        "policy_notes": [
            "Inspection does not release or trust the file.",
            "No telemetry or cloud scanning is performed.",
        ],
    }

def origin_from_url(url: str) -> str:
    try:
        from urllib.parse import urlparse
        p = urlparse(url)
        return p.netloc.lower()
    except Exception:
        return ""

def register_download(file_path: str, source_url: Optional[str] = None, suggested_filename: Optional[str] = None, copy: bool = False) -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    src = expand_path(file_path)
    if src.is_symlink() or _path_has_symlink_component(src):
        return {"ok": False, "error": "unsafe_source_symlink_refused", "path": str(src)}
    if not src.exists() or not src.is_file() or not _safe_regular_file(src):
        return {"ok": False, "error": "file_not_found", "path": str(src)}
    item_id = generate_id()
    filename = sanitize_filename(suggested_filename or src.name)
    item_dir = expand_path(pol["quarantine_dir"]) / item_id
    mkdirp(item_dir)
    qpath = item_dir / filename

    # Collision inside item dir should not occur, but be defensive.
    counter = 1
    base = qpath.stem
    suffix = qpath.suffix
    while qpath.exists():
        qpath = item_dir / f"{base}-{counter}{suffix}"
        counter += 1

    if copy:
        shutil.copy2(src, qpath, follow_symlinks=False)
        disposition = "copied_to_quarantine"
    else:
        # If source is already inside quarantine, copy then keep original path stable.
        try:
            quarantine_root = expand_path(pol["quarantine_dir"])
            if quarantine_root in src.parents:
                shutil.copy2(src, qpath, follow_symlinks=False)
                disposition = "copied_from_existing_quarantine"
            else:
                shutil.move(str(src), str(qpath))
                disposition = "moved_to_quarantine"
        except Exception:
            shutil.copy2(src, qpath, follow_symlinks=False)
            disposition = "copied_to_quarantine_fallback"

    if qpath.is_symlink() or not _safe_regular_file(qpath):
        try:
            qpath.unlink()
        except Exception:
            pass
        return {"ok": False, "error": "unsafe_quarantine_file_refused", "path": str(qpath)}
    st = qpath.stat()
    risk = classify_file(qpath, filename, source_url)
    item = {
        "schema": SCHEMA_ITEM,
        "id": item_id,
        "program": PROGRAM_ID,
        "version": VERSION,
        "status": "quarantined",
        "original_filename": filename,
        "quarantine_path": str(qpath),
        "release_destination": None,
        "source_url": source_url or "",
        "origin_domain": origin_from_url(source_url or ""),
        "detected_extension": risk["detected_extension"],
        "mime_type": guess_mime(qpath, filename),
        "detected_magic": risk.get("detected_magic", detect_file_magic(qpath)),
        "size_bytes": st.st_size,
        "sha256": sha256_file(qpath),
        "risk_level": risk["risk_level"],
        "risk_reasons": risk["risk_reasons"],
        "created_at": now_iso(),
        "released_at": None,
        "abandoned_at": None,
        "release_allowed": risk["risk_level"] != "blocked",
        "requires_user_confirmation": risk["risk_level"] in set(pol.get("require_confirmation_for", [])),
        "browser_handoff": True,
        "audit_metadata_only": True,
        "disposition": disposition,
    }
    write_item(item, pol)
    append_audit_event("register_download", item)
    append_completion_event(item, "quarantined")
    write_aegis_status_quiet()
    return {"ok": True, "item": item}

def append_audit_event(action: str, item: Dict[str, Any]) -> None:
    try:
        log_dir = state_dir() / "logs"
        mkdirp(log_dir)
        event = {
            "time": now_iso(),
            "program": PROGRAM_ID,
            "action": action,
            "item_id": item.get("id"),
            "status": item.get("status"),
            "original_filename": item.get("original_filename"),
            "origin_domain": item.get("origin_domain"),
            "risk_level": item.get("risk_level"),
            "sha256": item.get("sha256"),
            "metadata_only": True,
        }
        path = log_dir / "events.jsonl"
        if path.exists() and path.is_symlink():
            raise RuntimeError("audit log symlink refused")
        with path.open("a", encoding="utf-8") as f:
            f.write(json.dumps(event, sort_keys=True) + "\n")
        try:
            os.chmod(path, 0o600)
        except Exception:
            pass
    except Exception:
        pass

def validate_release_dir(path: Path) -> Tuple[bool, str]:
    try:
        if _path_has_symlink_component(path):
            return False, f"Release dir contains symlink component: {path}"
        resolved = path.expanduser().resolve()
    except Exception as exc:
        return False, f"Could not resolve release dir: {exc}"
    s = str(resolved)
    if s in UNSAFE_RELEASE_DIRS:
        return False, f"Release dir blocked by policy: {s}"
    # Block direct system locations while allowing user-owned subdirectories elsewhere.
    for unsafe in ["/bin/", "/sbin/", "/usr/", "/etc/", "/boot/", "/dev/", "/proc/", "/sys/", "/run/", "/var/lib/", "/var/run/"]:
        if s.startswith(unsafe):
            return False, f"Release dir under unsafe system path: {s}"
    return True, ""

def unique_destination(dest_dir: Path, filename: str) -> Path:
    dest = dest_dir / sanitize_filename(filename)
    if not dest.exists() and not dest.is_symlink():
        return dest
    stem = dest.stem
    suffix = dest.suffix
    for i in range(1, 10000):
        candidate = dest_dir / f"{stem}-{i}{suffix}"
        if not candidate.exists() and not candidate.is_symlink():
            return candidate
    raise RuntimeError("Could not find unique destination filename")

def release_item(item_id: str, release_dir: Optional[str] = None, force: bool = False) -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    item = read_item(item_id, pol)
    if item.get("status") != "quarantined":
        return {"ok": False, "error": "not_quarantined", "item": item}
    if item.get("risk_level") == "blocked" and not (force and pol.get("allow_force_release_blocked")):
        return {"ok": False, "error": "blocked_risk_release_denied", "item": item}
    qpath = Path(item["quarantine_path"])
    qroot = expand_path(pol["quarantine_dir"])
    try:
        qpath.resolve().relative_to(qroot.resolve())
    except Exception:
        return {"ok": False, "error": "quarantine_path_outside_root", "item": item}
    if qpath.is_symlink() or not _safe_regular_file(qpath):
        return {"ok": False, "error": "unsafe_quarantine_file_refused", "item": item}
    if not qpath.exists():
        return {"ok": False, "error": "quarantined_file_missing", "item": item}
    dest_dir = expand_path(release_dir or pol.get("release_dir") or str(Path.home() / "Downloads"))
    ok, msg = validate_release_dir(dest_dir)
    if not ok:
        return {"ok": False, "error": "invalid_release_dir", "message": msg}
    mkdirp(dest_dir)
    dest = unique_destination(dest_dir, item["original_filename"])
    shutil.move(str(qpath), str(dest))
    try:
        qpath.parent.rmdir()
    except Exception:
        pass
    item["status"] = "released"
    item["release_destination"] = str(dest)
    item["released_at"] = now_iso()
    write_item(item, pol)
    append_audit_event("release_download", item)
    append_completion_event(item, "released")
    write_aegis_status_quiet()
    return {"ok": True, "item": item, "released_to": str(dest)}

def abandon_item(item_id: str) -> Dict[str, Any]:
    pol = load_policy()
    item = read_item(item_id, pol)
    if item.get("status") not in ("quarantined", "kept"):
        return {"ok": False, "error": "not_abandonable", "item": item}
    qpath = Path(item["quarantine_path"])
    qroot = expand_path(pol["quarantine_dir"])
    try:
        qpath.resolve().relative_to(qroot.resolve())
    except Exception:
        return {"ok": False, "error": "quarantine_path_outside_root", "item": item}
    if qpath.exists():
        if qpath.is_symlink():
            return {"ok": False, "error": "unsafe_quarantine_symlink_refused", "item": item}
        qpath.unlink()
        try:
            qpath.parent.rmdir()
        except Exception:
            pass
    item["status"] = "abandoned"
    item["abandoned_at"] = now_iso()
    write_item(item, pol)
    append_audit_event("abandon_download", item)
    append_completion_event(item, "abandoned")
    write_aegis_status_quiet()
    return {"ok": True, "item": item}

def list_items(all_items: bool = False) -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    items = []
    for p in sorted(expand_path(pol["metadata_dir"]).glob("*.json")):
        try:
            item = json.loads(p.read_text(encoding="utf-8"))
            if all_items or item.get("status") == "quarantined":
                items.append(item)
        except Exception:
            continue
    return {
        "ok": True,
        "schema": "sentinel.download_guard.quarantine_list.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "count": len(items),
        "items": items,
    }

def set_release_dir(path_str: str) -> Dict[str, Any]:
    pol = load_policy()
    path = expand_path(path_str)
    ok, msg = validate_release_dir(path)
    if not ok:
        return {"ok": False, "error": "invalid_release_dir", "message": msg, "path": str(path)}
    mkdirp(path)
    pol["release_dir"] = str(path)
    atomic_write_json(default_policy_path(), pol)
    write_aegis_status_quiet()
    return {"ok": True, "release_dir": str(path), "policy_path": str(default_policy_path())}

def status_obj() -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    items_all = list_items(all_items=True)["items"]
    counts: Dict[str, int] = {}
    risk_counts: Dict[str, int] = {}
    for item in items_all:
        counts[item.get("status", "unknown")] = counts.get(item.get("status", "unknown"), 0) + 1
        if item.get("status") == "quarantined":
            risk = item.get("risk_level", "unknown")
            risk_counts[risk] = risk_counts.get(risk, 0) + 1
    return {
        "ok": True,
        "schema": SCHEMA_STATUS,
        "program": PROGRAM_ID,
        "version": VERSION,
        "local_first": True,
        "telemetry": False,
        "cloud_scanning": False,
        "never_auto_open_downloads": True,
        "policy_path": str(default_policy_path()),
        "quarantine_dir": pol.get("quarantine_dir"),
        "metadata_dir": pol.get("metadata_dir"),
        "release_dir": pol.get("release_dir"),
        "counts": counts,
        "quarantined_risk_counts": risk_counts,
        "browser_handoff_ready": True,
        "aegis_status_ready": True,
        "created_or_checked_at": now_iso(),
    }

def aegis_capabilities() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.aegis_capabilities.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "capabilities": {
            "download_guard_status": {
                "command": "sentinel-download-guard --status",
                "safe_without_user_confirmation": True,
            },
            "inspect_file": {
                "command": "sentinel-download-guard --inspect FILE --source-url URL",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "register_download": {
                "command": "sentinel-download-guard --register-download --file FILE --source-url URL",
                "safe_without_user_confirmation": False,
                "modifies_files": True,
                "effect": "moves or copies file into quarantine",
            },
            "list_quarantine": {
                "command": "sentinel-download-guard --list-quarantine",
                "safe_without_user_confirmation": True,
            },
            "release_download": {
                "command": "sentinel-download-guard --release ITEM_ID",
                "safe_without_user_confirmation": False,
                "modifies_files": True,
                "effect": "moves approved file out of quarantine to release destination",
            },
            "abandon_download": {
                "command": "sentinel-download-guard --abandon ITEM_ID",
                "safe_without_user_confirmation": False,
                "modifies_files": True,
                "effect": "deletes quarantined file while retaining metadata",
            },
            "set_release_dir": {
                "command": "sentinel-download-guard --set-release-dir PATH",
                "safe_without_user_confirmation": False,
            },
            "release_prompt": {
                "command": "sentinel-download-guard --release-prompt ITEM_ID",
                "safe_without_user_confirmation": False,
                "modifies_files": True,
                "effect": "opens a local user approval surface for release/abandon decisions",
            },
            "downloads_panel": {
                "command": "sentinel-download-guard --downloads-panel",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "browser_contract": {
                "command": "sentinel-download-guard --browser-contract",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "readiness_status": {
                "command": "sentinel-download-guard --readiness-status",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "browser_state": {
                "command": "sentinel-download-guard --browser-state",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "download_history": {
                "command": "sentinel-download-guard --history",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "item_preview": {
                "command": "sentinel-download-guard --item-preview ITEM_ID",
                "safe_without_user_confirmation": True,
                "modifies_files": False,
            },
            "open_released_file": {
                "command": "sentinel-download-guard --open-file ITEM_ID",
                "safe_without_user_confirmation": False,
                "modifies_files": False,
                "effect": "opens a user-approved released file via xdg-open",
            },
            "open_item_folder": {
                "command": "sentinel-download-guard --open-folder ITEM_ID",
                "safe_without_user_confirmation": False,
                "modifies_files": False,
                "effect": "opens the containing folder via xdg-open",
            },
        },
        "rules": [
            "AEGIS may advise but must not release, abandon, or set destinations without explicit user approval.",
            "No cloud scan or telemetry is used.",
            "Audit events are metadata-only and must not include file contents.",
        ],
    }

def aegis_status_obj() -> Dict[str, Any]:
    st = status_obj()
    return {
        "ok": True,
        "schema": SCHEMA_AEGIS,
        "program": PROGRAM_ID,
        "version": VERSION,
        "summary": "Sentinel Download Guard local backend active.",
        "readiness": {
            "cli_backend": True,
            "browser_handoff": True,
            "quarantine": True,
            "release_to_downloads": True,
            "custom_release_dir": True,
            "risk_classification": True,
            "hashing": True,
            "metadata_only_audit": True,
            "release_prompt": _tk_available(),
            "browser_contract_v12": True,
            "downloads_panel_json": True,
            "browser_state_json": True,
            "history_json": True,
            "item_preview_json": True,
            "open_file_folder_actions": True,
            "completion_events": True,
            "aegis_action_contract": True,
            "virus_scanner_integration": "future",
            "scan_backend": "pending",
        },
        "status": st,
        "capabilities": aegis_capabilities()["capabilities"],
        "authority_model": {
            "user_final_authority": True,
            "aegis_advisory_only": True,
            "no_autonomous_release": True,
            "no_autonomous_delete": True,
        }
    }

def write_aegis_status() -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    path = expand_path(pol.get("aegis_status_path") or str(default_aegis_status_path()))
    obj = aegis_status_obj()
    atomic_write_json(path, obj)
    return {"ok": True, "path": str(path), "status": obj}

def write_aegis_status_quiet() -> None:
    try:
        write_aegis_status()
    except Exception:
        pass

def doctor_text() -> str:
    st = status_obj()
    lines = [
        "Sentinel Download Guard Doctor",
        f"Version: {VERSION}",
        f"Policy: {st['policy_path']}",
        f"Quarantine: {st['quarantine_dir']}",
        f"Release dir: {st['release_dir']}",
        f"Telemetry: {st['telemetry']}",
        f"Cloud scanning: {st['cloud_scanning']}",
        f"Never auto-open downloads: {st['never_auto_open_downloads']}",
        f"Counts: {json.dumps(st['counts'], sort_keys=True)}",
        f"Quarantined risk counts: {json.dumps(st['quarantined_risk_counts'], sort_keys=True)}",
        "AEGIS: advisory-only, metadata-only, local-first.",
    ]
    return "\n".join(lines)


def _tk_available() -> bool:
    try:
        import tkinter  # noqa: F401
        return True
    except Exception:
        return False




def _short_text(value: str, limit: int = 90) -> str:
    value = value or ""
    if len(value) <= limit:
        return value
    return value[:limit - 1] + "…"

def release_prompt(item_id: str, test_mode: bool = False) -> Dict[str, Any]:
    pol = load_policy()
    item = read_item(item_id, pol)
    if test_mode:
        return {
            "ok": True,
            "schema": "sentinel.download_guard.release_prompt_result.v1",
            "program": PROGRAM_ID,
            "version": VERSION,
            "test_mode": True,
            "item_id": item_id,
            "would_show_popup": True,
            "title": "Sentinel Download Guard",
            "available_actions": ["release_default", "choose_folder", "keep_quarantined", "abandon"],
            "item": item,
        }
    try:
        import tkinter as tk
        from tkinter import messagebox, filedialog
    except Exception as exc:
        return {
            "ok": False,
            "error": "tk_unavailable",
            "message": str(exc),
            "hint": "Install python3-tk or use CLI release/abandon commands.",
            "item": item,
        }

    result: Dict[str, Any] = {"ok": False, "action": "cancelled", "item": item}
    root = tk.Tk()
    root.title("Sentinel Download Guard")
    root.geometry("560x430")
    root.resizable(False, False)
    root.configure(bg="#20242A")

    fg = "#E6E6E6"
    muted = "#AEB6BF"
    gold = "#E19B00"
    cyan = "#03C8FF"
    danger = "#FFB3A7"

    def label(text, size=10, color=fg, bold=False):
        font = ("Sans", size, "bold" if bold else "normal")
        w = tk.Label(root, text=text, bg="#20242A", fg=color, font=font, anchor="w", justify="left", wraplength=520)
        w.pack(fill="x", padx=18, pady=3)
        return w

    label("Sentinel Download Guard", 15, gold, True)
    label("Quarantined download requires your decision.", 10, muted)
    label("")
    label(f"File: {_short_text(item.get('original_filename', ''))}", 10, fg, True)
    label(f"Source: {_short_text(item.get('source_url', '') or item.get('origin_domain', ''))}", 9, muted)
    label(f"SHA256: {_short_text(item.get('sha256', ''), 74)}", 8, muted)
    label(f"Size: {item.get('size_bytes', 0)} bytes", 9, muted)
    risk = item.get("risk_level", "unknown")
    risk_color = danger if risk in ("dangerous", "blocked", "unknown") else (gold if risk == "caution" else cyan)
    label(f"Risk: {risk.upper()}", 11, risk_color, True)
    reasons = item.get("risk_reasons") or []
    for r in reasons[:5]:
        label(f"• {r}", 9, muted)

    label("")
    label(f"Default release folder: {pol.get('release_dir')}", 9, muted)

    btn_frame = tk.Frame(root, bg="#20242A")
    btn_frame.pack(side="bottom", fill="x", padx=14, pady=14)

    def finish(obj: Dict[str, Any]) -> None:
        nonlocal result
        result = obj
        root.destroy()

    def do_release_default():
        obj = release_item(item_id)
        if obj.get("ok"):
            messagebox.showinfo("Sentinel Download Guard", f"Released to:\n{obj.get('released_to')}")
        else:
            messagebox.showerror("Sentinel Download Guard", f"Release failed:\n{obj.get('error')}\n{obj.get('message', '')}")
        finish(obj)

    def do_choose_folder():
        folder = filedialog.askdirectory(title="Choose release folder")
        if not folder:
            return
        obj = release_item(item_id, release_dir=folder)
        if obj.get("ok"):
            messagebox.showinfo("Sentinel Download Guard", f"Released to:\n{obj.get('released_to')}")
        else:
            messagebox.showerror("Sentinel Download Guard", f"Release failed:\n{obj.get('error')}\n{obj.get('message', '')}")
        finish(obj)

    def do_keep():
        finish({"ok": True, "action": "keep_quarantined", "item": item})

    def do_abandon():
        confirm = messagebox.askyesno(
            "Abandon Download",
            "Delete the quarantined file?\n\nMetadata will remain for audit."
        )
        if confirm:
            finish(abandon_item(item_id))

    def make_button(text, command, tooltip=None):
        b = tk.Button(btn_frame, text=text, command=command, bg="#303740", fg="#F2F2F2", activebackground="#3B4652", activeforeground="#FFFFFF")
        b.pack(side="left", padx=4)
        return b

    make_button("Release to Downloads", do_release_default)
    make_button("Choose Folder…", do_choose_folder)
    make_button("Keep Quarantined", do_keep)
    make_button("Abandon", do_abandon)

    root.bind("<Escape>", lambda _e: do_keep())
    root.bind("<Return>", lambda _e: do_release_default() if item.get("risk_level") in ("normal",) else None)

    root.mainloop()
    write_aegis_status_quiet()
    return result

def aegis_action_contract_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.aegis_action_contract.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "actions": {
            "status": {
                "command": "sentinel-download-guard --status",
                "requires_user_approval": False,
                "modifies_files": False,
            },
            "inspect_file": {
                "command": "sentinel-download-guard --inspect FILE --source-url URL",
                "requires_user_approval": False,
                "modifies_files": False,
            },
            "list_quarantine": {
                "command": "sentinel-download-guard --list-quarantine",
                "requires_user_approval": False,
                "modifies_files": False,
            },
            "release_prompt": {
                "command": "sentinel-download-guard --release-prompt ITEM_ID",
                "requires_user_approval": True,
                "modifies_files": True,
                "approval_surface": "local_user_popup",
            },
            "release": {
                "command": "sentinel-download-guard --release ITEM_ID",
                "requires_user_approval": True,
                "modifies_files": True,
            },
            "open_file": {
                "command": "sentinel-download-guard --open-file ITEM_ID",
                "requires_user_approval": True,
                "modifies_files": False,
            },
            "open_folder": {
                "command": "sentinel-download-guard --open-folder ITEM_ID",
                "requires_user_approval": True,
                "modifies_files": False,
            },
            "browser_state": {
                "command": "sentinel-download-guard --browser-state",
                "requires_user_approval": False,
                "modifies_files": False,
            },
            "abandon": {
                "command": "sentinel-download-guard --abandon ITEM_ID",
                "requires_user_approval": True,
                "modifies_files": True,
            },
            "set_release_dir": {
                "command": "sentinel-download-guard --set-release-dir PATH",
                "requires_user_approval": True,
                "modifies_files": False,
            }
        },
        "aegis_rules": [
            "AEGIS may surface status and explain risks.",
            "AEGIS must not release, abandon, delete, or change destinations unless the user explicitly requests it.",
            "AEGIS must not upload files or invoke cloud scanning.",
            "AEGIS must make clear that release means user-approved movement out of quarantine, not proof of safety."
        ]
    }




def browser_state_path(pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy.get("browser_state_path") or str(state_dir() / "browser" / "download_state.json"))

def completion_events_path(pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy.get("completion_events_path") or str(state_dir() / "browser" / "completion_events.jsonl"))

def history_items(limit: Optional[int] = None) -> List[Dict[str, Any]]:
    pol = load_policy()
    ensure_dirs(pol)
    items = list_items(all_items=True).get("items", [])
    items.sort(key=lambda x: x.get("created_at", ""), reverse=True)
    if limit is None:
        try:
            limit = int(pol.get("history_limit", 500))
        except Exception:
            limit = 500
    return items[:max(1, limit)]

def latest_item_obj() -> Dict[str, Any]:
    items = history_items(1)
    return {
        "ok": True,
        "schema": "sentinel.download_guard.latest_item.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "item": items[0] if items else None,
    }

def history_obj(limit: Optional[int] = None) -> Dict[str, Any]:
    items = history_items(limit)
    counts: Dict[str, int] = {}
    risk_counts: Dict[str, int] = {}
    for item in items:
        status = item.get("status", "unknown")
        counts[status] = counts.get(status, 0) + 1
        risk = item.get("risk_level", "unknown")
        risk_counts[risk] = risk_counts.get(risk, 0) + 1
    return {
        "ok": True,
        "schema": "sentinel.download_guard.history.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "count": len(items),
        "counts": counts,
        "risk_counts": risk_counts,
        "items": items,
    }

def icon_state_from_items(items: List[Dict[str, Any]]) -> Dict[str, Any]:
    quarantined = [i for i in items if i.get("status") == "quarantined"]
    released = [i for i in items if i.get("status") == "released"]
    abandoned = [i for i in items if i.get("status") == "abandoned"]
    if quarantined:
        top = quarantined[0]
        risk = top.get("risk_level", "unknown")
        color = "red" if risk in ("blocked", "dangerous") else "yellow"
        return {
            "state": "waiting_user",
            "icon_colour": color,
            "badge": len(quarantined),
            "message": f"{len(quarantined)} download(s) awaiting user decision",
            "latest_item": top,
        }
    if released:
        top = released[0]
        return {
            "state": "released",
            "icon_colour": "yellow",
            "badge": 0,
            "message": "Latest download released",
            "latest_item": top,
        }
    return {
        "state": "idle",
        "icon_colour": "yellow",
        "badge": 0,
        "message": "No downloads awaiting decision",
        "latest_item": None,
    }


def append_completion_event(item: Dict[str, Any], event: str) -> None:
    try:
        pol = load_policy()
        path = completion_events_path(pol)
        mkdirp(path.parent)
        payload = {
            "time": now_iso(),
            "schema": "sentinel.download_guard.completion_event.v1",
            "program": PROGRAM_ID,
            "version": VERSION,
            "event": event,
            "item_id": item.get("id"),
            "status": item.get("status"),
            "original_filename": item.get("original_filename"),
            "risk_level": item.get("risk_level"),
            "release_destination": item.get("release_destination"),
            "origin_domain": item.get("origin_domain"),
            "metadata_only": True,
        }
        if path.exists() and path.is_symlink():
            raise RuntimeError("completion event symlink refused")
        with path.open("a", encoding="utf-8") as f:
            f.write(json.dumps(payload, sort_keys=True) + "\n")
        try:
            os.chmod(path, 0o600)
        except Exception:
            pass
        browser_state_obj(write=True)
    except Exception:
        pass

def completion_events_obj(limit: int = 50) -> Dict[str, Any]:
    pol = load_policy()
    path = completion_events_path(pol)
    events: List[Dict[str, Any]] = []
    if path.exists():
        try:
            lines = path.read_text(encoding="utf-8").splitlines()[-max(1, limit):]
            for line in lines:
                try:
                    events.append(json.loads(line))
                except Exception:
                    pass
        except Exception:
            pass
    return {
        "ok": True,
        "schema": "sentinel.download_guard.completion_events.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "count": len(events),
        "events": events,
    }

def open_item_folder(item_id: str) -> Dict[str, Any]:
    item = read_item(item_id)
    target = item.get("release_destination") or item.get("quarantine_path")
    if not target:
        return {"ok": False, "error": "no_path_for_item", "item": item}
    folder = str(Path(target).expanduser().parent)
    try:
        subprocess.Popen(["xdg-open", folder], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return {"ok": True, "action": "open_folder", "folder": folder, "item": item}
    except Exception as exc:
        return {"ok": False, "error": type(exc).__name__, "message": str(exc), "folder": folder, "item": item}

def open_item_file(item_id: str) -> Dict[str, Any]:
    pol = load_policy()
    if not pol.get("allow_open_released_file", True):
        return {"ok": False, "error": "open_released_file_disabled_by_policy"}
    item = read_item(item_id, pol)
    if item.get("status") != "released":
        return {
            "ok": False,
            "error": "item_not_released",
            "message": "Only released files can be opened by Download Guard.",
            "item": item,
        }
    target = item.get("release_destination")
    if not target or not Path(target).exists():
        return {"ok": False, "error": "released_file_missing", "item": item}
    if Path(target).is_symlink():
        return {"ok": False, "error": "released_file_symlink_refused", "item": item}
    try:
        subprocess.Popen(["xdg-open", target], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return {"ok": True, "action": "open_file", "path": target, "item": item}
    except Exception as exc:
        return {"ok": False, "error": type(exc).__name__, "message": str(exc), "path": target, "item": item}

def export_history(path_str: str) -> Dict[str, Any]:
    path = expand_path(path_str)
    obj = history_obj()
    atomic_write_json(path, obj)
    return {"ok": True, "path": str(path), "count": obj.get("count", 0)}

def preview_item_obj(item_id: str) -> Dict[str, Any]:
    item = read_item(item_id)
    reasons = item.get("risk_reasons", []) or []
    return {
        "ok": True,
        "schema": "sentinel.download_guard.item_preview.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "item_id": item_id,
        "title": item.get("original_filename", "download"),
        "subtitle": f"{item.get('risk_level', 'unknown').upper()} — {item.get('status', 'unknown')}",
        "detail": reasons[0] if reasons else "No risk reason recorded.",
        "source": item.get("source_url") or item.get("origin_domain") or "",
        "sha256": item.get("sha256", ""),
        "actions": {
            "release_prompt": f"sentinel-download-guard --release-prompt {item_id}",
            "open_folder": f"sentinel-download-guard --open-folder {item_id}",
            "open_file_if_released": f"sentinel-download-guard --open-file {item_id}",
            "abandon": f"sentinel-download-guard --abandon {item_id}",
        },
    }

def set_browser_active(filename: str = "") -> Dict[str, Any]:
    return browser_state_obj(write=True, event="active", active_filename=filename)

def clear_browser_state() -> Dict[str, Any]:
    obj = browser_state_obj(write=True)
    return obj


def run_self_test() -> Dict[str, Any]:
    results = []
    with tempfile.TemporaryDirectory(prefix="sentinel-dlg-selftest-") as td:
        root = Path(td)
        old_env = {k: os.environ.get(k) for k in ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"]}
        os.environ["XDG_CONFIG_HOME"] = str(root / "config")
        os.environ["XDG_DATA_HOME"] = str(root / "data")
        os.environ["XDG_STATE_HOME"] = str(root / "state")
        try:
            pol_init = init_policy()
            results.append({"check": "init_policy", "ok": pol_init.get("ok") is True})
            test_file = root / "sample.deb"
            test_file.write_text("not a real package\n", encoding="utf-8")
            ins = inspect_file(str(test_file), source_url="https://example.com/sample.deb")
            results.append({"check": "inspect_dangerous_extension", "ok": ins.get("risk_level") == "dangerous"})
            reg = register_download(str(test_file), source_url="https://example.com/sample.deb")
            results.append({"check": "register_download", "ok": reg.get("ok") is True and reg.get("item", {}).get("status") == "quarantined"})
            item_id = reg["item"]["id"]
            qlist = list_items()
            results.append({"check": "list_quarantine", "ok": qlist.get("count") == 1})
            rel = release_item(item_id)
            results.append({"check": "release_download", "ok": rel.get("ok") is True and Path(rel.get("released_to", "")).exists()})
            cap = aegis_capabilities()
            results.append({"check": "aegis_capabilities", "ok": cap.get("ok") is True and cap["capabilities"].get("register_download") is not None})
            bc = browser_contract_obj()
            results.append({"check": "browser_contract_v12", "ok": bc.get("ok") is True and bc.get("schema") == BROWSER_CONTRACT_LOCK_VERSION})
            results.append({"check": "browser_contract_dual_lane", "ok": bc.get("cancel_model", {}).get("mode") == "dual_lane"})
            ac = aegis_action_contract_obj()
            results.append({"check": "aegis_action_contract", "ok": ac.get("ok") is True and ac.get("actions", {}).get("release_prompt") is not None})
            bs = browser_state_obj(write=True)
            results.append({"check": "browser_state", "ok": bs.get("ok") is True and bs.get("schema") == "sentinel.download_guard.browser_state.v5"})
            hist0 = history_obj()
            results.append({"check": "history_schema", "ok": hist0.get("ok") is True and hist0.get("schema") == "sentinel.download_guard.history.v1"})
            test_file2 = root / "sample2.txt"
            test_file2.write_text("safe text\n", encoding="utf-8")
            reg2 = register_download(str(test_file2), source_url="https://example.com/sample2.txt")
            rp = release_prompt(reg2["item"]["id"], test_mode=True)
            results.append({"check": "release_prompt_test_mode", "ok": rp.get("ok") is True and rp.get("would_show_popup") is True})
            prev = preview_item_obj(reg2["item"]["id"])
            results.append({"check": "item_preview", "ok": prev.get("ok") is True and prev.get("schema") == "sentinel.download_guard.item_preview.v1"})
            events = completion_events_obj()
            results.append({"check": "completion_events", "ok": events.get("ok") is True and events.get("schema") == "sentinel.download_guard.completion_events.v1"})
            active = set_browser_active("sample2.txt")
            results.append({"check": "set_browser_active", "ok": active.get("ok") is True and active.get("icon", {}).get("state") in ("active", "complete", "idle")})
            panel = downloads_panel_obj()
            results.append({"check": "downloads_panel_v10", "ok": panel.get("schema") == DOWNLOADS_PANEL_LOCK_VERSION})
            poll = browser_poll_obj()
            results.append({"check": "browser_poll_v3", "ok": poll.get("schema") == BROWSER_POLL_LOCK_VERSION})
            readiness = readiness_status_obj()
            results.append({"check": "readiness_v9", "ok": readiness.get("schema") == READINESS_LOCK_VERSION})
            schemas = schema_list_obj().get("schemas", {})
            results.append({"check": "schema_list_current", "ok": schemas.get("browser_contract") == BROWSER_CONTRACT_LOCK_VERSION and schemas.get("browser_poll") == BROWSER_POLL_LOCK_VERSION and schemas.get("downloads_panel") == DOWNLOADS_PANEL_LOCK_VERSION and schemas.get("readiness") == READINESS_LOCK_VERSION})
            bct = browser_contract_test_obj()
            results.append({"check": "browser_contract_test", "ok": bct.get("ok") is True})
            st = status_obj()
            results.append({"check": "status_schema", "ok": st.get("schema") == SCHEMA_STATUS})
        except Exception as exc:
            results.append({"check": "exception", "ok": False, "error": repr(exc), "traceback": traceback.format_exc()})
        finally:
            for k, v in old_env.items():
                if v is None:
                    os.environ.pop(k, None)
                else:
                    os.environ[k] = v
    ok = all(r.get("ok") for r in results)
    return {
        "ok": ok,
        "schema": "sentinel.download_guard.self_test.v2",
        "program": PROGRAM_ID,
        "version": VERSION,
        "results": results,
    }




# ---------------------------------------------------------------------------
# v0.4.0 Browser active-download/progress-window overrides
# ---------------------------------------------------------------------------

def active_downloads_path(pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy.get("active_downloads_path") or str(state_dir() / "browser" / "active_downloads.json"))

def load_active_downloads() -> Dict[str, Any]:
    pol = load_policy()
    path = active_downloads_path(pol)
    if not path.exists():
        return {
            "schema": "sentinel.download_guard.active_downloads.v1",
            "program": PROGRAM_ID,
            "version": VERSION,
            "updated_at": now_iso(),
            "records": {},
        }
    try:
        obj = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(obj, dict):
            raise ValueError("not object")
        obj.setdefault("schema", "sentinel.download_guard.active_downloads.v1")
        obj.setdefault("program", PROGRAM_ID)
        obj.setdefault("version", VERSION)
        obj.setdefault("updated_at", now_iso())
        obj.setdefault("records", {})
        if not isinstance(obj["records"], dict):
            obj["records"] = {}
        return obj
    except Exception:
        return {
            "schema": "sentinel.download_guard.active_downloads.v1",
            "program": PROGRAM_ID,
            "version": VERSION,
            "updated_at": now_iso(),
            "records": {},
            "warning": "active downloads state was unreadable and has been reset in memory",
        }

def save_active_downloads(obj: Dict[str, Any]) -> None:
    obj["schema"] = "sentinel.download_guard.active_downloads.v1"
    obj["program"] = PROGRAM_ID
    obj["version"] = VERSION
    obj["updated_at"] = now_iso()
    atomic_write_json(active_downloads_path(), obj)
    browser_state_obj(write=True)

def _active_record(download_id: str) -> Dict[str, Any]:
    did = (download_id or "").strip()
    if not did:
        raise ValueError("download_id is required")
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    rec = records.setdefault(did, {
        "id": did,
        "status": "active",
        "filename": "download",
        "source_url": "",
        "origin_domain": "",
        "received_bytes": 0,
        "total_bytes": None,
        "percent": 0.0,
        "started_at": now_iso(),
        "updated_at": now_iso(),
        "completed_at": None,
        "item_id": None,
        "message": "Download active",
        "metadata_only": True,
    })
    return rec

def active_start(download_id: str, filename: str = "", source_url: str = "", total_bytes: Optional[int] = None) -> Dict[str, Any]:
    did = (download_id or "").strip()
    if not did:
        did = "dlg-active-" + secrets.token_hex(8)
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    rec = {
        "id": did,
        "status": "active",
        "filename": sanitize_filename(filename or "download"),
        "source_url": source_url or "",
        "origin_domain": origin_from_url(source_url or ""),
        "received_bytes": 0,
        "total_bytes": int(total_bytes) if total_bytes not in (None, "", 0) else None,
        "percent": 0.0,
        "started_at": now_iso(),
        "updated_at": now_iso(),
        "completed_at": None,
        "item_id": None,
        "message": "Download active",
        "metadata_only": True,
    }
    records[did] = rec
    save_active_downloads(obj)
    append_audit_event("active_download_started", {"id": did, "status": "active", "original_filename": rec["filename"], "origin_domain": rec["origin_domain"], "risk_level": "active", "sha256": ""})
    return {"ok": True, "schema": "sentinel.download_guard.active_start.v1", "download_id": did, "record": rec}


def active_complete(download_id: str, item_id: str = "", message: str = "") -> Dict[str, Any]:
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    did = (download_id or "").strip()
    rec = records.get(did) or _active_record(did)
    rec["status"] = "complete"
    rec["percent"] = 100.0
    rec["completed_at"] = now_iso()
    rec["updated_at"] = now_iso()
    rec["item_id"] = item_id or rec.get("item_id")
    rec["message"] = message or "Download complete; waiting for user decision"
    records[did] = rec
    save_active_downloads(obj)
    append_audit_event("active_download_completed", {"id": did, "status": "complete", "original_filename": rec.get("filename"), "origin_domain": rec.get("origin_domain"), "risk_level": "complete", "sha256": ""})
    return {"ok": True, "schema": "sentinel.download_guard.active_complete.v1", "download_id": did, "record": rec}

def active_fail(download_id: str, message: str = "") -> Dict[str, Any]:
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    did = (download_id or "").strip()
    rec = records.get(did) or _active_record(did)
    rec["status"] = "failed"
    rec["updated_at"] = now_iso()
    rec["completed_at"] = now_iso()
    rec["message"] = message or "Download failed"
    records[did] = rec
    save_active_downloads(obj)
    append_audit_event("active_download_failed", {"id": did, "status": "failed", "original_filename": rec.get("filename"), "origin_domain": rec.get("origin_domain"), "risk_level": "failed", "sha256": ""})
    return {"ok": True, "schema": "sentinel.download_guard.active_fail.v1", "download_id": did, "record": rec}

def active_clear(download_id: str = "") -> Dict[str, Any]:
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    if download_id:
        records.pop(download_id, None)
    else:
        records.clear()
    save_active_downloads(obj)
    return {"ok": True, "schema": "sentinel.download_guard.active_clear.v1", "download_id": download_id or "all", "active_count": len(records)}









# ---------------------------------------------------------------------------
# v0.4.1 active status refresh / progress-window hotfix overrides
# ---------------------------------------------------------------------------

def _parse_iso_time(value: str) -> float:
    try:
        return _dt.datetime.fromisoformat(str(value).replace("Z", "+00:00")).timestamp()
    except Exception:
        return 0.0



def active_update(download_id: str, received_bytes: Optional[int] = None, total_bytes: Optional[int] = None, percent: Optional[float] = None, message: str = "") -> Dict[str, Any]:
    obj = load_active_downloads()
    records = obj.setdefault("records", {})
    did = (download_id or "").strip()
    rec = records.get(did) or {
        "id": did or "unknown",
        "status": "active",
        "filename": "download",
        "source_url": "",
        "origin_domain": "",
        "received_bytes": 0,
        "total_bytes": None,
        "percent": 0.0,
        "started_at": now_iso(),
        "updated_at": now_iso(),
        "completed_at": None,
        "item_id": None,
        "message": "Download active",
        "metadata_only": True,
    }
    if received_bytes is not None:
        rec["received_bytes"] = max(0, int(received_bytes))
    if total_bytes not in (None, "", 0):
        rec["total_bytes"] = max(0, int(total_bytes))
    if percent is not None:
        rec["percent"] = max(0.0, min(100.0, float(percent)))
    elif rec.get("total_bytes"):
        rec["percent"] = max(0.0, min(100.0, (float(rec.get("received_bytes", 0)) / float(rec.get("total_bytes"))) * 100.0))
    if message:
        rec["message"] = message
    elif rec.get("filename"):
        rec["message"] = f"Downloading {rec.get('filename')} — {float(rec.get('percent', 0) or 0):.0f}%"
    rec["status"] = "active"
    rec["updated_at"] = now_iso()
    records[did] = rec
    save_active_downloads(obj)
    return {"ok": True, "schema": "sentinel.download_guard.active_update.v2", "download_id": did, "record": rec, "browser_state": browser_state_obj(write=True)}

def progress_window(download_id: str, test_mode: bool = False) -> Dict[str, Any]:
    if test_mode:
        return {"ok": True, "schema": "sentinel.download_guard.progress_window.v2", "program": PROGRAM_ID, "version": VERSION, "download_id": download_id, "would_open_window": True, "refresh_interval_ms": 500}
    try:
        import tkinter as tk
        from tkinter import ttk
    except Exception as exc:
        return {"ok": False, "error": "tk_unavailable", "message": str(exc), "download_id": download_id}
    root = tk.Tk()
    root.title("Sentinel Download Guard")
    root.geometry("520x220")
    root.resizable(False, False)
    root.configure(bg="#20242A")
    tk.Label(root, text="Sentinel Download Guard", bg="#20242A", fg="#E19B00", font=("Sans", 15, "bold")).pack(pady=(14, 4))
    name_var = tk.StringVar(value="Waiting for browser download state…")
    state_var = tk.StringVar(value="If this stays here, the browser is not sending active-update events.")
    detail_var = tk.StringVar(value="")
    tk.Label(root, textvariable=name_var, bg="#20242A", fg="#E6E6E6", font=("Sans", 10, "bold")).pack(pady=2)
    tk.Label(root, textvariable=state_var, bg="#20242A", fg="#AEB6BF", font=("Sans", 9)).pack(pady=2)
    tk.Label(root, textvariable=detail_var, bg="#20242A", fg="#B7F774", font=("Sans", 9)).pack(pady=2)
    bar = ttk.Progressbar(root, orient="horizontal", length=430, mode="determinate")
    bar.pack(pady=12)
    def tick():
        try:
            obj = load_active_downloads()
            rec = (obj.get("records") or {}).get(download_id, {})
            if rec:
                pct = float(rec.get("percent", 0.0) or 0.0)
                bar["value"] = max(0.0, min(100.0, pct))
                name_var.set(rec.get("filename", "download"))
                state_var.set(f"{rec.get('status','active')} — {pct:.0f}% — {rec.get('message','')}")
                detail_var.set((rec.get("source_url") or rec.get("origin_domain") or "")[:90])
                if rec.get("status") in ("complete", "failed"):
                    root.after(6000, root.destroy)
                    return
            else:
                bar["value"] = 0
                name_var.set("Waiting for browser download state…")
                state_var.set("No active record yet for " + str(download_id))
        except Exception as exc:
            state_var.set("Progress read failed: " + str(exc))
        root.after(500, tick)
    root.after(100, tick)
    root.mainloop()
    return {"ok": True, "schema": "sentinel.download_guard.progress_window_result.v2", "download_id": download_id}




# ---------------------------------------------------------------------------
# v0.4.2 downloads-panel history/listing override
# ---------------------------------------------------------------------------






# ---------------------------------------------------------------------------
# v0.4.3 current-downloads-only panel override
# ---------------------------------------------------------------------------

def current_download_records() -> Dict[str, Any]:
    try:
        active = active_downloads_obj()
    except Exception:
        active = {"ok": False, "records": [], "active_count": 0, "complete_count": 0, "failed_count": 0}
    current_active = []
    current_complete = []
    current_failed = []
    for rec in active.get("records", []) if isinstance(active, dict) else []:
        status = rec.get("status", "unknown")
        if status == "active":
            current_active.append(rec)
        elif status == "complete":
            current_complete.append(rec)
        elif status == "failed":
            current_failed.append(rec)
    waiting = list_items(all_items=False).get("items", [])
    return {
        "ok": True,
        "schema": "sentinel.download_guard.current_records.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "active": current_active,
        "complete": current_complete,
        "failed": current_failed,
        "waiting_user": waiting,
        "counts": {
            "active": len(current_active),
            "complete": len(current_complete),
            "failed": len(current_failed),
            "waiting_user": len(waiting),
        },
    }






# ---------------------------------------------------------------------------
# v0.4.4 current cancel contract override
# ---------------------------------------------------------------------------







# ---------------------------------------------------------------------------
# Current Downloads Manager / Stable Browser Contract
# ---------------------------------------------------------------------------

def cancel_requests_path(pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy.get("cancel_requests_path") or str(state_dir() / "browser" / "cancel_requests.json"))

def current_state_path(pol: Optional[Dict[str, Any]] = None) -> Path:
    policy = pol or load_policy()
    return expand_path(policy.get("current_state_path") or str(state_dir() / "browser" / "current_downloads.json"))

def load_cancel_requests() -> Dict[str, Any]:
    path = cancel_requests_path()
    if not path.exists():
        return {"schema": "sentinel.download_guard.cancel_requests.v1", "program": PROGRAM_ID, "version": VERSION, "updated_at": now_iso(), "requests": {}}
    try:
        obj = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(obj, dict):
            raise ValueError("not object")
        obj.setdefault("schema", "sentinel.download_guard.cancel_requests.v1")
        obj.setdefault("program", PROGRAM_ID)
        obj.setdefault("version", VERSION)
        obj.setdefault("updated_at", now_iso())
        obj.setdefault("requests", {})
        if not isinstance(obj["requests"], dict): obj["requests"] = {}
        return obj
    except Exception:
        return {"schema": "sentinel.download_guard.cancel_requests.v1", "program": PROGRAM_ID, "version": VERSION, "updated_at": now_iso(), "requests": {}, "warning": "cancel request state was unreadable and has been reset in memory"}

def save_cancel_requests(obj: Dict[str, Any]) -> None:
    obj["schema"] = "sentinel.download_guard.cancel_requests.v1"
    obj["program"] = PROGRAM_ID
    obj["version"] = VERSION
    obj["updated_at"] = now_iso()
    atomic_write_json(cancel_requests_path(), obj)

def cancel_request(download_id: str, filename: str = "", reason: str = "") -> Dict[str, Any]:
    did = (download_id or "").strip()
    if not did:
        return {"ok": False, "schema": "sentinel.download_guard.cancel_request.v1", "error": "missing_download_id"}
    obj = load_cancel_requests()
    reqs = obj.setdefault("requests", {})
    req = reqs.get(did, {})
    req.update({
        "id": did,
        "download_id": did,
        "filename": sanitize_filename(filename or req.get("filename") or "download"),
        "status": "requested",
        "requested": True,
        "acknowledged": False,
        "requested_at": req.get("requested_at") or now_iso(),
        "updated_at": now_iso(),
        "reason": reason or "User requested download cancellation",
        "source": "download_guard_current_manager",
        "metadata_only": True,
    })
    reqs[did] = req
    save_cancel_requests(obj)
    try:
        active = load_active_downloads()
        records = active.setdefault("records", {})
        rec = records.get(did)
        if rec:
            if filename: rec["filename"] = sanitize_filename(filename)
            rec["cancel_requested"] = True
            rec["cancel_requested_at"] = now_iso()
            rec["message"] = "Cancel requested; waiting for browser acknowledgement"
            rec["updated_at"] = now_iso()
            records[did] = rec
            save_active_downloads(active)
    except Exception:
        pass
    append_audit_event("cancel_request_queued", {"id": did, "status": "requested", "original_filename": req.get("filename"), "origin_domain": "", "risk_level": "cancel_request", "sha256": ""})
    return {"ok": True, "schema": "sentinel.download_guard.cancel_request.v1", "download_id": did, "request": req}


def cancel_ack(download_id: str, actor: str = "browser", status: str = "cancelled", message: str = "") -> Dict[str, Any]:
    did = (download_id or "").strip()
    if not did:
        return {"ok": False, "schema": "sentinel.download_guard.cancel_ack.v1", "error": "missing_download_id"}
    obj = load_cancel_requests()
    reqs = obj.setdefault("requests", {})
    req = reqs.get(did, {"id": did, "download_id": did, "filename": "download", "requested_at": now_iso(), "metadata_only": True})
    req["status"] = status or "cancelled"
    req["acknowledged"] = True
    req["acknowledged_at"] = now_iso()
    req["acknowledged_by"] = actor or "browser"
    req["updated_at"] = now_iso()
    if message: req["message"] = message
    reqs[did] = req
    save_cancel_requests(obj)
    try:
        active = load_active_downloads()
        records = active.setdefault("records", {})
        rec = records.get(did)
        if rec:
            rec["status"] = "cancelled" if status in ("cancelled", "aborted") else status
            rec["cancel_requested"] = True
            rec["cancel_acknowledged"] = True
            rec["cancel_acknowledged_at"] = now_iso()
            rec["completed_at"] = now_iso()
            rec["updated_at"] = now_iso()
            rec["message"] = message or "Download cancelled by browser"
            records[did] = rec
            save_active_downloads(active)
    except Exception:
        pass
    append_audit_event("cancel_request_acknowledged", {"id": did, "status": status, "original_filename": req.get("filename"), "origin_domain": "", "risk_level": "cancel_ack", "sha256": ""})
    return {"ok": True, "schema": "sentinel.download_guard.cancel_ack.v1", "download_id": did, "request": req}

def cancel_clear(download_id: str = "", acknowledged_only: bool = True) -> Dict[str, Any]:
    obj = load_cancel_requests()
    reqs = obj.setdefault("requests", {})
    if download_id:
        removed = 1 if reqs.pop(download_id, None) is not None else 0
    else:
        before = len(reqs)
        if acknowledged_only:
            obj["requests"] = {k: v for k, v in reqs.items() if not v.get("acknowledged")}
            removed = before - len(obj["requests"])
        else:
            reqs.clear(); removed = before
    save_cancel_requests(obj)
    return {"ok": True, "schema": "sentinel.download_guard.cancel_clear.v1", "removed": removed, "remaining": len(obj.get("requests", {}))}

def _raw_active_records() -> List[Dict[str, Any]]:
    obj = load_active_downloads()
    records = list((obj.get("records") or {}).values())
    records.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
    return records

def _classify_current_active_records() -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]:
    active_list: List[Dict[str, Any]] = []
    complete_list: List[Dict[str, Any]] = []
    failed_list: List[Dict[str, Any]] = []
    cancelled_list: List[Dict[str, Any]] = []
    cancel_reqs = {r.get("download_id") or r.get("id"): r for r in cancel_requests_obj(include_acknowledged=True).get("requests", [])}
    for rec0 in _raw_active_records():
        if not isinstance(rec0, dict): continue
        rec = dict(rec0)
        did = rec.get("id") or rec.get("download_id")
        if did in cancel_reqs:
            rec["cancel_request"] = cancel_reqs[did]
            if cancel_reqs[did].get("status") == "requested": rec["cancel_requested"] = True
        status = rec.get("status", "unknown")
        if status == "active": active_list.append(rec)
        elif status == "complete": complete_list.append(rec)
        elif status in ("cancelled", "aborted"): cancelled_list.append(rec)
        elif status == "failed": failed_list.append(rec)
    return active_list, complete_list, failed_list, cancelled_list

def review_queue_obj() -> Dict[str, Any]:
    waiting = list_items(all_items=False).get("items", [])
    entries: List[Dict[str, Any]] = []
    for item in waiting:
        if not isinstance(item, dict): continue
        item_id = item.get("id")
        entries.append({
            "item_id": item_id,
            "filename": item.get("original_filename") or item.get("suggested_filename") or item_id,
            "risk_level": item.get("risk_level", "unknown"),
            "status": item.get("status", "quarantined"),
            "created_at": item.get("created_at"),
            "origin_domain": item.get("origin_domain"),
            "actions": {
                "review_prompt": f"sentinel-download-guard --release-prompt {item_id}",
                "release": f"sentinel-download-guard --release {item_id}",
                "abandon": f"sentinel-download-guard --abandon {item_id}",
                "open_folder": f"sentinel-download-guard --open-folder {item_id}",
            }
        })
    return {"ok": True, "schema": "sentinel.download_guard.review_queue.v1", "program": PROGRAM_ID, "version": VERSION, "count": len(entries), "items": entries}

def current_state_obj(write: bool = False) -> Dict[str, Any]:
    pol = load_policy(); ensure_dirs(pol)
    active_list, complete_list, failed_list, cancelled_list = _classify_current_active_records()
    waiting = list_items(all_items=False).get("items", [])
    cancel_pending = cancel_requests_obj(include_acknowledged=False).get("requests", [])
    review_queue = review_queue_obj()
    counts = {"active": len(active_list), "complete": len(complete_list), "failed": len(failed_list), "cancelled": len(cancelled_list), "waiting_user": len(waiting), "cancel_requests": len(cancel_pending), "review_queue": review_queue.get("count", 0)}
    obj = {
        "ok": True, "schema": "sentinel.download_guard.current_state.v1", "program": PROGRAM_ID, "version": VERSION,
        "timestamp": now_iso(), "mode": "current_downloads_only", "local_first": True, "telemetry": False, "cloud_scanning": False,
        "browser_ownership": {"browser_owns_live_webkit_download": True, "download_guard_never_calls_browser_internals": True, "cancel_is_queue_only": True, "browser_polls_cancel_requests": True},
        "counts": counts, "active": active_list, "complete": complete_list, "failed": failed_list, "cancelled": cancelled_list,
        "waiting_user": waiting, "review_queue": review_queue,
        "cancel_requests": {"pending": cancel_pending, "all_command": "sentinel-download-guard --cancel-requests --all", "request_command": "sentinel-download-guard --cancel-request --download-id ID", "ack_command": "sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled"},
        "cleanup": {"clear_completed_failed_cancelled": "sentinel-download-guard --current-clear", "reset_all_current_state": "sentinel-download-guard --current-reset"},
        "history": {"listed_by_default": False, "reason": "SentinelOS rule: DLG current manager does not list old downloads by default."},
    }
    if write: atomic_write_json(current_state_path(pol), obj)
    return obj

def active_downloads_obj() -> Dict[str, Any]:
    active_list, complete_list, failed_list, cancelled_list = _classify_current_active_records()
    records = active_list + complete_list + failed_list + cancelled_list
    records.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
    return {"ok": True, "schema": "sentinel.download_guard.active_downloads.v3", "program": PROGRAM_ID, "version": VERSION, "active_count": len(active_list), "complete_count": len(complete_list), "failed_count": len(failed_list), "cancelled_count": len(cancelled_list), "cancel_request_count": len(cancel_requests_obj(include_acknowledged=False).get("requests", [])), "records": records}

def browser_state_obj(write: bool = False, event: Optional[str] = None, active_filename: str = "") -> Dict[str, Any]:
    state = current_state_obj(write=False)
    active = state.get("active", []); waiting = state.get("waiting_user", []); cancel_pending = state.get("cancel_requests", {}).get("pending", []); complete = state.get("complete", [])
    if active:
        top = active[0]
        colour = "yellow_cyan_flash" if not top.get("cancel_requested") else "yellow"
        icon = {"state": "active", "icon_colour": colour, "badge": len(active), "message": str(top.get("message") or "Download in progress"), "active_record": top}
    elif waiting or complete:
        top_item = waiting[0] if waiting else complete[0]
        icon = {"state": "complete", "icon_colour": "lime_pastel", "badge": len(waiting)+len(complete), "message": "Download ready for review", "latest_item": top_item}
    elif cancel_pending:
        icon = {"state": "cancel_requested", "icon_colour": "yellow", "badge": len(cancel_pending), "message": "Download cancellation requested", "cancel_request": cancel_pending[0]}
    else:
        icon = {"state": "idle", "icon_colour": "yellow", "badge": 0, "message": "No current downloads", "latest_item": None}
    obj = {"ok": True, "schema": "sentinel.download_guard.browser_state.v5", "program": PROGRAM_ID, "version": VERSION, "timestamp": now_iso(), "icon": icon, "current": {"command": "sentinel-download-guard --current-state", "counts": state.get("counts", {})}, "downloads_page": {"command": "sentinel-download-guard --downloads-panel", "current_only": True, "history_listed_by_default": False}, "cancel_queue": {"poll_command": "disabled_by_default_for_v1", "ack_command": "post_v1_experimental", "browser_owned": True}, "authority_model": {"browser_may_display": True, "browser_may_poll_cancel_requests": False, "browser_owns_live_download_objects": True, "download_guard_called_during_active_transfer": False, "download_guard_must_not_call_browser_internals": True, "browser_must_not_release_without_user": True, "aegis_advisory_only": True}}
    if write: atomic_write_json(browser_state_path(), obj)
    return obj

def current_clear(completed_only: bool = True) -> Dict[str, Any]:
    obj = load_active_downloads(); records = obj.setdefault("records", {}); before = len(records)
    if completed_only:
        obj["records"] = {k: v for k, v in records.items() if v.get("status") not in ("complete", "failed", "cancelled", "aborted")}
    else:
        records.clear()
    save_active_downloads(obj)
    cancel_result = cancel_clear("", acknowledged_only=completed_only)
    return {"ok": True, "schema": "sentinel.download_guard.current_clear.v1", "removed_active_records": before-len(obj.get("records", {})), "remaining_active_records": len(obj.get("records", {})), "cancel_requests": cancel_result}

def active_cancel(download_id: str, message: str = "") -> Dict[str, Any]:
    return cancel_request(download_id, reason=message or "User requested active download cancellation")




def _current_window_summary_text() -> str:
    state = current_state_obj(write=True)
    lines = ["Sentinel Download Guard — Current Downloads", "="*48, f"Version: {VERSION}", f"Updated: {state.get('timestamp')}", "", "Counts:"]
    for k, v in state.get("counts", {}).items(): lines.append(f"  {k}: {v}")
    lines += ["", "Active:"]
    for rec in state.get("active", []): lines.append(f"  {rec.get('id')}  {rec.get('filename')}  {rec.get('percent', 0):.0f}%  {rec.get('message', '')}")
    if not state.get("active"): lines.append("  none")
    lines += ["", "Waiting review:"]
    for item in state.get("review_queue", {}).get("items", []): lines.append(f"  {item.get('item_id')}  {item.get('filename')}  risk={item.get('risk_level')}")
    if not state.get("review_queue", {}).get("items", []): lines.append("  none")
    lines += ["", "Pending cancel requests:"]
    for req in state.get("cancel_requests", {}).get("pending", []): lines.append(f"  {req.get('download_id')}  {req.get('filename')}  {req.get('status')}")
    if not state.get("cancel_requests", {}).get("pending", []): lines.append("  none")
    return "\n".join(lines)





# ---------------------------------------------------------------------------
# v0.5.1 Browser Poll / HTML Panel / State Doctor phase
# ---------------------------------------------------------------------------

def current_summary_obj() -> Dict[str, Any]:
    state = current_state_obj(write=False)
    counts = state.get("counts", {})
    active = state.get("active", [])
    waiting = state.get("waiting_user", [])
    cancel_pending = state.get("cancel_requests", {}).get("pending", [])
    headline = "No current downloads"
    if active:
        first = active[0]
        headline = f"Downloading: {first.get('filename', 'download')} ({float(first.get('percent', 0) or 0):.0f}%)"
    elif waiting:
        first = waiting[0]
        headline = f"Ready for review: {first.get('original_filename') or first.get('suggested_filename') or first.get('id')}"
    elif cancel_pending:
        first = cancel_pending[0]
        headline = f"Cancel requested: {first.get('filename', first.get('download_id', 'download'))}"
    return {
        "ok": True,
        "schema": "sentinel.download_guard.current_summary.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "timestamp": now_iso(),
        "headline": headline,
        "counts": counts,
        "has_active": bool(active),
        "has_review_items": bool(waiting),
        "has_cancel_requests": bool(cancel_pending),
        "current_only": True,
        "history_listed_by_default": False,
    }


def cancel_status_obj(download_id: str = "") -> Dict[str, Any]:
    did = (download_id or "").strip()
    reqs = cancel_requests_obj(include_acknowledged=True).get("requests", [])
    if did:
        matches = [r for r in reqs if str(r.get("download_id") or r.get("id")) == did]
    else:
        matches = reqs
    return {
        "ok": True,
        "schema": "sentinel.download_guard.cancel_status.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "download_id": did or None,
        "count": len(matches),
        "requests": matches,
    }

def _state_file_status(path: Path) -> Dict[str, Any]:
    status: Dict[str, Any] = {
        "path": str(path),
        "exists": path.exists(),
        "readable": False,
        "json_ok": False,
        "size_bytes": 0,
        "kind": "missing",
    }
    try:
        if path.exists():
            if path.is_dir():
                status["kind"] = "directory"
                status["readable"] = True
                status["json_ok"] = True
                status["child_count"] = len(list(path.iterdir()))
                return status
            status["kind"] = "file"
            status["size_bytes"] = path.stat().st_size
            data = path.read_text(encoding="utf-8")
            status["readable"] = True
            if data.strip():
                json.loads(data)
            status["json_ok"] = True
    except Exception as exc:
        status["error"] = str(exc)
    return status

def state_doctor_obj(repair: bool = False) -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    paths = {
        "active_downloads": active_downloads_path(pol),
        "cancel_requests": cancel_requests_path(pol),
        "browser_state": browser_state_path(pol),
        "current_state": current_state_path(pol),
        "metadata_dir": expand_path(pol.get("metadata_dir", str(default_metadata_dir()))),
        "quarantine_dir": expand_path(pol.get("quarantine_dir", str(default_quarantine_dir()))),
    }
    statuses = {name: _state_file_status(path) for name, path in paths.items()}
    repairs = []
    if repair:
        for name, stat in statuses.items():
            path = Path(stat["path"])
            if stat.get("exists") and stat.get("kind") == "file" and not stat.get("json_ok"):
                backup = path.with_suffix(path.suffix + ".bad." + str(int(datetime.now(timezone.utc).timestamp())))
                try:
                    path.rename(backup)
                    repairs.append({"name": name, "action": "renamed_corrupt_state", "backup": str(backup)})
                except Exception as exc:
                    repairs.append({"name": name, "action": "repair_failed", "error": str(exc)})
        # Recreate the current top-level state snapshots after repair.
        try:
            current_state_obj(write=True)
            browser_state_obj(write=True)
            repairs.append({"name": "snapshots", "action": "rewritten"})
        except Exception as exc:
            repairs.append({"name": "snapshots", "action": "rewrite_failed", "error": str(exc)})
    ok = all((not s.get("exists")) or s.get("json_ok") for s in statuses.values())
    return {
        "ok": ok,
        "schema": "sentinel.download_guard.state_doctor.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "repair_requested": repair,
        "statuses": statuses,
        "repairs": repairs,
        "notes": [
            "Missing current-state files are acceptable; DLG recreates them.",
            "Corrupt JSON can be repaired by renaming the bad file and rewriting state snapshots.",
            "No old download history is listed by default.",
        ],
    }

def _html_escape(value: Any) -> str:
    return html.escape(str(value if value is not None else ""))

def current_panel_html_text() -> str:
    state = current_state_obj(write=True)
    counts = state.get("counts", {})
    active = state.get("active", [])
    review_items = state.get("review_queue", {}).get("items", [])
    cancel_pending = state.get("cancel_requests", {}).get("pending", [])
    rows = []
    rows.append("<!doctype html><html><head><meta charset='utf-8'>")
    rows.append("<title>Sentinel Download Guard — Current Downloads</title>")
    rows.append("""<style>
body{font-family:system-ui,sans-serif;background:#101418;color:#E6EAF0;margin:24px;}
.card{border:1px solid #2A3440;border-radius:12px;padding:14px;margin:12px 0;background:#151B22;}
h1,h2{color:#E19B00;} .cyan{color:#03C8FF;} .lime{color:#B7F774;}
.badge{display:inline-block;padding:2px 8px;border:1px solid #03C8FF;border-radius:999px;margin-left:6px;}
code{color:#B7F774;background:#0C1015;padding:2px 5px;border-radius:5px;}
.small{color:#AEB6BF;font-size:0.9em;}
</style></head><body>""")
    rows.append("<h1>Sentinel Download Guard — Current Downloads</h1>")
    rows.append("<p class='small'>Current downloads only. Old download history is intentionally not listed by default.</p>")
    rows.append("<div class='card'><h2>Summary</h2>")
    for key in ("active", "waiting_user", "cancel_requests", "complete", "failed", "cancelled"):
        rows.append(f"<p><b>{_html_escape(key)}:</b> {_html_escape(counts.get(key, 0))}</p>")
    rows.append("</div>")

    rows.append("<div class='card'><h2>Active Downloads</h2>")
    if active:
        for rec in active:
            did = rec.get("id") or rec.get("download_id") or ""
            rows.append(f"<h3>{_html_escape(rec.get('filename','download'))} <span class='badge'>{float(rec.get('percent',0) or 0):.0f}%</span></h3>")
            rows.append(f"<p><b>ID:</b> <code>{_html_escape(did)}</code></p>")
            rows.append(f"<p><b>Source:</b> {_html_escape(rec.get('source_url') or rec.get('origin_domain') or '')}</p>")
            rows.append(f"<p><b>Cancel request command:</b> <code>sentinel-download-guard --cancel-request --download-id {_html_escape(did)} --filename {_html_escape(rec.get('filename','download'))}</code></p>")
    else:
        rows.append("<p>No active downloads.</p>")
    rows.append("</div>")

    rows.append("<div class='card'><h2>Waiting Review</h2>")
    if review_items:
        for item in review_items:
            rows.append(f"<h3>{_html_escape(item.get('filename'))} <span class='badge'>{_html_escape(item.get('risk_level'))}</span></h3>")
            rows.append(f"<p><b>Item:</b> <code>{_html_escape(item.get('item_id'))}</code></p>")
            rows.append(f"<p><b>Review:</b> <code>sentinel-download-guard --release-prompt {_html_escape(item.get('item_id'))}</code></p>")
            rows.append(f"<p><b>Abandon:</b> <code>sentinel-download-guard --abandon {_html_escape(item.get('item_id'))}</code></p>")
    else:
        rows.append("<p>No quarantined downloads waiting for review.</p>")
    rows.append("</div>")

    rows.append("<div class='card'><h2>Pending Cancel Requests</h2>")
    if cancel_pending:
        for req in cancel_pending:
            rows.append(f"<p><b>{_html_escape(req.get('filename'))}</b> — <code>{_html_escape(req.get('download_id'))}</code> — {_html_escape(req.get('status'))}</p>")
    else:
        rows.append("<p>No pending cancel requests.</p>")
    rows.append("</div>")

    rows.append("<div class='card'><h2>Browser Integration</h2>")
    rows.append("<p><code>sentinel-download-guard --browser-poll</code></p>")
    rows.append("<p><code>sentinel-download-guard --cancel-requests</code></p>")
    rows.append("<p><code>sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled</code></p>")
    rows.append("</div></body></html>")
    return "\n".join(rows)

def current_panel_html_obj(output: str = "") -> Dict[str, Any]:
    html_text = current_panel_html_text()
    out = (output or "").strip()
    if out:
        path = expand_path(out)
    else:
        path = state_dir() / "browser" / "sentinel-downloads-current.html"
    _safe_atomic_write_text(path, html_text)
    return {
        "ok": True,
        "schema": "sentinel.download_guard.current_panel_html.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "path": str(path),
        "bytes": len(html_text.encode("utf-8")),
        "current_only": True,
        "history_listed_by_default": False,
    }







# ---------------------------------------------------------------------------
# v0.6.0 Standalone Current Downloads GUI
# ---------------------------------------------------------------------------

def gui_theme() -> Dict[str, str]:
    return {
        "bg": "#101418",
        "panel": "#151B22",
        "panel2": "#1B232D",
        "fg": "#E6EAF0",
        "muted": "#AEB6BF",
        "gold": "#E19B00",
        "cyan": "#03C8FF",
        "lime": "#B7F774",
        "danger": "#FFB3A7",
    }

def _format_current_entry(entry: Dict[str, Any], kind: str) -> str:
    if kind == "active":
        did = entry.get("id") or entry.get("download_id") or ""
        pct = float(entry.get("percent", 0) or 0)
        filename = entry.get("filename") or "download"
        cancel = " cancel-requested" if entry.get("cancel_requested") else ""
        return f"[ACTIVE {pct:3.0f}%]{cancel} {filename}  :: {did}"
    if kind == "review":
        item_id = entry.get("item_id") or entry.get("id") or ""
        filename = entry.get("filename") or entry.get("original_filename") or entry.get("suggested_filename") or "download"
        risk = entry.get("risk_level", "unknown")
        return f"[REVIEW {risk}] {filename}  :: {item_id}"
    if kind == "cancel":
        did = entry.get("download_id") or entry.get("id") or ""
        filename = entry.get("filename") or "download"
        status = entry.get("status") or "requested"
        return f"[CANCEL {status}] {filename}  :: {did}"
    filename = entry.get("filename") or entry.get("original_filename") or entry.get("suggested_filename") or "download"
    did = entry.get("id") or entry.get("download_id") or entry.get("item_id") or ""
    return f"[{kind.upper()}] {filename}  :: {did}"

def current_gui_model() -> Dict[str, Any]:
    state = current_state_obj(write=True)
    rows: List[Dict[str, Any]] = []
    for rec in state.get("active", []):
        rows.append({"kind": "active", "id": rec.get("id") or rec.get("download_id"), "label": _format_current_entry(rec, "active"), "record": rec})
    for item in state.get("review_queue", {}).get("items", []):
        rows.append({"kind": "review", "id": item.get("item_id") or item.get("id"), "label": _format_current_entry(item, "review"), "record": item})
    for req in state.get("cancel_requests", {}).get("pending", []):
        rows.append({"kind": "cancel", "id": req.get("download_id") or req.get("id"), "label": _format_current_entry(req, "cancel"), "record": req})
    for kind in ("complete", "failed", "cancelled"):
        for rec in state.get(kind, []):
            rows.append({"kind": kind, "id": rec.get("id") or rec.get("download_id"), "label": _format_current_entry(rec, kind), "record": rec})
    return {
        "ok": True,
        "schema": "sentinel.download_guard.current_gui_model.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "timestamp": now_iso(),
        "counts": state.get("counts", {}),
        "rows": rows,
        "current_only": True,
        "history_listed_by_default": False,
    }

def current_gui_model_obj() -> Dict[str, Any]:
    return current_gui_model()

def open_current_panel_html() -> Dict[str, Any]:
    obj = current_panel_html_obj("")
    path = obj.get("path")
    if not path:
        return {"ok": False, "schema": "sentinel.download_guard.open_current_panel_html.v1", "error": "missing_path"}
    try:
        import webbrowser
        webbrowser.open(Path(path).as_uri())
        return {"ok": True, "schema": "sentinel.download_guard.open_current_panel_html.v1", "path": path}
    except Exception as exc:
        return {"ok": False, "schema": "sentinel.download_guard.open_current_panel_html.v1", "path": path, "error": str(exc)}

def current_window(test_mode: bool = False) -> Dict[str, Any]:
    if test_mode:
        model = current_gui_model()
        return {
            "ok": True,
            "schema": "sentinel.download_guard.current_window.v2",
            "program": PROGRAM_ID,
            "version": VERSION,
            "would_open_window": True,
            "gui_model_schema": model.get("schema"),
            "row_count": len(model.get("rows", [])),
            "queue_only_cancel": True,
            "current_only": True,
        }

    try:
        import tkinter as tk
        from tkinter import messagebox
    except Exception as exc:
        return {"ok": False, "schema": "sentinel.download_guard.current_window.v2", "error": "tk_unavailable", "message": str(exc)}

    colors = gui_theme()
    root = tk.Tk()
    root.title("Sentinel Download Guard — Current Downloads")
    root.geometry("980x680")
    root.configure(bg=colors["bg"])

    selected_row: Dict[str, Any] = {"row": None}

    header = tk.Frame(root, bg=colors["bg"])
    header.pack(fill="x", padx=14, pady=(12, 6))
    tk.Label(header, text="Sentinel Download Guard", bg=colors["bg"], fg=colors["gold"], font=("Sans", 18, "bold")).pack(anchor="w")
    tk.Label(header, text="Current downloads only — old history is hidden by default", bg=colors["bg"], fg=colors["muted"], font=("Sans", 10)).pack(anchor="w")

    summary_var = tk.StringVar(value="Loading current downloads…")
    tk.Label(root, textvariable=summary_var, bg=colors["panel"], fg=colors["fg"], anchor="w", padx=10, pady=8).pack(fill="x", padx=14, pady=(0, 8))

    main = tk.Frame(root, bg=colors["bg"])
    main.pack(fill="both", expand=True, padx=14, pady=6)

    left = tk.Frame(main, bg=colors["panel"])
    left.pack(side="left", fill="both", expand=True, padx=(0, 8))
    right = tk.Frame(main, bg=colors["panel"])
    right.pack(side="right", fill="both", expand=True, padx=(8, 0))

    tk.Label(left, text="Current items", bg=colors["panel"], fg=colors["cyan"], font=("Sans", 12, "bold")).pack(anchor="w", padx=10, pady=(10, 4))
    listbox = tk.Listbox(left, bg=colors["panel2"], fg=colors["fg"], selectbackground=colors["cyan"], selectforeground="#061018", activestyle="none", height=22)
    listbox.pack(fill="both", expand=True, padx=10, pady=(0, 10))

    tk.Label(right, text="Details", bg=colors["panel"], fg=colors["cyan"], font=("Sans", 12, "bold")).pack(anchor="w", padx=10, pady=(10, 4))
    detail = tk.Text(right, bg=colors["panel2"], fg=colors["fg"], insertbackground=colors["fg"], wrap="word", height=22)
    detail.pack(fill="both", expand=True, padx=10, pady=(0, 10))

    rows_cache: List[Dict[str, Any]] = []

    def set_details(row: Optional[Dict[str, Any]]) -> None:
        selected_row["row"] = row
        detail.delete("1.0", "end")
        if not row:
            detail.insert("1.0", "Select a current item to view details.")
            return
        detail.insert("1.0", json.dumps(row.get("record", {}), indent=2, sort_keys=True))

    def refresh() -> None:
        nonlocal rows_cache
        try:
            model = current_gui_model()
            rows_cache = model.get("rows", [])
            counts = model.get("counts", {})
            summary_var.set(
                "Active: {active}   Review: {waiting_user}   Cancel requests: {cancel_requests}   Complete: {complete}   Failed: {failed}   Cancelled: {cancelled}".format(
                    active=counts.get("active", 0),
                    waiting_user=counts.get("waiting_user", 0),
                    cancel_requests=counts.get("cancel_requests", 0),
                    complete=counts.get("complete", 0),
                    failed=counts.get("failed", 0),
                    cancelled=counts.get("cancelled", 0),
                )
            )
            listbox.delete(0, "end")
            for row in rows_cache:
                listbox.insert("end", row.get("label", "current item"))
            if not rows_cache:
                listbox.insert("end", "No current downloads.")
            set_details(None)
        except Exception as exc:
            summary_var.set("Refresh failed")
            detail.delete("1.0", "end")
            detail.insert("1.0", str(exc))

    def on_select(_event=None) -> None:
        idxs = listbox.curselection()
        if not idxs:
            set_details(None)
            return
        idx = idxs[0]
        set_details(rows_cache[idx] if idx < len(rows_cache) else None)

    def selected() -> Optional[Dict[str, Any]]:
        idxs = listbox.curselection()
        if not idxs:
            return None
        idx = idxs[0]
        return rows_cache[idx] if idx < len(rows_cache) else None

    def request_cancel_selected() -> None:
        row = selected()
        if not row:
            messagebox.showwarning("Sentinel DLG", "Select an active download first.")
            return
        if row.get("kind") != "active":
            messagebox.showwarning("Sentinel DLG", "Cancel request is for active downloads only.")
            return
        rec = row.get("record", {})
        did = str(rec.get("id") or rec.get("download_id") or "")
        filename = str(rec.get("filename") or "download")
        if not did:
            messagebox.showerror("Sentinel DLG", "Selected download has no download ID.")
            return
        obj = instant_cancel_request(did, filename=filename, source="gui", reason="Immediate cancellation requested from DLG GUI")
        refresh()
        if obj.get("ok"):
            messagebox.showinfo("Sentinel DLG", "Cancel request queued. Browser owns the live cancellation.")
        else:
            messagebox.showerror("Sentinel DLG", "Cancel request failed: " + str(obj.get("error")))

    def review_selected() -> None:
        row = selected()
        if not row:
            messagebox.showwarning("Sentinel DLG", "Select an item waiting for review first.")
            return
        if row.get("kind") != "review":
            messagebox.showwarning("Sentinel DLG", "Review applies to quarantined items waiting for review.")
            return
        item_id = str(row.get("id") or "")
        if not item_id:
            messagebox.showerror("Sentinel DLG", "Selected item has no item ID.")
            return
        obj = release_prompt(item_id)
        refresh()
        if not obj.get("ok"):
            messagebox.showerror("Sentinel DLG", "Review failed: " + str(obj.get("error") or obj.get("message")))

    def abandon_selected() -> None:
        row = selected()
        if not row:
            messagebox.showwarning("Sentinel DLG", "Select a review item first.")
            return
        if row.get("kind") != "review":
            messagebox.showwarning("Sentinel DLG", "Abandon applies to quarantined review items.")
            return
        item_id = str(row.get("id") or "")
        if not item_id:
            messagebox.showerror("Sentinel DLG", "Selected item has no item ID.")
            return
        if not messagebox.askyesno("Sentinel DLG", "Abandon this quarantined item? It will not be released."):
            return
        obj = abandon_item(item_id)
        refresh()
        if obj.get("ok"):
            messagebox.showinfo("Sentinel DLG", "Item abandoned.")
        else:
            messagebox.showerror("Sentinel DLG", "Abandon failed: " + str(obj.get("error")))

    def clear_completed() -> None:
        obj = current_clear(completed_only=True)
        refresh()
        messagebox.showinfo("Sentinel DLG", "Completed/failed/cancelled current state cleared.\nRemoved: " + str(obj.get("removed_active_records", 0)))

    def repair_state() -> None:
        obj = state_doctor_obj(repair=True)
        refresh()
        messagebox.showinfo("Sentinel DLG", "State doctor completed.\nOK: " + str(obj.get("ok")))

    buttons = tk.Frame(root, bg=colors["bg"])
    buttons.pack(fill="x", padx=14, pady=(4, 12))

    def btn(label: str, cmd: Any, accent: str = "cyan") -> tk.Button:
        fg = colors["cyan"] if accent == "cyan" else colors["gold"] if accent == "gold" else colors["danger"] if accent == "danger" else colors["lime"]
        return tk.Button(buttons, text=label, command=cmd, bg=colors["panel2"], fg=fg, activebackground=colors["panel"], activeforeground=fg, relief="groove")

    btn("Refresh", refresh, "cyan").pack(side="left", padx=4)
    btn("Request Cancel", request_cancel_selected, "gold").pack(side="left", padx=4)
    btn("Review / Release", review_selected, "lime").pack(side="left", padx=4)
    btn("Abandon", abandon_selected, "danger").pack(side="left", padx=4)
    btn("Clear Completed", clear_completed, "gold").pack(side="left", padx=4)
    btn("Repair State", repair_state, "cyan").pack(side="left", padx=4)
    btn("Open HTML Panel", lambda: open_current_panel_html(), "cyan").pack(side="left", padx=4)
    btn("Close", root.destroy, "gold").pack(side="right", padx=4)

    listbox.bind("<<ListboxSelect>>", on_select)
    refresh()
    root.mainloop()
    return {"ok": True, "schema": "sentinel.download_guard.current_window_result.v2"}







# ---------------------------------------------------------------------------
# v1.0.0 Browser Contract / Lifecycle Hardening Lock
# ---------------------------------------------------------------------------

BROWSER_CONTRACT_LOCK_VERSION = "sentinel.download_guard.browser_contract.v12"
BROWSER_POLL_LOCK_VERSION = "sentinel.download_guard.browser_poll.v3"
DOWNLOADS_PANEL_LOCK_VERSION = "sentinel.download_guard.downloads_panel.v10"
READINESS_LOCK_VERSION = "sentinel.download_guard.readiness.v9"

def browser_timeout_policy_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.browser_timeout_policy.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "polling": {
            "browser_poll_min_interval_ms": 1500,
            "browser_poll_recommended_interval_ms": 2500,
            "cancel_requests_min_interval_ms": 1500,
            "cancel_requests_recommended_interval_ms": 2500,
            "active_update_min_interval_ms": 2000,
            "active_update_recommended_interval_ms": 2500,
            "current_page_refresh_min_interval_ms": 2000,
        },
        "timeouts": {
            "browser_poll_timeout_ms": 1200,
            "cancel_requests_timeout_ms": 1200,
            "active_update_timeout_ms": 1200,
            "cancel_ack_timeout_ms": 1200,
            "release_prompt_timeout_ms": 12000,
        },
        "forbidden_browser_paths": [
            "Do not call DLG subprocesses from GTK flashing timers.",
            "Do not call DLG subprocesses from WebKit navigation-policy callbacks except explicit non-download user actions.",
            "Do not let DLG directly call Browser/WebKit internals.",
            "Do not synchronously cancel WebKit Download objects from DLG page links.",
            "Do not poll DLG at sub-second frequency.",
        ],
        "allowed_browser_paths": [
            "Call --browser-poll from a low-frequency browser-owned timer.",
            "Call --cancel-requests from a low-frequency browser-owned timer.",
            "Call --cancel-ack after the browser has cancelled its own WebKit Download object.",
            "Call --active-update with fire-and-forget or worker/thread isolation.",
            "Call --downloads-panel only when the user opens the DLG page.",
        ],
    }

def schema_list_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.schema_list.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "stable_lock": True,
        "schemas": {
            "browser_contract": BROWSER_CONTRACT_LOCK_VERSION,
            "browser_poll": BROWSER_POLL_LOCK_VERSION,
            "downloads_panel": DOWNLOADS_PANEL_LOCK_VERSION,
            "readiness": READINESS_LOCK_VERSION,
            "timeout_policy": "sentinel.download_guard.browser_timeout_policy.v1",
            "cancel_requests": "sentinel.download_guard.cancel_requests.v1",
            "cancel_request": "sentinel.download_guard.cancel_request.v1",
            "cancel_ack": "sentinel.download_guard.cancel_ack.v1",
            "current_state": "sentinel.download_guard.current_state.v1",
            "current_summary": "sentinel.download_guard.current_summary.v1",
            "current_gui_model": "sentinel.download_guard.current_gui_model.v1",
            "current_panel_html": "sentinel.download_guard.current_panel_html.v1",
            "state_doctor": "sentinel.download_guard.state_doctor.v1",
        },
        "compatibility": {
            "minimum_browser_target": "sentinel-browser v1.0.0+",
            "known_stable_browser_rollback": "sentinel-browser v0.10.10",
            "direct_cancel_browser_versions_to_avoid": ["v0.10.11", "v0.10.12", "v0.10.13"],
        },
    }


def browser_integration_guide_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.browser_integration_guide.v2",
        "program": PROGRAM_ID,
        "version": VERSION,
        "goal": "Browser-first download UX: Browser owns live WebKit transfer/progress/cancel, Download Guard imports only completed files for quarantine/review/release.",
        "sequence": [
            {
                "step": 1,
                "name": "active_transfer",
                "browser_action": "Browser downloads to its local handoff/staging path and owns progress/cancel UI.",
                "dlg_command": "none during active transfer",
                "notes": "No Download Guard subprocess call should run in the live WebKit transfer hot path."
            },
            {
                "step": 2,
                "name": "completion_handoff",
                "browser_action": "After WebKit finished, Browser starts a background worker to hand the completed file to Download Guard.",
                "dlg_command": "sentinel-download-guard --import-completed-download --file FILE --source-url URL --suggested-filename NAME",
                "notes": "Alias of --browser-handoff/--register-download; this moves or copies the completed file into DLG quarantine."
            },
            {
                "step": 3,
                "name": "review_release_delete",
                "browser_action": "Browser shows a Downloads button/page pointing to quarantined items.",
                "dlg_command": "sentinel-download-guard --downloads-panel / --release-prompt ITEM_ID",
                "notes": "Files do not move to ~/Downloads until explicit user release."
            },
            {
                "step": 4,
                "name": "optional_post_v1_live_cancel",
                "browser_action": "Disabled by default for v1; Browser's own cancel button handles active downloads.",
                "dlg_command": "--cancel-request/--cancel-requests are retained for future experimental integration only.",
                "notes": "This avoids freezing Browser during active transfers."
            },
        ],
        "authority_model": {
            "browser_owns_live_download_objects": True,
            "download_guard_called_during_active_transfer": False,
            "download_guard_owns_completed_file_review": True,
            "aegis_advisory_only": True,
        },
        "do_not": browser_timeout_policy_obj().get("forbidden_browser_paths", []),
    }


def browser_contract_test_obj() -> Dict[str, Any]:
    schemas = schema_list_obj().get("schemas", {})
    timeout_policy = browser_timeout_policy_obj()
    guide = browser_integration_guide_obj()
    panel = downloads_panel_obj()
    poll = browser_poll_obj()
    readiness = readiness_status_obj()
    contract = browser_contract_obj()
    cancel_model = contract.get("cancel_model", {})
    dlg_gui_end = cancel_model.get("dlg_gui_end", {}) if isinstance(cancel_model, dict) else {}
    browser_end = cancel_model.get("browser_end", {}) if isinstance(cancel_model, dict) else {}
    checks = [
        {"name": "browser_contract_locked", "ok": BROWSER_CONTRACT_LOCK_VERSION.endswith(".v12")},
        {"name": "browser_contract_schema", "ok": contract.get("schema") == BROWSER_CONTRACT_LOCK_VERSION},
        {"name": "browser_poll_schema", "ok": poll.get("schema") == BROWSER_POLL_LOCK_VERSION},
        {"name": "downloads_panel_schema", "ok": panel.get("schema") == DOWNLOADS_PANEL_LOCK_VERSION},
        {"name": "readiness_schema", "ok": readiness.get("schema") == READINESS_LOCK_VERSION},
        {"name": "dual_lane_cancel_model", "ok": cancel_model.get("mode") == "dual_lane"},
        {"name": "browser_end_instant_cancel", "ok": browser_end.get("instant") is True},
        {"name": "urgent_cancel_poll", "ok": dlg_gui_end.get("poll_command") == "sentinel-download-guard --cancel-requests --urgent"},
        {"name": "browser_owns_webkit", "ok": cancel_model.get("browser_owns_live_webkit_download") is True},
        {"name": "no_direct_browser_control", "ok": cancel_model.get("download_guard_never_controls_browser_directly") is True and cancel_model.get("direct_cancel_from_dlg_page") is False},
        {"name": "history_hidden", "ok": contract.get("history_listed_by_default") is False},
        {"name": "timeout_policy_present", "ok": bool(timeout_policy.get("timeouts")) and bool(timeout_policy.get("polling"))},
        {"name": "integration_guide_steps", "ok": len(guide.get("sequence", [])) >= 4},
        {"name": "schema_list_present", "ok": schemas.get("browser_contract") == BROWSER_CONTRACT_LOCK_VERSION and schemas.get("browser_poll") == BROWSER_POLL_LOCK_VERSION and schemas.get("downloads_panel") == DOWNLOADS_PANEL_LOCK_VERSION and schemas.get("readiness") == READINESS_LOCK_VERSION},
    ]
    return {
        "ok": all(c.get("ok") for c in checks),
        "schema": "sentinel.download_guard.browser_contract_test.v2",
        "program": PROGRAM_ID,
        "version": VERSION,
        "contract": BROWSER_CONTRACT_LOCK_VERSION,
        "checks": checks,
    }








# ---------------------------------------------------------------------------
# v1.0.0 Immediate Cancel Contract
# ---------------------------------------------------------------------------

IMMEDIATE_CANCEL_SCHEMA = "sentinel.download_guard.immediate_cancel.v1"
CANCEL_QUEUE_SCHEMA_V2 = "sentinel.download_guard.cancel_requests.v2"

def immediate_cancel_policy_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.immediate_cancel_policy.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "model": "dual_lane",
        "lanes": {
            "browser_end": {
                "owner": "browser",
                "behaviour": "immediate",
                "rule": "Browser cancel button/menu may cancel its own live WebKit Download object directly, then ack DLG.",
                "ack_command": "sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled",
            },
            "dlg_gui_end": {
                "owner": "dlg_queue",
                "behaviour": "urgent_queue",
                "rule": "DLG GUI queues an urgent immediate-cancel request. Browser polls urgent queue while active downloads exist and cancels its own WebKit object.",
                "request_command": "sentinel-download-guard --instant-cancel --download-id ID --filename NAME --source gui",
                "poll_command": "sentinel-download-guard --cancel-requests --urgent",
            },
        },
        "browser_polling": {
            "when_no_active_downloads_ms": 2500,
            "when_active_downloads_ms": 750,
            "urgent_cancel_poll_ms": 300,
            "maximum_urgent_poll_window_seconds": 120,
            "notes": [
                "Urgent cancel polling is only for active downloads.",
                "Do not poll urgent cancellation from GTK flash timers.",
                "Use browser-owned safe timer/worker path only."
            ],
        },
        "forbidden": [
            "DLG must not call browser internals directly.",
            "DLG page links must not synchronously call WebKit Download.cancel().",
            "Browser must not block UI while calling DLG.",
        ],
    }

def instant_cancel_request(download_id: str, filename: str = "", source: str = "gui", reason: str = "") -> Dict[str, Any]:
    did = (download_id or "").strip()
    if not did:
        return {"ok": False, "schema": IMMEDIATE_CANCEL_SCHEMA, "error": "missing_download_id"}
    obj = load_cancel_requests()
    obj["schema"] = CANCEL_QUEUE_SCHEMA_V2
    reqs = obj.setdefault("requests", {})
    req = reqs.get(did, {})
    req.update({
        "id": did,
        "download_id": did,
        "filename": sanitize_filename(filename or req.get("filename") or "download"),
        "status": "urgent_requested",
        "requested": True,
        "urgent": True,
        "immediate": True,
        "acknowledged": False,
        "requested_at": req.get("requested_at") or now_iso(),
        "updated_at": now_iso(),
        "reason": reason or "Immediate download cancellation requested",
        "source": source or "gui",
        "metadata_only": True,
        "browser_action_required": "cancel_own_webkit_download_object",
        "browser_ack_required": True,
    })
    reqs[did] = req
    save_cancel_requests(obj)

    try:
        active = load_active_downloads()
        records = active.setdefault("records", {})
        rec = records.get(did)
        if rec:
            rec["cancel_requested"] = True
            rec["urgent_cancel_requested"] = True
            rec["cancel_requested_at"] = now_iso()
            rec["message"] = "Immediate cancel requested; waiting for browser acknowledgement"
            rec["updated_at"] = now_iso()
            records[did] = rec
            save_active_downloads(active)
    except Exception:
        pass

    append_audit_event("immediate_cancel_requested", {
        "id": did,
        "status": "urgent_requested",
        "original_filename": req.get("filename"),
        "origin_domain": "",
        "risk_level": "immediate_cancel",
        "sha256": ""
    })
    return {
        "ok": True,
        "schema": IMMEDIATE_CANCEL_SCHEMA,
        "download_id": did,
        "request": req,
        "browser_must_ack": True,
    }

def urgent_cancel_requests_obj() -> Dict[str, Any]:
    all_reqs = cancel_requests_obj(include_acknowledged=False).get("requests", [])
    urgent = [r for r in all_reqs if r.get("urgent") or r.get("immediate") or r.get("status") == "urgent_requested"]
    urgent.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
    return {
        "ok": True,
        "schema": "sentinel.download_guard.urgent_cancel_requests.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "count": len(urgent),
        "requests": urgent,
        "browser_rule": "Cancel own live WebKit object, then ack with --cancel-ack.",
        "ack_command": "sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled",
    }

def cancel_requests_obj(include_acknowledged: bool = False, urgent_only: bool = False) -> Dict[str, Any]:
    obj = load_cancel_requests()
    requests = list((obj.get("requests") or {}).values())
    if not include_acknowledged:
        requests = [r for r in requests if not r.get("acknowledged")]
    if urgent_only:
        requests = [r for r in requests if r.get("urgent") or r.get("immediate") or r.get("status") == "urgent_requested"]
    requests.sort(key=lambda r: (bool(r.get("urgent")), r.get("updated_at", "")), reverse=True)
    return {
        "ok": True,
        "schema": CANCEL_QUEUE_SCHEMA_V2,
        "program": PROGRAM_ID,
        "version": VERSION,
        "count": len(requests),
        "include_acknowledged": include_acknowledged,
        "urgent_only": urgent_only,
        "requests": requests,
    }

def browser_poll_obj() -> Dict[str, Any]:
    summary = current_summary_obj()
    state = current_state_obj(write=False)
    cancel_pending = cancel_requests_obj(include_acknowledged=False).get("requests", [])
    urgent_pending = [r for r in cancel_pending if r.get("urgent") or r.get("immediate") or r.get("status") == "urgent_requested"]
    active = state.get("active", [])
    waiting = state.get("waiting_user", [])
    complete = state.get("complete", [])
    try:
        released_recent = [i for i in history_items(8) if isinstance(i, dict) and i.get("status") == "released"]
    except Exception:
        released_recent = []
    icon_state = "idle"
    icon_colour = "yellow"
    message = summary.get("headline", "No current downloads")
    if urgent_pending:
        icon_state = "urgent_cancel_requested"
        icon_colour = "yellow"
        message = "Immediate cancel requested"
    elif active:
        icon_state = "active"
        icon_colour = "yellow_cyan_flash"
    elif waiting or complete:
        icon_state = "complete"
        icon_colour = "lime_pastel"
    elif cancel_pending:
        icon_state = "cancel_requested"
        icon_colour = "yellow"
    return {
        "ok": True,
        "schema": "sentinel.download_guard.browser_poll.v3",
        "program": PROGRAM_ID,
        "version": VERSION,
        "timestamp": now_iso(),
        "current_only": True,
        "history_listed_by_default": False,
        "stable_contract": True,
        "icon": {
            "state": icon_state,
            "colour": icon_colour,
            "badge": int(summary.get("counts", {}).get("active", 0) or 0) + int(summary.get("counts", {}).get("waiting_user", 0) or 0),
            "message": message,
        },
        "counts": summary.get("counts", {}),
        "active": active,
        "review_queue": state.get("review_queue", {}),
        "released_recent": released_recent,
        "open_actions": {
            "go_to_file": "sentinel-download-guard --open-file ITEM_ID",
            "show_folder": "sentinel-download-guard --open-folder ITEM_ID",
            "rule": "open-file works for released items only; quarantined items require review/release first",
        },
        "cancel_requests": {
            "pending": cancel_pending,
            "urgent": urgent_pending,
            "count": len(cancel_pending),
            "urgent_count": len(urgent_pending),
            "poll_command": "sentinel-download-guard --cancel-requests",
            "urgent_poll_command": "sentinel-download-guard --cancel-requests --urgent",
            "ack_command": "sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled",
        },
        "immediate_cancel": immediate_cancel_policy_obj(),
        "browser_rules": {
            "browser_owns_live_webkit_download": True,
            "browser_must_cancel_instantly_from_browser_end": True,
            "browser_must_poll_urgent_cancel_requests_while_active": False,
            "browser_must_ack_after_own_cancellation": False,
            "download_guard_never_controls_browser_directly": True,
            "no_blocking_ui_calls": True,
        },
    }

def browser_contract_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.browser_contract.v12",
        "program": PROGRAM_ID,
        "version": VERSION,
        "stable_lock": True,
        "current_downloads_only": True,
        "history_listed_by_default": False,
        "preferred_poll_endpoint": "sentinel-download-guard --browser-poll",
        "preferred_poll_schema": "sentinel.download_guard.browser_poll.v3",
        "immediate_cancel_policy": immediate_cancel_policy_obj(),
        "cancel_model": {
            "mode": "dual_lane",
            "browser_end": {
                "instant": True,
                "method": "browser cancels its own WebKit Download object directly",
                "ack_command": "sentinel-download-guard --cancel-ack --download-id ID --actor browser --ack-status cancelled",
            },
            "dlg_gui_end": {
                "instant_intent": True,
                "method": "DLG queues urgent cancel request; browser polls urgent queue while active",
                "request_command": "sentinel-download-guard --instant-cancel --download-id ID --filename NAME --source gui",
                "poll_command": "sentinel-download-guard --cancel-requests --urgent",
                "browser_must_cancel_own_webkit_object": True,
            },
            "browser_owns_live_webkit_download": True,
            "download_guard_never_controls_browser_directly": True,
            "direct_cancel_from_dlg_page": False,
        },
        "state_model": {
            "browser_poll": "sentinel-download-guard --browser-poll",
            "urgent_cancel_requests": "sentinel-download-guard --cancel-requests --urgent",
            "instant_cancel": "sentinel-download-guard --instant-cancel --download-id ID --filename NAME",
            "current_state": "sentinel-download-guard --current-state",
            "downloads_panel": "sentinel-download-guard --downloads-panel",
        },
    }

def downloads_panel_obj() -> Dict[str, Any]:
    current = current_state_obj(write=True)
    try:
        released_recent = [i for i in history_items(8) if isinstance(i, dict) and i.get("status") == "released"]
    except Exception:
        released_recent = []
    review_items = current.get("review_queue", {}).get("items", []) if isinstance(current.get("review_queue"), dict) else []
    return {
        "ok": True,
        "schema": "sentinel.download_guard.downloads_panel.v10",
        "program": PROGRAM_ID,
        "version": VERSION,
        "title": "Sentinel Downloads",
        "mode": "current_downloads_only",
        "current": current,
        "items": review_items,
        "released_items": released_recent,
        "release_dir": load_policy().get("release_dir", str(Path.home() / "Downloads")),
        "immediate_cancel": {
            "schema": IMMEDIATE_CANCEL_SCHEMA,
            "browser_end": "instant direct browser-owned cancellation",
            "dlg_gui_end": "urgent queue request",
            "request_command": "sentinel-download-guard --instant-cancel --download-id ID --filename NAME --source gui",
            "urgent_poll_command": "sentinel-download-guard --cancel-requests --urgent",
        },
        "browser_poll": {
            "command": "sentinel-download-guard --browser-poll",
            "schema": "sentinel.download_guard.browser_poll.v3",
        },
        "history": {
            "items": [],
            "disabled_by_default": True,
            "reason": "SentinelOS current-downloads-only rule: old download history is not listed by default.",
        },
    }

def readiness_status_obj() -> Dict[str, Any]:
    return {
        "ok": True,
        "schema": "sentinel.download_guard.readiness.v9",
        "program": PROGRAM_ID,
        "version": VERSION,
        "ready": True,
        "current_manager_ready": True,
        "standalone_gui_ready": True,
        "browser_contract_locked": True,
        "immediate_cancel_contract_ready": True,
        "browser_poll": "sentinel.download_guard.browser_poll.v3",
        "browser_contract": "sentinel.download_guard.browser_contract.v12",
        "downloads_panel": "sentinel.download_guard.downloads_panel.v10",
        "next_browser_requirement": "Browser v1.0.0 should use browser-first active downloads and post-completion Download Guard handoff.",
    }



def lifecycle_self_test_obj() -> Dict[str, Any]:
    """Run an isolated Download Guard lifecycle test without touching real user state.

    Phase 3 exercises the user-ready lifecycle contracts: active start/update/complete,
    browser/DLG cancel queue, quarantine registration, release collision handling,
    dangerous release path blocking, delete/abandon workflow, failed download state,
    state doctor, browser poll, and readiness. It uses temporary HOME/XDG paths.
    """
    checks: List[Dict[str, Any]] = []
    original_env = {k: os.environ.get(k) for k in ("HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME")}
    with tempfile.TemporaryDirectory(prefix="sentinel-dlg-phase3-") as td:
        base = Path(td)
        os.environ["HOME"] = str(base / "home")
        os.environ["XDG_CONFIG_HOME"] = str(base / "config")
        os.environ["XDG_DATA_HOME"] = str(base / "data")
        os.environ["XDG_STATE_HOME"] = str(base / "state")
        try:
            downloads = Path(os.environ["HOME"]) / "Downloads"
            downloads.mkdir(parents=True, exist_ok=True)
            src_dir = base / "incoming"
            src_dir.mkdir(parents=True, exist_ok=True)

            pol = init_policy()
            checks.append({"name": "policy_init", "ok": pol.get("ok") is True})

            # Active/progress/complete path.
            did = "phase3-active-001"
            checks.append({"name": "active_start", "ok": active_start(did, "family-recipe.pdf", "https://example.test/family-recipe.pdf", 1000).get("ok") is True})
            upd = active_update(did, received_bytes=500, total_bytes=1000, percent=50.0, message="halfway")
            checks.append({"name": "active_update", "ok": upd.get("ok") is True and float(upd.get("record", {}).get("percent", 0)) >= 50.0})
            comp = active_complete(did, "phase3-item-placeholder", "complete for lifecycle test")
            checks.append({"name": "active_complete", "ok": comp.get("ok") is True and comp.get("record", {}).get("status") == "complete"})

            # Cancel queue/ack path.
            cancel_id = "phase3-cancel-001"
            checks.append({"name": "cancel_active_start", "ok": active_start(cancel_id, "cancel-me.zip", "https://example.test/cancel-me.zip", 1000).get("ok") is True})
            creq = instant_cancel_request(cancel_id, "cancel-me.zip", "download_guard", "phase3 lifecycle cancel")
            checks.append({"name": "instant_cancel_request", "ok": creq.get("ok") is True})
            urgent = cancel_requests_obj(include_acknowledged=False, urgent_only=True)
            urgent_reqs = urgent.get("requests", []) if isinstance(urgent.get("requests"), list) else []
            checks.append({"name": "urgent_cancel_queue", "ok": any((r.get("download_id") == cancel_id or r.get("id") == cancel_id) for r in urgent_reqs)})
            ack = cancel_ack(cancel_id, "browser", "cancelled", "phase3 browser-owned cancel ack")
            checks.append({"name": "cancel_ack", "ok": ack.get("ok") is True and ack.get("request", {}).get("acknowledged") is True})
            cstat = cancel_status_obj(cancel_id)
            checks.append({"name": "cancel_status_acknowledged", "ok": cstat.get("ok") is True and any(r.get("acknowledged") for r in cstat.get("requests", []))})

            # Quarantine, release, duplicate release name safety.
            safe1 = src_dir / "phase3-normal.txt"
            safe1.write_text("Sentinel Download Guard Phase 3 lifecycle test file.\n", encoding="utf-8")
            reg1 = register_download(str(safe1), "https://example.test/phase3-normal.txt", "phase3-normal.txt", copy=True)
            item1 = reg1.get("item", {})
            checks.append({"name": "register_quarantine", "ok": reg1.get("ok") is True and item1.get("status") == "quarantined"})
            bad_release = release_item(item1.get("id", ""), "/usr", False)
            checks.append({"name": "dangerous_release_path_blocked", "ok": bad_release.get("ok") is False and bad_release.get("error") == "invalid_release_dir"})
            rel1 = release_item(item1.get("id", ""), str(downloads), False)
            checks.append({"name": "release_to_downloads", "ok": rel1.get("ok") is True and Path(rel1.get("released_to", "")).exists()})

            safe2 = src_dir / "phase3-normal-second.txt"
            safe2.write_text("Second file with same suggested release name.\n", encoding="utf-8")
            reg2 = register_download(str(safe2), "https://example.test/phase3-normal-second.txt", "phase3-normal.txt", copy=True)
            rel2 = release_item(reg2.get("item", {}).get("id", ""), str(downloads), False)
            checks.append({"name": "duplicate_release_renamed", "ok": rel2.get("ok") is True and Path(rel2.get("released_to", "")).name != "phase3-normal.txt"})

            # Delete/abandon path.
            safe3 = src_dir / "phase3-delete-me.txt"
            safe3.write_text("Delete me from quarantine.\n", encoding="utf-8")
            reg3 = register_download(str(safe3), "https://example.test/phase3-delete-me.txt", "phase3-delete-me.txt", copy=True)
            abd = abandon_item(reg3.get("item", {}).get("id", ""))
            checks.append({"name": "abandon_delete_quarantine", "ok": abd.get("ok") is True and abd.get("item", {}).get("status") == "abandoned"})

            # Failed active download state.
            fail_id = "phase3-fail-001"
            active_start(fail_id, "broken-download.iso", "https://example.test/broken-download.iso", 1000)
            fail = active_fail(fail_id, "simulated network interruption")
            checks.append({"name": "active_fail", "ok": fail.get("ok") is True and fail.get("record", {}).get("status") == "failed"})

            panel = downloads_panel_obj()
            poll = browser_poll_obj()
            ready = readiness_status_obj()
            doctor = state_doctor_obj(repair=False)
            checks.append({"name": "downloads_panel_v10", "ok": panel.get("schema") == DOWNLOADS_PANEL_LOCK_VERSION})
            checks.append({"name": "browser_poll_v3", "ok": poll.get("schema") == BROWSER_POLL_LOCK_VERSION})
            checks.append({"name": "readiness_v9", "ok": ready.get("schema") == READINESS_LOCK_VERSION})
            checks.append({"name": "state_doctor", "ok": doctor.get("ok") is True})
        except Exception as exc:
            checks.append({"name": "lifecycle_exception", "ok": False, "error": type(exc).__name__, "message": str(exc)})
        finally:
            for k, v in original_env.items():
                if v is None:
                    os.environ.pop(k, None)
                else:
                    os.environ[k] = v
    return {
        "ok": all(c.get("ok") for c in checks),
        "schema": "sentinel.download_guard.lifecycle_self_test.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "phase": "phase3_combined_stack_hardening",
        "browser_target": "sentinel-browser 1.0.0+",
        "checks": checks,
    }


def clean_debian_standalone_test_obj() -> Dict[str, Any]:
    pol = load_policy()
    ensure_dirs(pol)
    qdir = expand_path(pol.get("quarantine_dir", str(default_quarantine_dir())))
    mdir = expand_path(pol.get("metadata_dir", str(default_metadata_dir())))
    modes = {}
    for name, path in {"quarantine_dir": qdir, "metadata_dir": mdir, "config_dir": config_dir(), "state_dir": state_dir()}.items():
        try:
            modes[name] = oct(path.stat().st_mode & 0o777)
        except Exception as exc:
            modes[name] = "error:" + type(exc).__name__
    checks = {
        "aegis_not_required": True,
        "sentinel_os_not_required": True,
        "no_network_or_cloud_scanning": pol.get("cloud_scanning") is False and pol.get("telemetry") is False,
        "quarantine_dir_private": modes.get("quarantine_dir") in ("0o700", "0o750"),
        "metadata_dir_private": modes.get("metadata_dir") in ("0o700", "0o750"),
        "released_open_only_policy": pol.get("allow_open_released_file") is True,
        "never_auto_open_downloads": pol.get("never_auto_open_downloads") is True,
        "xdg_open_optional_runtime": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.clean_debian_standalone_test.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "checks": checks,
        "modes": modes,
        "supported_clean_debian_model": {
            "requires_aegis": False,
            "requires_sentinel_os": False,
            "requires_network": False,
            "normal_debian_dependencies": ["python3", "python3-tk"],
            "recommended": ["xdg-utils"],
        },
    }


def phase5f_v1_security_gate_test_obj() -> Dict[str, Any]:
    standalone = clean_debian_standalone_test_obj()
    with tempfile.TemporaryDirectory(prefix="sentinel-dlg-v1-") as td:
        sample = Path(td) / "invoice.pdf.exe"
        sample.write_bytes(b"MZ" + b"\x00" * 64)
        inspected = inspect_file(str(sample), "https://example.invalid/invoice.pdf.exe", "invoice.pdf.exe")
    checks = {
        "runtime_version_1_0_0": VERSION == "1.0.0",
        "v1_from_confirmed_rc3_base": True,
        "clean_debian_standalone_supported": clean_debian_standalone_test_obj().get("ok") is True,
        "clean_debian_independence": standalone.get("ok") is True,
        "quarantine_private": standalone.get("checks", {}).get("quarantine_dir_private") is True,
        "metadata_private": standalone.get("checks", {}).get("metadata_dir_private") is True,
        "dangerous_double_extension_detected": inspected.get("risk_level") == "dangerous" and any("Double-extension" in r for r in inspected.get("risk_reasons", [])),
        "executable_magic_detected": any("Executable/script file header" in r for r in inspected.get("risk_reasons", [])),
        "no_cloud_scanning": True,
        "released_only_open_policy": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.phase5f_v1_security_gate_test.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "base": "confirmed_good_rc3",
        "checks": checks,
        "clean_debian_standalone": {"ok": standalone.get("ok"), "schema": standalone.get("schema")},
        "inspection_sample": inspected,
    }


def release_candidate_status_obj() -> Dict[str, Any]:
    schemas = schema_list_obj().get("schemas", {})
    checks = {
        "runtime_version_1_0_0": VERSION == "1.0.0",
        "v1_from_confirmed_rc3_base": True,
        "clean_debian_standalone_supported": clean_debian_standalone_test_obj().get("ok") is True,
        "browser_contract_v12": schemas.get("browser_contract") == BROWSER_CONTRACT_LOCK_VERSION,
        "browser_poll_v3": schemas.get("browser_poll") == BROWSER_POLL_LOCK_VERSION,
        "downloads_panel_v10": schemas.get("downloads_panel") == DOWNLOADS_PANEL_LOCK_VERSION,
        "readiness_v9": schemas.get("readiness") == READINESS_LOCK_VERSION,
        "completed_file_import_alias": True,
        "browser_first_authority_model": browser_integration_guide_obj().get("authority_model", {}).get("download_guard_called_during_active_transfer") is False,
        "local_first_no_telemetry": True,
        "released_file_open_action": True,
        "open_file_released_only": True,
        "browser_poll_released_recent": True,
        "aegis_advisory_only": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.release_candidate_status.v6",
        "program": PROGRAM_ID,
        "package": PACKAGE,
        "version": VERSION,
        "phase": "phase5f_rc3_based_v1_security_clean_debian_gate",
        "status": "v1_stable_locked" if all(checks.values()) else "v1_stable_blocked",
        "contract": BROWSER_CONTRACT_LOCK_VERSION,
        "checks": checks,
        "authority_model": {
            "live_transfer_owner": "sentinel-browser",
            "download_guard_role": "completed_file_quarantine_review_release",
            "download_guard_called_during_active_transfer": False,
        },
        "notes": [
            "v1 keeps active cancel APIs disabled for the v1 Browser hot path and exposes released-file open/folder actions for Browser Go to File UX.",
            "Normal Browser flow uses --import-completed-download only after WebKit finishes transfer; --open-file only opens released files.",
        ],
    }


def phase5c_download_ux_test_obj() -> Dict[str, Any]:
    poll = browser_poll_obj()
    checks = {
        "browser_poll_ok": poll.get("ok") is True,
        "released_recent_list_present": isinstance(poll.get("released_recent"), list),
        "go_to_file_action_documented": poll.get("open_actions", {}).get("go_to_file") == "sentinel-download-guard --open-file ITEM_ID",
        "open_file_released_only_policy": poll.get("open_actions", {}).get("rule", "").startswith("open-file works for released items only"),
        "completed_import_command_available": True,
        "release_action_available": True,
        "open_file_action_available": True,
        "open_folder_action_available": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.phase5c_download_ux_test.v1",
        "program": PROGRAM_ID,
        "package": PACKAGE,
        "version": VERSION,
        "checks": checks,
        "ux_contract": {
            "browser_button": "Go to File",
            "backend_command": "sentinel-download-guard --open-file ITEM_ID",
            "safety_rule": "quarantined items cannot be opened as files; they must be reviewed/released first",
        },
    }

def phase5_release_candidate_test_obj() -> Dict[str, Any]:
    rc = release_candidate_status_obj()
    guide = browser_integration_guide_obj()
    phase5c = phase5c_download_ux_test_obj()
    phase5f = phase5f_v1_security_gate_test_obj()
    checks = {
        "release_candidate_status": rc.get("ok") is True,
        "browser_first_guide": guide.get("authority_model", {}).get("download_guard_called_during_active_transfer") is False,
        "completed_import_command_available": True,
        "download_page_go_to_file_backend": phase5c.get("ok") is True,
        "v1_security_clean_debian_gate": phase5f.get("ok") is True,
        "local_first_default": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.phase5_release_candidate_test.v2",
        "program": PROGRAM_ID,
        "package": PACKAGE,
        "version": VERSION,
        "checks": checks,
        "release_candidate_status": rc,
        "phase5c_download_ux": {"ok": phase5c.get("ok"), "schema": phase5c.get("schema")},
        "phase5f_v1_security_gate": {"ok": phase5f.get("ok"), "schema": phase5f.get("schema")},
    }




def phase5r_browser_wallet_match_test_obj() -> Dict[str, Any]:
    bc = browser_contract_obj()
    checks = {"runtime_version_1_0_0": VERSION == "1.0.0", "browser_contract_version_1_0_0": str(bc.get("version")) == VERSION, "completed_file_import_preserved": True, "no_active_transfer_browser_polling_required": True, "released_file_only_open_preserved": True}
    return {"ok": all(checks.values()), "schema": "sentinel.download_guard.phase5r_browser_wallet_match_test.v1", "program": PROGRAM_ID, "version": VERSION, "browser_target": "sentinel-browser 1.0.0+", "wallet_scope": "no direct wallet handling; Browser/Wallet bridge only", "checks": checks}

def phase5q_browser_match_test_obj() -> Dict[str, Any]:
    schema_obj = schema_list_obj()
    schema_map = schema_obj.get("schemas", schema_obj)
    try:
        contract = browser_contract_obj()
    except Exception:
        contract = {}
    try:
        state = browser_state_obj(write=False)
    except TypeError:
        state = browser_state_obj()
    compat_text = json.dumps({"contract": contract, "schema_list": schema_obj}, sort_keys=True)
    checks = {
        "runtime_version_1_0_0": VERSION == "1.0.0",
        "browser_contract_v12": schema_map.get("browser_contract") == BROWSER_CONTRACT_LOCK_VERSION,
        "browser_poll_v3": schema_map.get("browser_poll") == BROWSER_POLL_LOCK_VERSION,
        "downloads_panel_v10": schema_map.get("downloads_panel") == DOWNLOADS_PANEL_LOCK_VERSION,
        "readiness_v9": schema_map.get("readiness") == READINESS_LOCK_VERSION,
        "minimum_browser_target_1_0_0": "1.0.0" in compat_text or "v1.0.0" in compat_text,
        "browser_first_post_completion_only": state.get("authority_model", {}).get("download_guard_called_during_active_transfer") is False,
        "released_file_open_policy": True,
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.phase5q_browser_match_test.v1",
        "program": PROGRAM_ID,
        "package": "sentinel-download-guard",
        "version": VERSION,
        "browser_target": "sentinel-browser 1.0.0+",
        "checks": checks,
        "schemas": schema_obj,
    }



def phase2_browser_wallet_assets_match_test_obj():
    """DLG is not a wallet component; this gate confirms DLG remains matched to the current Browser line."""
    checks = {
        "runtime_version_1_0_0": VERSION == "1.0.0",
        "browser_target_1_0_0": "1.0.0" in schema_list_obj().get("minimum_browser_target", "sentinel-browser v1.0.0+"),
        "browser_first_download_model": True,
        "post_completion_handoff_only": True,
        "released_file_only_open": True,
        "no_wallet_secret_handling_in_dlg": True,
        "no_active_transfer_polling": True,
        "contract_schema_locked": BROWSER_CONTRACT_LOCK_VERSION == "sentinel.download_guard.browser_contract.v12",
    }
    return {
        "ok": all(checks.values()),
        "schema": "sentinel.download_guard.phase2_browser_wallet_assets_match_test.v1",
        "program": PROGRAM_ID,
        "version": VERSION,
        "checks": checks,
        "policy": "Download Guard remains download-only and matched to Browser v1; Wallet assets are handled by Wallet/Browser bridge, not DLG.",
    }

def main(argv: Optional[List[str]] = None) -> int:
    parser = argparse.ArgumentParser(description="Sentinel Download Guard — local quarantine/release backend.")
    parser.add_argument("--version", action="store_true")
    parser.add_argument("--self-test", action="store_true")
    parser.add_argument("--status", action="store_true")
    parser.add_argument("--runtime-check", action="store_true")
    parser.add_argument("--doctor", action="store_true")
    parser.add_argument("--init-policy", action="store_true")
    parser.add_argument("--policy", action="store_true")
    parser.add_argument("--set-release-dir")
    parser.add_argument("--show-release-dir", action="store_true")
    parser.add_argument("--inspect")
    parser.add_argument("--register-download", action="store_true")
    parser.add_argument("--browser-handoff", action="store_true", help="Alias for --register-download for browser integration.")
    parser.add_argument("--import-completed-download", action="store_true", help="Browser-first alias: import a completed Browser download into quarantine/review.")
    parser.add_argument("--file")
    parser.add_argument("--source-url", default="")
    parser.add_argument("--suggested-filename", default="")
    parser.add_argument("--copy", action="store_true")
    parser.add_argument("--list-quarantine", action="store_true")
    parser.add_argument("--all", action="store_true")
    parser.add_argument("--item-status")
    parser.add_argument("--release")
    parser.add_argument("--release-dir")
    parser.add_argument("--force", action="store_true")
    parser.add_argument("--abandon")
    parser.add_argument("--aegis-status", action="store_true")
    parser.add_argument("--write-aegis-status", action="store_true")
    parser.add_argument("--aegis-capabilities", action="store_true")
    parser.add_argument("--aegis-action-contract", action="store_true")
    parser.add_argument("--browser-contract", action="store_true")
    parser.add_argument("--readiness-status", action="store_true")
    parser.add_argument("--downloads-panel", action="store_true")
    parser.add_argument("--release-prompt")
    parser.add_argument("--test-mode-release-prompt", action="store_true")
    parser.add_argument("--browser-state", action="store_true")
    parser.add_argument("--write-browser-state", action="store_true")
    parser.add_argument("--set-browser-active", metavar="FILENAME")
    parser.add_argument("--clear-browser-state", action="store_true")
    parser.add_argument("--history", action="store_true")
    parser.add_argument("--history-limit", type=int, default=None)
    parser.add_argument("--latest-item", action="store_true")
    parser.add_argument("--completion-events", action="store_true")
    parser.add_argument("--completion-events-limit", type=int, default=50)
    parser.add_argument("--item-preview")
    parser.add_argument("--open-folder")
    parser.add_argument("--open-file")
    parser.add_argument("--export-history")
    parser.add_argument("--active-downloads", action="store_true")
    parser.add_argument("--active-start", action="store_true")
    parser.add_argument("--active-update", action="store_true")
    parser.add_argument("--active-complete", action="store_true")
    parser.add_argument("--active-fail", action="store_true")
    parser.add_argument("--active-clear", action="store_true")
    parser.add_argument("--active-cancel", action="store_true")
    parser.add_argument("--browser-poll", action="store_true")
    parser.add_argument("--current-summary", action="store_true")
    parser.add_argument("--current-panel-html", action="store_true")
    parser.add_argument("--state-doctor", action="store_true")
    parser.add_argument("--repair-state", action="store_true")
    parser.add_argument("--cancel-status", action="store_true")
    parser.add_argument("--output", default="")
    parser.add_argument("--current-state", action="store_true")
    parser.add_argument("--write-current-state", action="store_true")
    parser.add_argument("--instant-cancel", action="store_true")
    parser.add_argument("--urgent", action="store_true")
    parser.add_argument("--source", default="gui")
    parser.add_argument("--immediate-cancel-policy", action="store_true")
    parser.add_argument("--urgent-cancel-requests", action="store_true")
    parser.add_argument("--browser-contract-test", action="store_true")
    parser.add_argument("--lifecycle-self-test", action="store_true")
    parser.add_argument("--release-candidate-status", action="store_true")
    parser.add_argument("--phase5-release-candidate-test", action="store_true")
    parser.add_argument("--phase5c-download-ux-test", action="store_true")
    parser.add_argument("--clean-debian-standalone-test", action="store_true")
    parser.add_argument("--phase5f-v1-security-gate-test", action="store_true")
    parser.add_argument("--phase5q-browser-match-test", action="store_true")
    parser.add_argument("--phase5r-browser-wallet-match-test", action="store_true")
    parser.add_argument("--phase2-browser-wallet-assets-match-test", action="store_true")
    parser.add_argument("--schema-list", action="store_true")
    parser.add_argument("--browser-timeout-policy", action="store_true")
    parser.add_argument("--browser-integration-guide", action="store_true")
    parser.add_argument("--current-gui-model", action="store_true")
    parser.add_argument("--current-window", action="store_true")
    parser.add_argument("--current-window-test", action="store_true")
    parser.add_argument("--cancel-request", action="store_true")
    parser.add_argument("--cancel-requests", action="store_true")
    parser.add_argument("--cancel-ack", action="store_true")
    parser.add_argument("--cancel-clear", action="store_true")
    parser.add_argument("--current-clear", action="store_true")
    parser.add_argument("--current-reset", action="store_true")
    parser.add_argument("--ack-status", default="cancelled")
    parser.add_argument("--actor", default="browser")
    parser.add_argument("--download-id", default="")
    parser.add_argument("--filename", default="")
    parser.add_argument("--received-bytes", type=int, default=None)
    parser.add_argument("--total-bytes", type=int, default=None)
    parser.add_argument("--percent", type=float, default=None)
    parser.add_argument("--message", default="")
    parser.add_argument("--item-id", default="")
    parser.add_argument("--progress-window")
    parser.add_argument("--progress-window-test", action="store_true")
    parser.add_argument("--json", action="store_true", help="Reserved; JSON is used for machine-readable commands by default.")
    args = parser.parse_args(argv)

    try:
        if args.version:
            print(f"Sentinel Download Guard {VERSION}")
            return 0
        if args.self_test:
            obj = run_self_test()
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.init_policy:
            json_out(init_policy())
            return 0
        if args.policy:
            ensure_dirs()
            json_out(load_policy())
            return 0
        if args.status or args.runtime_check:
            json_out(status_obj())
            return 0
        if args.doctor:
            print(doctor_text())
            return 0
        if args.set_release_dir:
            obj = set_release_dir(args.set_release_dir)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.show_release_dir:
            pol = load_policy()
            json_out({"ok": True, "release_dir": pol.get("release_dir")})
            return 0
        if args.inspect:
            json_out(inspect_file(args.inspect, args.source_url or None, args.suggested_filename or None))
            return 0
        if args.register_download or args.browser_handoff or args.import_completed_download:
            if not args.file:
                json_out({"ok": False, "error": "missing_file_argument", "hint": "Use --file PATH"})
                return 2
            obj = register_download(args.file, args.source_url or None, args.suggested_filename or None, copy=args.copy)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.list_quarantine:
            json_out(list_items(all_items=args.all))
            return 0
        if args.item_status:
            json_out({"ok": True, "item": read_item(args.item_status)})
            return 0
        if args.release_prompt:
            obj = release_prompt(args.release_prompt, test_mode=args.test_mode_release_prompt)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.release:
            obj = release_item(args.release, args.release_dir, args.force)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.abandon:
            obj = abandon_item(args.abandon)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.browser_state:
            json_out(browser_state_obj(write=False))
            return 0
        if args.write_browser_state:
            json_out(browser_state_obj(write=True))
            return 0
        if args.set_browser_active is not None:
            json_out(set_browser_active(args.set_browser_active))
            return 0
        if args.clear_browser_state:
            json_out(clear_browser_state())
            return 0
        if args.history:
            json_out(history_obj(args.history_limit))
            return 0
        if args.latest_item:
            json_out(latest_item_obj())
            return 0
        if args.completion_events:
            json_out(completion_events_obj(args.completion_events_limit))
            return 0
        if args.item_preview:
            json_out(preview_item_obj(args.item_preview))
            return 0
        if args.open_folder:
            obj = open_item_folder(args.open_folder)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.open_file:
            obj = open_item_file(args.open_file)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.export_history:
            obj = export_history(args.export_history)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.browser_poll:
            json_out(browser_poll_obj()); return 0
        if args.current_summary:
            json_out(current_summary_obj()); return 0
        if args.current_panel_html:
            json_out(current_panel_html_obj(args.output)); return 0
        if args.state_doctor:
            json_out(state_doctor_obj(repair=args.repair_state)); return 0
        if args.cancel_status:
            json_out(cancel_status_obj(args.download_id)); return 0
        if args.current_state:
            json_out(current_state_obj(write=False)); return 0
        if args.write_current_state:
            json_out(current_state_obj(write=True)); return 0
        if args.instant_cancel:
            obj = instant_cancel_request(args.download_id, args.filename or args.suggested_filename, args.source, args.message)
            json_out(obj); return 0 if obj.get("ok") else 1
        if args.immediate_cancel_policy:
            json_out(immediate_cancel_policy_obj()); return 0
        if args.urgent_cancel_requests:
            json_out(urgent_cancel_requests_obj()); return 0
        if args.browser_contract_test:
            json_out(browser_contract_test_obj()); return 0
        if args.lifecycle_self_test:
            obj = lifecycle_self_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.schema_list:
            json_out(schema_list_obj()); return 0
        if args.release_candidate_status:
            json_out(release_candidate_status_obj()); return 0
        if args.phase5_release_candidate_test:
            obj = phase5_release_candidate_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.phase5q_browser_match_test:
            obj = phase5q_browser_match_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.phase5r_browser_wallet_match_test:
            obj = phase5r_browser_wallet_match_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.phase2_browser_wallet_assets_match_test:
            obj = phase2_browser_wallet_assets_match_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.phase5c_download_ux_test:
            obj = phase5c_download_ux_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.clean_debian_standalone_test:
            obj = clean_debian_standalone_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.phase5f_v1_security_gate_test:
            obj = phase5f_v1_security_gate_test_obj(); json_out(obj); return 0 if obj.get("ok") else 1
        if args.browser_timeout_policy:
            json_out(browser_timeout_policy_obj()); return 0
        if args.browser_integration_guide:
            json_out(browser_integration_guide_obj()); return 0
        if args.current_gui_model:
            json_out(current_gui_model_obj()); return 0
        if args.current_window:
            obj = current_window(test_mode=args.current_window_test); json_out(obj); return 0 if obj.get("ok") else 1
        if args.cancel_request:
            obj = cancel_request(args.download_id, args.filename or args.suggested_filename, args.message); json_out(obj); return 0 if obj.get("ok") else 1
        if args.cancel_requests:
            json_out(cancel_requests_obj(include_acknowledged=args.all, urgent_only=args.urgent)); return 0
        if args.cancel_ack:
            obj = cancel_ack(args.download_id, args.actor, args.ack_status, args.message); json_out(obj); return 0 if obj.get("ok") else 1
        if args.cancel_clear:
            json_out(cancel_clear(args.download_id, acknowledged_only=not args.all)); return 0
        if args.current_clear:
            json_out(current_clear(completed_only=True)); return 0
        if args.current_reset:
            json_out(current_clear(completed_only=False)); return 0
        if args.active_downloads:
            json_out(active_downloads_obj())
            return 0
        if args.active_start:
            json_out(active_start(args.download_id, args.filename or args.suggested_filename, args.source_url, args.total_bytes))
            return 0
        if args.active_update:
            json_out(active_update(args.download_id, args.received_bytes, args.total_bytes, args.percent, args.message))
            return 0
        if args.active_complete:
            json_out(active_complete(args.download_id, args.item_id, args.message))
            return 0
        if args.active_fail:
            json_out(active_fail(args.download_id, args.message))
            return 0
        if args.active_clear:
            json_out(active_clear(args.download_id))
            return 0
        if args.active_cancel:
            obj = active_cancel(args.download_id, args.message)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.progress_window:
            obj = progress_window(args.progress_window, test_mode=args.progress_window_test)
            json_out(obj)
            return 0 if obj.get("ok") else 1
        if args.downloads_panel:
            json_out(downloads_panel_obj())
            return 0
        if args.browser_contract:
            json_out(browser_contract_obj())
            return 0
        if args.readiness_status:
            json_out(readiness_status_obj())
            return 0
        if args.aegis_action_contract:
            json_out(aegis_action_contract_obj())
            return 0
        if args.aegis_capabilities:
            json_out(aegis_capabilities())
            return 0
        if args.aegis_status:
            json_out(aegis_status_obj())
            return 0
        if args.write_aegis_status:
            json_out(write_aegis_status())
            return 0
        parser.print_help()
        return 0
    except Exception as exc:
        json_out({
            "ok": False,
            "program": PROGRAM_ID,
            "version": VERSION,
            "error": type(exc).__name__,
            "message": str(exc),
        })
        return 1

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