#!/usr/bin/env python3
"""Fail closed unless a committed Base V3 candidate changes only Base paths.

Run from a clean, dedicated Base worktree after Base and Solana work have been
committed separately. This tool only reads Git metadata; it never stages,
resets, deletes, signs, deploys, or contacts a chain.
"""
from __future__ import annotations

import argparse
import subprocess
from pathlib import Path

ALLOWED_PREFIXES = (
    ".gitignore",
    "ci/check_base_v3_release_scope.py",
    "ci/check_v3_runtime_sizes.py",
    "ci/check_base_v3_source_snapshot.py",
    "ci/check_loop_bounds.py",
    "ci/release-gates.sh",
    "ci/RELEASE_GATES.md",
    "ci/manifests/v3-loop-bounds.json",
    "ci/manifests/base-v3-remediation-source.sha256",
    "ci/manifests/base-read-only-bootstrap-release-gate.json",
    ".gitlab-ci.yml",
    "tests/test_base_v3_release_scope.py",
    "tests/test_base_read_only_bootstrap_release_gate.py",
    "pr10/partner-package/",
    "pr11/base-sepolia-rehearsal/",
    "pr8/pilot/base_v3_deployment_manifest.json",
    "pr8/pilot/base_v3_read_only_bootstrap_profile.json",
    "pr8/pilot/BASE_READ_ONLY_BOOTSTRAP.md",
    "pr8/pilot/deployment_manifest.py",
    "pr8/pilot/bootstrap_profile.py",
    "pr8/pilot/generate_bootstrap_package.py",
    "pr8/pilot/test_deployment_manifest.py",
    "repos/vaultfire-sdk/src/",
    "repos/vaultfire-sdk/tests/",
    "repos/vaultfire-sdk/dist/baseSepoliaV3Rehearsal.",
    "repos/vaultfire-sdk/dist/index.",
    "v3-src/contracts/",
    "v3-src/test/",
    "v3-src/test/fork/base/",
    "v3-migration/completeness/",
)
FORBIDDEN_PREFIXES = ("repos/vaultfire-solana/",)


def is_allowed(path: str) -> bool:
    return path.startswith(ALLOWED_PREFIXES)


def scope_errors(paths: list[str]) -> list[str]:
    errors: list[str] = []
    for path in paths:
        if path.startswith(FORBIDDEN_PREFIXES):
            errors.append(f"Base-only candidate includes forbidden Solana path: {path}")
        elif not is_allowed(path):
            errors.append(f"Base-only candidate includes path outside explicit allowlist: {path}")
    return errors


def git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(["git", "-C", str(repo), *args], text=True, capture_output=True, check=False)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--repo-root", type=Path, default=Path.cwd())
    parser.add_argument("--base", required=True, help="reachable committed Base candidate baseline")
    parser.add_argument("--head", default="HEAD", help="committed candidate tip")
    parser.add_argument("--require-clean", action="store_true")
    args = parser.parse_args()
    if args.require_clean:
        status = git(args.repo_root, "status", "--porcelain", "--untracked-files=all")
        if status.returncode != 0 or status.stdout.strip():
            print("release worktree must be clean before Base scope validation")
            return 2
    # Do not filter out deletions: deleting an out-of-scope (or Solana) file is
    # itself an out-of-scope candidate change.  Disable rename detection so a
    # rename is represented by both its removed and added path.
    names = git(args.repo_root, "diff", "--name-only", "--no-renames", f"{args.base}..{args.head}")
    if names.returncode != 0:
        print("unable to resolve committed Base candidate range")
        return 2
    paths = [line for line in names.stdout.splitlines() if line]
    errors = scope_errors(paths)
    if errors:
        print("\n".join(errors))
        return 2
    print(f"Base V3 release scope passed: {len(paths)} committed paths are allowlisted; Solana is excluded.")
    return 0

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