import fcntl
import hashlib
import json
import os
import pathlib
import shutil
import stat
import subprocess

import pytest

from test_candidate_pipeline import candidate

ROOT = pathlib.Path(__file__).resolve().parents[3]
MODULE = ROOT / "modules/subrouter"
INSTALL = MODULE / "bin/install-candidate"


def test_live_preflight_runs_as_service_identity_with_isolated_environment():
    script = INSTALL.read_text()
    preflight = script[
        script.index("authority_status() {") :
        script.index("grep -qx 'Provider credentials: 0'")
    ]
    assert 'ADMIN_OWNER_ARGS=(-o overdeck-subrouter -g overdeck-subrouter)' in script
    assert 'CLIENT_UID=${OVERDECK_SUBROUTER_CLIENT_UID:-$(stat -c %u "$ROOT")}' in script
    assert 'client_token_path="${TEST_ROOT}/etc/overdeck/subrouter-authority-client.token"' in script
    assert 'install "${CLIENT_OWNER_ARGS[@]}" -m 0600 /dev/stdin "$client_token_staging"' in script
    assert 'pwd.getpwnam("overdeck-subrouter").pw_uid' in script
    assert "path.stat().st_nlink != 1" in script
    assert "/usr/sbin/runuser --user overdeck-subrouter -- /usr/bin/env -i" in preflight
    assert 'HOME="$RUNTIME/state/home"' in preflight
    assert 'XDG_CONFIG_HOME="$RUNTIME/state/config"' in preflight
    assert 'SUBROUTER_STATE_DIR="$RUNTIME/state"' in preflight
    assert '"$binary" authority-status' in preflight
    assert 'pre_status=$(authority_status "$target/bin/subrouter")' in preflight
    assert 'post_status=$(authority_status "$CURRENT/bin/subrouter")' in script


def test_candidate_snapshot_uses_executable_temp_filesystem_not_run_lock():
    script = INSTALL.read_text()
    snapshot = script[
        script.index("# Pin the submitted directory") :
        script.index("candidate=\"$CANDIDATE_SNAPSHOT_PARENT/$candidate_name\"")
    ]
    assert "SNAPSHOT_ROOT=/var/tmp" in snapshot
    assert 'mktemp -d "$SNAPSHOT_ROOT/overdeck-subrouter-candidate.XXXXXX"' in snapshot
    assert 'mktemp -d "$LOCK_DIR/overdeck-subrouter-candidate.XXXXXX"' not in snapshot


def test_installed_status_command_names_the_isolated_state_directory():
    command = (
        "deck-sudo /usr/sbin/runuser --user overdeck-subrouter -- /usr/bin/env -i "
        "OVERDECK_SUBROUTER_SERVICE_IDENTITY=overdeck-subrouter "
        "/var/lib/overdeck/subrouter/current/bin/subrouter authority-status "
        "--state-dir /var/lib/overdeck/subrouter/state"
    )
    sources = (
        (MODULE / "README.md").read_text(),
        (ROOT / "docs/plans/2026-08-17-subrouter-authority.md").read_text(),
    )
    assert all(command in source for source in sources)
    assert all(
        "/var/lib/overdeck/subrouter/current/bin/subrouter authority-status\n"
        not in source
        for source in sources
    )


def _fake_systemctl(
    bin_dir: pathlib.Path,
    *,
    fail_first_restart: bool = False,
    initially_enabled: bool = False,
    missing_unit_errors: bool = False,
):
    state = bin_dir / "systemctl-state"
    calls = bin_dir / "systemctl-calls"
    script = bin_dir / "systemctl"
    fail = "1" if fail_first_restart else "0"
    enabled = "0" if initially_enabled else "1"
    missing = "1" if missing_unit_errors else "0"
    script.write_text(
        "#!/usr/bin/env bash\n"
        "set -eu\n"
        f"fail_first={fail}\n"
        f"enabled_exit={enabled}\n"
        f"missing_unit_errors={missing}\n"
        f"state={state!s}\n"
        f"calls={calls!s}\n"
        "printf '%s\\n' \"$*\" >>\"$calls\"\n"
        "case ${1:-} in\n"
        "  is-active) exit 0 ;;\n"
        "  is-enabled) exit \"$enabled_exit\" ;;\n"
        "  disable|stop) [[ $missing_unit_errors == 0 ]] || exit 5 ;;\n"
        "  restart)\n"
        "    count=0; [[ -f \"$state\" ]] && count=$(<\"$state\")\n"
        "    count=$((count + 1)); printf '%s\\n' \"$count\" >\"$state\"\n"
        "    if [[ $fail_first == 1 && $count == 1 ]]; then exit 1; fi ;;\n"
        "esac\n"
        "exit 0\n"
    )
    script.chmod(0o755)


def _installer_env(test_root: pathlib.Path, bin_dir: pathlib.Path) -> dict[str, str]:
    return {
        **os.environ,
        "OVERDECK_SUBROUTER_TEST_ROOT": str(test_root),
        "PATH": f"{bin_dir}:{os.environ['PATH']}",
    }


def _install(candidate: pathlib.Path, test_root: pathlib.Path, bin_dir: pathlib.Path):
    return subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )


def _clone_release(source: pathlib.Path, destination: pathlib.Path, *, binary_exit=None):
    shutil.copytree(source, destination)
    for path in (destination, *destination.rglob("*")):
        if path.is_dir():
            path.chmod(0o755)
        elif path.is_file():
            path.chmod(0o644)
    receipt_path = destination / "receipt.json"
    receipt = json.loads(receipt_path.read_text())
    binary = destination / "bin/subrouter"
    if binary_exit is not None:
        binary.write_text(f"#!/usr/bin/env bash\nexit {binary_exit}\n")
    receipt["binary_sha256"] = hashlib.sha256(binary.read_bytes()).hexdigest()
    manifest = json.loads((destination / "manifest.json").read_text())
    digest = hashlib.sha256()
    for value in (
        receipt["input_digest"],
        manifest["revision"],
        manifest["archive_sha256"],
        receipt["patch_sha256"],
        manifest["go"],
    ):
        digest.update(value.encode() + b"\0")
    digest.update(bytes.fromhex(receipt["binary_sha256"]))
    final = destination.parent / digest.hexdigest()
    receipt["candidate_digest"] = final.name
    receipt_path.write_text(json.dumps(receipt, sort_keys=True) + "\n")
    destination.rename(final)
    (final / "bin/subrouter").chmod(0o555)
    for path in final.rglob("*"):
        if path.is_file() and path != final / "bin/subrouter":
            path.chmod(0o444)
        elif path.is_dir():
            path.chmod(0o555)
    final.chmod(0o555)
    return final


def test_service_isolated_credential_empty_and_loopback_only():
    unit = (MODULE / "systemd/overdeck-subrouter.service").read_text()
    required = (
        "User=overdeck-subrouter",
        "Group=overdeck-subrouter",
        "ProtectHome=yes",
        "ProtectSystem=strict",
        "NoNewPrivileges=yes",
        "PrivateTmp=yes",
        "ReadOnlyPaths=/var/lib/overdeck/subrouter/releases",
        "ReadWritePaths=/var/lib/overdeck/subrouter/state",
        "--authority-mode",
        "--state-dir /var/lib/overdeck/subrouter/state",
        "--authority-admin-file /var/lib/overdeck/subrouter/state/authority-admin",
        "--addr 127.0.0.1:31415",
        "--sr-switch-interval 0",
        "--fetch-usage=false",
    )
    for line in required:
        assert line in unit
    forbidden = (
        "--transcripts",
        "--account-import-token",
        "SUBROUTER_ACCOUNT_IMPORT_TOKEN",
        "SUBROUTER_ADMIN_TOKEN",
        "/home/",
    )
    for text in forbidden:
        assert text not in unit


def test_installer_rejects_non_root_without_test_seam(candidate):
    env = {**os.environ}
    env.pop("OVERDECK_SUBROUTER_TEST_ROOT", None)
    result = subprocess.run(
        [str(INSTALL), str(candidate)], cwd=ROOT, env=env, text=True, capture_output=True
    )
    if os.geteuid() == 0:
        return
    assert result.returncode == 77
    assert "root required" in result.stderr


def test_installer_installs_exact_candidate_idempotently(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    env = _installer_env(test_root, bin_dir)
    for _ in range(2):
        result = subprocess.run(
            [str(INSTALL), str(candidate)],
            cwd=ROOT,
            env=env,
            text=True,
            capture_output=True,
        )
        assert result.returncode == 0, result.stderr
    runtime = test_root / "var/lib/overdeck/subrouter"
    target = runtime / "releases" / candidate.name
    assert (runtime / "current").resolve() == target.resolve()
    assert (target / "bin/subrouter").is_file()
    assert stat.S_IMODE((target / "bin/subrouter").stat().st_mode) == 0o555
    assert stat.S_IMODE((target / "receipt.json").stat().st_mode) == 0o444
    assert stat.S_IMODE((target / "manifest.json").stat().st_mode) == 0o444
    assert json.loads((target / "authority-state.json").read_text()) == {
        "schema": 1,
        "authority_state_schema": 1,
        "rollback_compatible_state_schemas": [1],
    }
    assert json.loads((runtime / "authority-state-schema.json").read_text()) == {"schema": 1}
    admin_marker = runtime / "state/authority-admin"
    assert admin_marker.is_file() and not admin_marker.is_symlink()
    assert stat.S_IMODE(admin_marker.stat().st_mode) == 0o600
    token = admin_marker.read_text().strip()
    assert len(token) == 64 and all(c in "0123456789abcdef" for c in token)
    client_token = test_root / "etc/overdeck/subrouter-authority-client.token"
    assert client_token.read_text().strip() == token
    assert stat.S_IMODE(client_token.stat().st_mode) == 0o600
    assert not (runtime / "state/authority-routes.json").exists()
    assert not any(path.stat().st_mode & 0o222 for path in target.rglob("*") if path.is_file())
    assert (
        test_root / "etc/systemd/system/overdeck-subrouter.service"
    ).read_text() == (MODULE / "systemd/overdeck-subrouter.service").read_text()
    assert "enable overdeck-subrouter.service" in (bin_dir / "systemctl-calls").read_text()


def test_installer_refuses_permissive_or_symlinked_admin_material(candidate, tmp_path):
    for kind in ("permissive", "symlink", "hardlink"):
        test_root = tmp_path / kind / "root"
        bin_dir = tmp_path / kind / "bin"
        test_root.mkdir(parents=True)
        bin_dir.mkdir()
        _fake_systemctl(bin_dir)
        state = test_root / "var/lib/overdeck/subrouter/state"
        state.mkdir(parents=True, mode=0o700)
        marker = state / "authority-admin"
        if kind == "permissive":
            marker.write_text("synthetic\n")
            marker.chmod(0o644)
        elif kind == "symlink":
            target = state / "synthetic-marker"
            target.write_text("synthetic\n")
            target.chmod(0o600)
            marker.symlink_to(target)
        else:
            marker.write_text("synthetic\n")
            marker.chmod(0o600)
            os.link(marker, state / "synthetic-marker-link")
        result = _install(candidate, test_root, bin_dir)
        assert result.returncode != 0
        assert "administration token is invalid" in result.stderr


def test_installer_refuses_nonempty_authority_state(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    env = _installer_env(test_root, bin_dir)
    env["FIXTURE_PROVIDER_CREDENTIALS"] = "1"
    result = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=env,
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "containing provider credentials" in result.stderr
    assert not (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_preserves_provider_credentials_on_trusted_upgrade(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)

    initial = _install(candidate, test_root, bin_dir)
    assert initial.returncode == 0, initial.stderr

    accounts = test_root / "var/lib/overdeck/subrouter/state/providers/codex/accounts"
    accounts.mkdir(parents=True, mode=0o700)
    for directory in (
        test_root / "var/lib/overdeck/subrouter/state/providers",
        test_root / "var/lib/overdeck/subrouter/state/providers/codex",
        accounts,
    ):
        directory.chmod(0o700)
    credential = accounts / "fixture@example.com.json"
    credential.write_text("synthetic credential bytes must remain untouched\n")
    credential.chmod(0o600)
    before = credential.read_bytes()

    env = _installer_env(test_root, bin_dir)
    env.update(
        FIXTURE_PROVIDER_CREDENTIALS="1",
        FIXTURE_AUTHORITY_ROUTES="1",
        FIXTURE_PROXY_GRANTS="1",
    )
    upgraded = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=env,
        text=True,
        capture_output=True,
    )
    assert upgraded.returncode == 0, upgraded.stderr
    assert credential.read_bytes() == before
    assert (test_root / "var/lib/overdeck/subrouter/current").resolve().name == candidate.name
    assert "Provider credentials: 1" in upgraded.stdout
    assert "Authority routes: 1" in upgraded.stdout
    assert "Proxy grants: 1" in upgraded.stdout


def test_installer_trusted_upgrade_still_rejects_provider_symlink(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    initial = _install(candidate, test_root, bin_dir)
    assert initial.returncode == 0, initial.stderr

    accounts = test_root / "var/lib/overdeck/subrouter/state/providers/codex/accounts"
    accounts.mkdir(parents=True, mode=0o700)
    for directory in (
        test_root / "var/lib/overdeck/subrouter/state/providers",
        test_root / "var/lib/overdeck/subrouter/state/providers/codex",
        accounts,
    ):
        directory.chmod(0o700)
    outside = tmp_path / "synthetic-secret-never-read-upgrade"
    outside.write_text("synthetic fixture content\n")
    (accounts / "fixture@example.com.json").symlink_to(outside)
    env = _installer_env(test_root, bin_dir)
    env.update(
        FIXTURE_PROVIDER_CREDENTIALS="1",
        FIXTURE_AUTHORITY_ROUTES="1",
        FIXTURE_PROXY_GRANTS="1",
    )
    result = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=env,
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "provider credential tree metadata policy refused" in result.stderr
    assert outside.read_text() == "synthetic fixture content\n"


def test_installer_refuses_provider_tree_symlink_without_reading_content(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    state = test_root / "var/lib/overdeck/subrouter/state"
    claude_root = state / "providers/claude"
    claude_root.mkdir(parents=True, mode=0o700)
    outside = tmp_path / "synthetic-secret-never-read"
    outside.write_text("synthetic fixture content\n")
    (claude_root / "account.json").symlink_to(outside)
    result = _install(candidate, test_root, bin_dir)
    assert result.returncode != 0
    assert "provider credential tree metadata policy refused" in result.stderr
    assert outside.read_text() == "synthetic fixture content\n"
    assert not (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_allows_empty_codex_lock_metadata(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    accounts = test_root / "var/lib/overdeck/subrouter/state/providers/codex/accounts"
    accounts.mkdir(parents=True, mode=0o700)
    lock_file = accounts / "..subrouter-migration-batch-control.json.lock"
    lock_file.write_bytes(b"")
    lock_file.chmod(0o600)
    result = _install(candidate, test_root, bin_dir)
    assert result.returncode == 0, result.stderr
    assert (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_allows_empty_claude_lock_metadata(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    claude_root = test_root / "var/lib/overdeck/subrouter/state/providers/claude"
    claude_root.mkdir(parents=True, mode=0o700)
    lock_file = claude_root / "claude.json.lock"
    lock_file.write_bytes(b"")
    lock_file.chmod(0o600)
    result = _install(candidate, test_root, bin_dir)
    assert result.returncode == 0, result.stderr
    assert (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_rejects_nonempty_provider_lock_metadata(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    claude_root = test_root / "var/lib/overdeck/subrouter/state/providers/claude"
    claude_root.mkdir(parents=True, mode=0o700)
    lock_file = claude_root / "claude.json.lock"
    lock_file.write_text("not-empty\n")
    lock_file.chmod(0o600)
    result = _install(candidate, test_root, bin_dir)
    assert result.returncode != 0
    assert "provider credential tree metadata policy refused" in result.stderr
    assert not (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_refuses_unresolved_authority_state(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    env = _installer_env(test_root, bin_dir)
    env["FIXTURE_UNRESOLVED_ATTEMPTS"] = "1"
    result = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=env,
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "unresolved refresh attempts" in result.stderr
    assert not (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_installer_refuses_concurrent_activation(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    lock_dir = test_root / "run/lock"
    test_root.mkdir()
    bin_dir.mkdir()
    lock_dir.mkdir(parents=True)
    _fake_systemctl(bin_dir)
    with (lock_dir / "overdeck-subrouter-install.lock").open("w") as lock:
        fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
        result = subprocess.run(
            [str(INSTALL), str(candidate)],
            cwd=ROOT,
            env=_installer_env(test_root, bin_dir),
            text=True,
            capture_output=True,
        )
    assert result.returncode != 0
    assert "another Subrouter install or rollback is active" in result.stderr
    assert not (test_root / "var/lib/overdeck/subrouter/current").exists()


def test_failed_first_activation_restores_absent_runtime_without_missing_unit_errors(
    candidate, tmp_path
):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir, fail_first_restart=True, missing_unit_errors=True)

    result = _install(candidate, test_root, bin_dir)

    assert result.returncode != 0
    assert "previous runtime restored" in result.stderr
    assert "could not be fully restored" not in result.stderr
    runtime = test_root / "var/lib/overdeck/subrouter"
    assert not (runtime / "current").exists()
    assert not (test_root / "etc/systemd/system/overdeck-subrouter.service").exists()
    calls = (bin_dir / "systemctl-calls").read_text().splitlines()
    assert "disable overdeck-subrouter.service" not in calls
    assert "stop overdeck-subrouter.service" not in calls


def test_failed_restart_restores_previous_link_and_unit(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    runtime = test_root / "var/lib/overdeck/subrouter"
    unit = test_root / "etc/systemd/system/overdeck-subrouter.service"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    installed = _install(candidate, test_root, bin_dir)
    assert installed.returncode == 0, installed.stderr
    old = (runtime / "current").resolve()

    unit.write_text("old unit\n")
    (bin_dir / "systemctl-state").unlink(missing_ok=True)
    (bin_dir / "systemctl-calls").unlink(missing_ok=True)
    _fake_systemctl(bin_dir, fail_first_restart=True, initially_enabled=True)
    result = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "previous runtime restored" in result.stderr
    assert (runtime / "current").resolve() == old.resolve()
    assert not (runtime / "previous").exists()
    assert unit.read_text() == "old unit\n"
    assert "enable overdeck-subrouter.service" in (bin_dir / "systemctl-calls").read_text()


def test_failed_explicit_rollback_restores_both_links(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    installed = _install(candidate, test_root, bin_dir)
    assert installed.returncode == 0, installed.stderr
    runtime = test_root / "var/lib/overdeck/subrouter"
    releases = runtime / "releases"
    current_release = (runtime / "current").resolve()
    failed_previous = _clone_release(
        current_release, releases / ("f" * 64), binary_exit=1
    )
    (runtime / "previous").unlink(missing_ok=True)
    (runtime / "previous").symlink_to(failed_previous)
    result = subprocess.run(
        [str(INSTALL), "--rollback"],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "original runtime restored" in result.stderr
    assert (runtime / "current").resolve() == current_release.resolve()
    assert (runtime / "previous").resolve() == failed_previous.resolve()


def test_installer_rejects_symlink_inside_candidate(candidate, tmp_path):
    receipt = candidate / "receipt.json"
    candidate.chmod(0o755)
    receipt.chmod(0o644)
    receipt.unlink()
    receipt.symlink_to(candidate / "logs/focused.log")
    candidate.chmod(0o555)
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    result = subprocess.run(
        [str(INSTALL), str(candidate)],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "candidate" in result.stderr.lower()


def test_installer_promotes_snapshot_not_replaced_source(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    original_receipt = (candidate / "receipt.json").read_bytes()
    env = _installer_env(test_root, bin_dir)
    env["OVERDECK_SUBROUTER_TEST_AFTER_SNAPSHOT"] = (
        f"chmod u+w {candidate / 'receipt.json'}; printf '{{}}\\n' >{candidate / 'receipt.json'}"
    )
    result = subprocess.run(
        [str(INSTALL), str(candidate)], cwd=ROOT, env=env, text=True, capture_output=True
    )
    assert result.returncode == 0, result.stderr
    installed_receipt = (
        test_root
        / "var/lib/overdeck/subrouter/releases"
        / candidate.name
        / "receipt.json"
    ).read_bytes()
    assert installed_receipt == original_receipt
    assert (candidate / "receipt.json").read_bytes() != installed_receipt


@pytest.mark.parametrize("damage", ["receipt", "manifest", "binary_digest"])
def test_rollback_refuses_invalid_previous_provenance(candidate, tmp_path, damage):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    installed = _install(candidate, test_root, bin_dir)
    assert installed.returncode == 0, installed.stderr
    runtime = test_root / "var/lib/overdeck/subrouter"
    current = (runtime / "current").resolve()
    previous = _clone_release(
        current, runtime / "releases" / ("f" * 64), binary_exit=0
    )
    damaged = {
        "receipt": previous / "receipt.json",
        "manifest": previous / "manifest.json",
        "binary_digest": previous / "bin/subrouter",
    }[damage]
    damaged.chmod(0o644)
    damaged.write_text("{}\n" if damage != "binary_digest" else "#!/usr/bin/env bash\nexit 0\n# changed\n")
    damaged.chmod(0o555 if damage == "binary_digest" else 0o444)
    (runtime / "previous").unlink(missing_ok=True)
    (runtime / "previous").symlink_to(previous)
    result = subprocess.run(
        [str(INSTALL), "--rollback"],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "previous release provenance or compatibility is invalid" in result.stderr
    assert (runtime / "current").resolve() == current


def test_rollback_refuses_incompatible_authority_schema(candidate, tmp_path):
    test_root = tmp_path / "root"
    bin_dir = tmp_path / "bin"
    test_root.mkdir()
    bin_dir.mkdir()
    _fake_systemctl(bin_dir)
    installed = _install(candidate, test_root, bin_dir)
    assert installed.returncode == 0, installed.stderr
    runtime = test_root / "var/lib/overdeck/subrouter"
    current = (runtime / "current").resolve()
    previous = _clone_release(
        current, runtime / "releases" / ("f" * 64), binary_exit=0
    )
    metadata = previous / "authority-state.json"
    metadata.chmod(0o644)
    metadata.write_text(
        json.dumps(
            {
                "schema": 1,
                "authority_state_schema": 2,
                "rollback_compatible_state_schemas": [2],
            }
        )
        + "\n"
    )
    metadata.chmod(0o444)
    (runtime / "previous").unlink(missing_ok=True)
    (runtime / "previous").symlink_to(previous)
    result = subprocess.run(
        [str(INSTALL), "--rollback"],
        cwd=ROOT,
        env=_installer_env(test_root, bin_dir),
        text=True,
        capture_output=True,
    )
    assert result.returncode != 0
    assert "previous release provenance or compatibility is invalid" in result.stderr
    assert (runtime / "current").resolve() == current
