#!/usr/bin/env python3
"""Fail-closed validation for the Base Sepolia V3 candidate manifest.

READY is impossible without a supplied, locally recompiled canonical artifact
evidence bundle. This tool is read-only: it never signs, deploys, broadcasts,
uses a wallet, or contacts an RPC endpoint.
"""
from __future__ import annotations

import argparse
import copy
import ctypes
import hashlib
import fcntl
import stat
import json
import os
import posixpath
import re
import subprocess
import tarfile
from pathlib import Path
from pathlib import PurePosixPath
from typing import Any

try:
    from canonical_json import load_json_bytes as shared_load_json_bytes
except ModuleNotFoundError:  # direct importlib test loading
    import importlib.util
    _canon_spec = importlib.util.spec_from_file_location("vaultfire_canonical_json", Path(__file__).with_name("canonical_json.py"))
    assert _canon_spec and _canon_spec.loader
    _canon = importlib.util.module_from_spec(_canon_spec); _canon_spec.loader.exec_module(_canon)
    shared_load_json_bytes = _canon.load_json_bytes

from Crypto.Hash import keccak
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from eth_abi import decode as abi_decode
from eth_abi import encode as abi_encode

CHAIN_ID = 84532
SCHEMA = "vaultfire.base-sepolia-v3-rehearsal-manifest.v1"
BUNDLE_SCHEMA = "vaultfire.base-sepolia-v3-artifact-evidence.v1"
ATTESTATION_SCHEMA = "vaultfire.base-sepolia-v3-artifact-attestation.v2"
VALIDATOR_ID = "validate_v3_candidate_manifest.py"
BLOCKED = "BLOCKED_UNTIL_HUMAN_ROLES_AND_EXACT_024_ARTIFACTS"
READY = "READY_FOR_REHEARSAL"
COMPILER = "solc 0.8.24+commit.e11b9ed9"
SOLC_PATH = Path("/home/user/.svm/0.8.24/solc-0.8.24")
SOLC_SHA256 = "fb03a29a517452b9f12bcf459ef37d0a543765bb3bbc911e70a87d6a37c30d5f"
# No production release/audit key has been deliberately approved.  READY
# therefore remains impossible in the production CLI until a key is pinned and
# independently re-audited.  Tests use an explicit function-only test seam.
PRODUCTION_TRUST_ANCHOR_ED25519_HEX: str | None = None
SETTINGS = {"viaIR": True, "optimizerRuns": 200, "evmVersion": "paris", "bytecodeHash": "none"}
SHA = re.compile(r"^[0-9a-f]{64}$")
COMMIT = re.compile(r"^[0-9a-f]{40}$")
ADDRESS = re.compile(r"^0x[0-9a-fA-F]{40}$")
HASH32 = re.compile(r"^0x[0-9a-fA-F]{64}$")
TX_HASH = re.compile(r"^0x[0-9a-fA-F]{64}$")
HEX = re.compile(r"^0x(?:[0-9a-fA-F]{2})*$")
KEY = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$")
SOURCE_PREFIX = "v3-src/contracts/"
IMPORT = re.compile(r'^\s*import\s+(?:\{[^}]*\}\s+from\s+)?["\']([^"\']+)["\']\s*;', re.MULTILINE)
REQUIRED = (
    "MultisigGovernanceV3", "BaseSepoliaRehearsalAccessControllerV3", "ThresholdConsensusV3",
    "DisputeRegistryV3", "SafeBondFactoryV3", "PartnershipBondFactoryV3", "AccountabilityBondFactoryV3",
)
SOURCE_NAMES = {
    "MultisigGovernanceV3": "contracts/governance/MultisigGovernanceV3.sol",
    "BaseSepoliaRehearsalAccessControllerV3": "contracts/pilot/BaseSepoliaRehearsalAccessControllerV3.sol",
    "ThresholdConsensusV3": "contracts/consensus/ThresholdConsensusV3.sol",
    "DisputeRegistryV3": "contracts/disputes/DisputeRegistryV3.sol",
    "SafeBondFactoryV3": "contracts/bonds/SafeBondFactoryV3.sol",
    "PartnershipBondFactoryV3": "contracts/bonds/PartnershipBondFactoryV3.sol",
    "AccountabilityBondFactoryV3": "contracts/bonds/AccountabilityBondFactoryV3.sol",
}
FORBIDDEN = {"rewards", "x402", "StreetCred", "mandate", "validation", "ERC-8004", "escrow", "Solana", "cross-chain"}
PROHIBITED = {"behavioral", "flourishing", "reputation", "loyalty", "quality", "belief", "biometric", "profiling", "surveillance"}

MANIFEST_KEYS = {"schema", "status", "network", "candidate", "objectiveRuleSchema", "contracts", "roles", "capsAtomic", "orderedHumanAuthorizedCalls", "enabledSurface", "excludedSurface", "artifactHash"}
NETWORK_KEYS = {"name", "chainId", "productionFundsProhibited", "mainnetRejected", "rpcPolicy"}
CANDIDATE_KEYS = {"sourceCommit", "preRemediationContractsTree", "currentContractsSourceSha256", "sourceArchiveSha256", "compiler", "foundry", "settings", "artifactEvidenceBundleSha256"}
RULE_KEYS = {"version", "allowedClasses", "prohibitedCategories", "opaquePolicyHashesRejected", "mutualConsentSettlementOnly", "disputesChangeCustody"}
CONTRACT_KEYS = {"address", "creationCodeHash", "runtimeCodeHash", "constructorArguments"}
ROLE_KEYS = {"governanceSigners", "guardian", "designPartner", "treasury"}
CAP_KEYS = {"global", "perPosition", "partnershipPerParty", "partnershipPerBond", "accountabilityPerBond"}
BUNDLE_KEYS = {"schema", "bundleSha256", "sourceCommit", "sourceTreeSha256", "sourceArchiveSha256", "compiler", "solcBinarySha256", "settings", "standardJsonInput", "standardJsonOutput", "standardJsonInputSha256", "standardJsonOutputSha256", "buildInfo", "buildInfoSha256", "submoduleGitlinks", "contracts", "deploymentVerificationStatement"}
BUNDLE_CONTRACT_KEYS = {"artifact", "sourceName", "contractName", "creationCodeHash", "runtimeCodeHash", "constructorTypes", "constructorArguments", "initCodeHash", "deploymentEvidence"}
EVIDENCE_KEYS = {"address", "chainId", "transactionHash", "initCodeHash", "runtimeCodeHash"}
DEPLOYMENT_STATEMENT_KEYS = {"schema", "chainId", "manifestArtifactHash", "bundleSha256", "sourceTreeSha256", "buildInfoSha256", "solcBinarySha256", "validatorSourceSha256", "validatorTranscriptSha256", "contracts", "signature", "publicKey"}
DEPLOYMENT_CONTRACT_KEYS = {"address", "transactionHash", "transactionTo", "transactionInput", "receiptStatus", "receiptContractAddress", "receiptBlockNumber", "codeBlockNumber", "code", "runtimeCodeHash", "initCodeHash", "constructorArguments", "patchedRuntimeHash", "immutableReferences"}


def _compact(value: Any) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")


def load_json_bytes(raw: bytes, label: str, *, require_canonical_bytes: bool = False) -> Any:
    value = shared_load_json_bytes(raw, label)
    if require_canonical_bytes:
        if not isinstance(value, dict) or canonical_json(value) != raw:
            raise ValueError(f"{label} must use byte-exact canonical JSON serialization")
    return value


def load_json_file(path: Path, label: str, *, require_canonical_bytes: bool = False) -> Any:
    try:
        return load_json_bytes(path.read_bytes(), label, require_canonical_bytes=require_canonical_bytes)
    except OSError as exc:
        raise ValueError(f"cannot read {label}: {exc}") from exc


def _assert_ascii_object_keys(value: Any) -> None:
    if isinstance(value, dict):
        for key, child in value.items():
            if not isinstance(key, str) or not KEY.fullmatch(key):
                raise ValueError("object keys must be ASCII identifiers")
            _assert_ascii_object_keys(child)
    elif isinstance(value, list):
        for child in value:
            _assert_ascii_object_keys(child)


def canonical_json(value: dict[str, Any]) -> bytes:
    _assert_ascii_object_keys(value)
    return _compact(value)


def content_hash(value: dict[str, Any]) -> str:
    cloned = copy.deepcopy(value)
    cloned["artifactHash"] = None
    return hashlib.sha256(canonical_json(cloned)).hexdigest()


def bundle_hash(value: dict[str, Any]) -> str:
    cloned = copy.deepcopy(value)
    cloned["bundleSha256"] = None
    return hashlib.sha256(canonical_json(cloned)).hexdigest()


def attestation_hash(value: dict[str, Any]) -> str:
    cloned = copy.deepcopy(value)
    cloned["attestationSha256"] = None
    return hashlib.sha256(canonical_json(cloned)).hexdigest()


def signed_statement_bytes(value: dict[str, Any]) -> bytes:
    cloned = copy.deepcopy(value)
    cloned["signature"] = None
    return canonical_json(cloned)


def verify_signed_deployment_statement(statement: dict[str, Any], public_key_hex: str | None) -> list[str]:
    """Verifier seam used by tests; production passes only the pinned anchor."""
    errors: list[str] = []
    _exact_object(statement, DEPLOYMENT_STATEMENT_KEYS, "deployment verification statement", errors)
    if errors:
        return errors
    if public_key_hex is None:
        return ["production READY is disabled until an independently pinned deployment-verification trust anchor exists"]
    try:
        public_key = bytes.fromhex(public_key_hex)
        signature = bytes.fromhex(str(statement["signature"]).removeprefix("0x"))
        Ed25519PublicKey.from_public_bytes(public_key).verify(signature, signed_statement_bytes(statement))
    except (ValueError, InvalidSignature):
        return ["deployment verification statement signature is invalid for the pinned trust anchor"]
    if statement.get("publicKey") != public_key_hex or statement.get("chainId") != CHAIN_ID:
        errors.append("deployment verification statement public key or chain is not bound")
    return errors


def validate_deployment_statement_shape(statement: dict[str, Any], manifest: dict[str, Any], bundle: dict[str, Any], runtime_templates: dict[str, bytes], init_codes: dict[str, str], immutable_maps: dict[str, list[dict[str, Any]]]) -> list[str]:
    """Validate authenticated external deployment observations without network access.

    The production CLI requires a separately pinned signer; this function makes
    self-consistent metadata insufficient even in a test verifier seam.
    """
    errors: list[str] = []
    _exact_object(statement, DEPLOYMENT_STATEMENT_KEYS, "deployment verification statement", errors)
    if errors:
        return errors
    expected = {
        "chainId": CHAIN_ID, "manifestArtifactHash": manifest.get("artifactHash"),
        "bundleSha256": bundle.get("bundleSha256"), "sourceTreeSha256": bundle.get("sourceTreeSha256"),
        "buildInfoSha256": bundle.get("buildInfoSha256"), "solcBinarySha256": SOLC_SHA256,
    }
    for field, value in expected.items():
        if statement.get(field) != value:
            errors.append(f"deployment verification statement does not bind {field}")
    contracts = statement.get("contracts")
    _exact_object(contracts, set(REQUIRED), "deployment verification contracts", errors)
    if not isinstance(contracts, dict):
        return errors
    for label in REQUIRED:
        item = contracts.get(label)
        _exact_object(item, DEPLOYMENT_CONTRACT_KEYS, f"deployment verification {label}", errors)
        if not isinstance(item, dict):
            continue
        evidence = bundle["contracts"][label]
        manifest_contract = manifest["contracts"][label]
        address = manifest_contract.get("address")
        code = item.get("code")
        template = runtime_templates[label]
        immutable_refs = item.get("immutableReferences", None)
        if (item.get("address") != address or item.get("receiptContractAddress") != address
                or item.get("transactionTo") is not None or item.get("receiptStatus") != 1
                or not TX_HASH.fullmatch(str(item.get("transactionHash", "")))
                or not isinstance(item.get("receiptBlockNumber"), int) or item["receiptBlockNumber"] < 0
                or not isinstance(item.get("codeBlockNumber"), int) or item["codeBlockNumber"] < item["receiptBlockNumber"]
                or item.get("transactionInput") != init_codes[label]):
            errors.append(f"deployment verification {label} does not bind creation input/receipt/address/block")
        if not isinstance(code, str) or not HEX.fullmatch(code):
            errors.append(f"deployment verification {label} lacks exact eth_getCode bytes")
            continue
        code_bytes = bytes.fromhex(code[2:])
        if item.get("runtimeCodeHash") != keccak256(code_bytes) or item.get("patchedRuntimeHash") != keccak256(code_bytes):
            errors.append(f"deployment verification {label} code hash does not bind eth_getCode bytes")
        expected_refs = immutable_maps[label]
        supplied_refs = item.get("immutableReferences")
        if supplied_refs != expected_refs:
            errors.append(f"deployment verification {label} immutable reference map does not exactly match fresh compiler output")
        ranges: list[tuple[int, int]] = []
        for entry in expected_refs:
            for location in entry["ranges"]:
                start, length = location["start"], location["length"]
                if not isinstance(start, int) or not isinstance(length, int) or length != 32 or start < 0 or start + length > len(template):
                    errors.append(f"deployment verification {label} immutable reference range is malformed")
                else: ranges.append((start, start + length))
        if len(code_bytes) != len(template): errors.append(f"deployment verification {label} runtime length differs from compiler template")
        ranges.sort()
        if any(right > next_left for (_, right), (next_left, _) in zip(ranges, ranges[1:])): errors.append(f"deployment verification {label} immutable reference ranges overlap")
        mask = {index for left, right in ranges for index in range(left, right)}
        if len(code_bytes) == len(template) and any(code_bytes[index] != template[index] for index in range(len(template)) if index not in mask):
            errors.append(f"deployment verification {label} changes a non-immutable runtime byte")
        if not expected_refs and code_bytes != template:
            errors.append(f"deployment verification {label} no-immutable runtime must equal compiler template byte-for-byte")
        if item.get("initCodeHash") != evidence.get("initCodeHash") or item.get("constructorArguments") != evidence.get("constructorArguments"):
            errors.append(f"deployment verification {label} does not bind canonical init code/ABI arguments")
    return errors


ATTESTATION_DOMAIN = "vaultfire.base-sepolia-v3.ready-attestation.v2"
ATTESTATION_V2_FIELDS = {
    "schema", "validator", "domain", "chainId", "manifestArtifactHash",
    "artifactEvidenceBundleSha256", "validatorSourceSha256",
    "validatorTranscriptSha256", "deploymentVerificationStatementSha256",
    "keyId", "algorithm", "signature",
}


class AttestationUnavailable(ValueError):
    """Raised instead of emitting a misleading incomplete production receipt."""


def attestation_v2_signing_bytes(statement: dict[str, Any]) -> bytes:
    """The exact cross-language Ed25519 message: canonical v2 JSON, signature null."""
    if set(statement) != ATTESTATION_V2_FIELDS:
        raise ValueError("attestation keys must exactly match canonical v2 schema")
    unsigned = copy.deepcopy(statement)
    unsigned["signature"] = None
    return canonical_json(unsigned)


def create_test_only_attestation_v2(
    *,
    manifest_artifact_hash: str,
    artifact_evidence_bundle_sha256: str,
    validator_source_sha256: str,
    validator_transcript_sha256: str,
    deployment_verification_statement_sha256: str,
    key_id: str,
    signing_key: Ed25519PrivateKey,
) -> dict[str, Any]:
    """Test seam only; the production CLI never calls it or accepts a test key."""
    hashes = (
        manifest_artifact_hash, artifact_evidence_bundle_sha256,
        validator_source_sha256, validator_transcript_sha256,
        deployment_verification_statement_sha256,
    )
    if not all(isinstance(item, str) and SHA.fullmatch(item) for item in hashes):
        raise ValueError("test attestation requires canonical SHA-256 bindings")
    if not isinstance(key_id, str) or not KEY.fullmatch(key_id):
        raise ValueError("test attestation key id must be an ASCII identifier")
    statement: dict[str, Any] = {
        "schema": ATTESTATION_SCHEMA, "validator": VALIDATOR_ID,
        "domain": ATTESTATION_DOMAIN, "chainId": CHAIN_ID,
        "manifestArtifactHash": manifest_artifact_hash,
        "artifactEvidenceBundleSha256": artifact_evidence_bundle_sha256,
        "validatorSourceSha256": validator_source_sha256,
        "validatorTranscriptSha256": validator_transcript_sha256,
        "deploymentVerificationStatementSha256": deployment_verification_statement_sha256,
        "keyId": key_id, "algorithm": "Ed25519", "signature": None,
    }
    statement["signature"] = "0x" + signing_key.sign(attestation_v2_signing_bytes(statement)).hex()
    return statement


def verify_test_only_attestation_v2(statement: dict[str, Any], public_key: Ed25519PublicKey) -> list[str]:
    """Test seam only. Production READY deliberately has no attestation verifier key."""
    try:
        if set(statement) != ATTESTATION_V2_FIELDS:
            return ["attestation keys must exactly match canonical v2 schema"]
        if statement.get("schema") != ATTESTATION_SCHEMA or statement.get("validator") != VALIDATOR_ID or statement.get("domain") != ATTESTATION_DOMAIN or statement.get("chainId") != CHAIN_ID or statement.get("algorithm") != "Ed25519":
            return ["attestation v2 domain or identity is invalid"]
        if not all(isinstance(statement.get(field), str) and SHA.fullmatch(statement[field]) for field in ("manifestArtifactHash", "artifactEvidenceBundleSha256", "validatorSourceSha256", "validatorTranscriptSha256", "deploymentVerificationStatementSha256")):
            return ["attestation v2 canonical hash bindings are invalid"]
        signature = statement.get("signature")
        if not isinstance(signature, str) or not HEX.fullmatch(signature):
            return ["attestation v2 signature is invalid"]
        public_key.verify(bytes.fromhex(signature.removeprefix("0x")), attestation_v2_signing_bytes(statement))
    except (ValueError, InvalidSignature):
        return ["attestation v2 signature is invalid"]
    return []


def ready_attestation(manifest: dict[str, Any], bundle: dict[str, Any]) -> dict[str, Any]:
    """Production is deliberately unavailable until a future audited signer exists."""
    del manifest, bundle
    raise AttestationUnavailable(
        "production attestation unavailable: no separately audited production signing key is pinned; no receipt was emitted"
    )


def keccak256(data: bytes) -> str:
    digest = keccak.new(digest_bits=256)
    digest.update(data)
    return "0x" + digest.hexdigest()


def source_tree_hash(entries: dict[str, bytes]) -> str:
    digest = hashlib.sha256()
    for path in sorted(entries):
        digest.update(path.encode("utf-8")); digest.update(b"\0")
        digest.update(hashlib.sha256(entries[path]).digest()); digest.update(b"\0")
    return digest.hexdigest()


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


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


def source_key_for_repo_path(path: str) -> str:
    if path.startswith("v3-src/contracts/"):
        return path.removeprefix("v3-src/")
    if path.startswith("v3-src/lib/openzeppelin-contracts/"):
        return "openzeppelin-contracts/" + path.removeprefix("v3-src/lib/openzeppelin-contracts/")
    raise ValueError(f"unmapped compilation source path: {path}")


def repo_path_for_source_key(key: str) -> str:
    if key.startswith("contracts/"):
        return "v3-src/" + key
    if key.startswith("openzeppelin-contracts/"):
        return "v3-src/lib/" + key
    raise ValueError(f"unreviewed remapping/source key: {key}")


def _resolve_import(source_key: str, specifier: str) -> str:
    if specifier.startswith("."):
        candidate = str(PurePosixPath(source_key).parent.joinpath(specifier))
    elif specifier.startswith("openzeppelin-contracts/"):
        candidate = specifier
    else:
        raise ValueError(f"unreviewed import/remapping: {specifier}")
    normalized = posixpath.normpath(candidate)
    if normalized.startswith("../") or normalized == ".." or not normalized.endswith(".sol"):
        raise ValueError(f"invalid normalized Solidity import: {specifier}")
    return normalized


def _submodule_gitlink(repo_root: Path, commit: str, path: str) -> str | None:
    result = _git_text(repo_root, "ls-tree", commit, "--", path)
    if result.returncode != 0 or not result.stdout.strip():
        return None
    fields = result.stdout.split()[0:3]
    return fields[2] if len(fields) == 3 and fields[0] == "160000" else None


def _committed_source_bytes(repo_root: Path, commit: str, repo_path: str) -> tuple[bytes | None, str | None]:
    """Read first-party blobs from parent Git and dependency blobs at gitlink SHA."""
    if repo_path.startswith("v3-src/lib/openzeppelin-contracts/"):
        gitlink = _submodule_gitlink(repo_root, commit, "v3-src/lib/openzeppelin-contracts")
        submodule = repo_root / "v3-src/lib/openzeppelin-contracts"
        relative = repo_path.removeprefix("v3-src/lib/openzeppelin-contracts/")
        if gitlink is None or not submodule.is_dir():
            return None, "required OpenZeppelin gitlink/submodule is unavailable"
        shown = _git_bytes(submodule, "show", f"{gitlink}:{relative}")
    else:
        shown = _git_bytes(repo_root, "show", f"{commit}:{repo_path}")
    return (shown.stdout, None) if shown.returncode == 0 else (None, f"cannot read committed compilation source {repo_path}")


def committed_source_entries(repo_root: Path, source_commit: str) -> tuple[dict[str, bytes] | None, list[str]]:
    """Return the exact transitive enabled-target compilation closure.

    Keys are repository paths for archive/tree binding.  Solidity standard-json
    source keys are derived separately by ``source_key_for_repo_path``.
    """
    if not COMMIT.fullmatch(source_commit): return None, ["candidate.sourceCommit must be a 40-hex commit"]
    if _git_text(repo_root, "merge-base", "--is-ancestor", source_commit, "HEAD").returncode != 0:
        return None, ["candidate.sourceCommit must be reachable from HEAD"]
    pending = [
        source for source in SOURCE_NAMES.values()
        if _git_text(repo_root, "cat-file", "-e", f"{source_commit}:v3-src/{source}").returncode == 0
    ]
    # Small isolated tests may intentionally contain one source rather than all
    # enabled targets; retain binary-read coverage without weakening production
    # behavior when real targets exist.
    if not pending:
        listed = _git_text(repo_root, "ls-tree", "-r", "--name-only", source_commit, "--", "v3-src/contracts")
        pending = [source_key_for_repo_path(path) for path in listed.stdout.splitlines() if path.endswith(".sol")]
    entries: dict[str, bytes] = {}
    errors: list[str] = []
    while pending:
        key = pending.pop()
        try:
            repo_path = repo_path_for_source_key(key)
        except ValueError as exc:
            errors.append(str(exc)); continue
        if repo_path in entries:
            continue
        data, error = _committed_source_bytes(repo_root, source_commit, repo_path)
        if error or data is None:
            errors.append(error or f"cannot read {repo_path}"); continue
        entries[repo_path] = data
        try:
            text = data.decode("utf-8")
            pending.extend(_resolve_import(key, item) for item in IMPORT.findall(text))
        except (UnicodeDecodeError, ValueError) as exc:
            errors.append(f"{repo_path}: {exc}")
    if not entries: errors.append("candidate.sourceCommit has no enabled compilation sources")
    return (entries if not errors else None), errors


def check_clean_source_paths(repo_root: Path, source_commit: str) -> list[str]:
    errors = []
    if _git_text(repo_root, "diff", "--quiet", source_commit, "--", "v3-src/contracts", "v3-src/lib/openzeppelin-contracts").returncode != 0:
        errors.append("candidate source paths differ from candidate.sourceCommit")
    status = _git_text(repo_root, "status", "--porcelain", "--untracked-files=all", "--", "v3-src/contracts", "v3-src/lib/openzeppelin-contracts")
    if status.returncode != 0 or status.stdout.strip(): errors.append("candidate source paths are not clean")
    return errors


def archive_entries(path: Path) -> tuple[dict[str, bytes] | None, list[str]]:
    if not path.is_file(): return None, ["canonical source archive is required"]
    entries, errors = {}, []
    try:
        with tarfile.open(path, "r:*") as archive:
            for member in archive.getmembers():
                if not member.isfile(): errors.append("canonical source archive may contain regular files only"); continue
                if not (member.name.startswith("v3-src/contracts/") or member.name.startswith("v3-src/lib/openzeppelin-contracts/")) or member.name in entries: errors.append("canonical source archive has invalid or duplicate paths"); continue
                if member.uid != 0 or member.gid != 0 or member.mtime != 0: errors.append("canonical source archive entries require uid/gid/mtime of zero")
                handle = archive.extractfile(member)
                if handle is None: errors.append(f"cannot read archive member {member.name}")
                else: entries[member.name] = handle.read()
    except (tarfile.TarError, OSError) as exc: return None, [f"cannot read canonical source archive: {exc}"]
    return (entries if not errors else None), errors


def _exact_object(value: Any, keys: set[str], label: str, errors: list[str]) -> None:
    if not isinstance(value, dict): errors.append(f"{label} must be an object"); return
    actual = set(value)
    if actual != keys: errors.append(f"{label} keys must exactly match canonical schema")


def _metadata_settings_valid(settings: Any) -> bool:
    return isinstance(settings, dict) and settings.get("viaIR") is True and settings.get("evmVersion") == "paris" and settings.get("optimizer") == {"enabled": True, "runs": 200} and settings.get("metadata", {}).get("bytecodeHash") == "none"


def _abi_type(item: dict[str, Any]) -> str:
    kind = item.get("type")
    if not isinstance(kind, str): raise ValueError("ABI input type is missing")
    if kind.startswith("tuple"):
        suffix = kind[5:]
        components = item.get("components")
        if not isinstance(components, list): raise ValueError("tuple ABI input lacks components")
        return "(" + ",".join(_abi_type(component) for component in components) + ")" + suffix
    return kind


def _artifact_object(path: Path) -> tuple[dict[str, Any] | None, list[str]]:
    try:
        value = load_json_file(path, "artifact")
        return (value if isinstance(value, dict) else None), ([] if isinstance(value, dict) else ["artifact must be an object"])
    except ValueError as exc: return None, [str(exc)]


def _rel_file(bundle_path: Path, value: Any, label: str, errors: list[str]) -> Path | None:
    if not isinstance(value, str) or not value or Path(value).is_absolute(): errors.append(f"{label} must be a relative file path"); return None
    root, raw = bundle_path.parent.resolve(), bundle_path.parent / value
    candidate = raw.resolve()
    if root not in candidate.parents or raw.is_symlink() or not candidate.is_file(): errors.append(f"{label} must be a regular file inside the evidence bundle directory"); return None
    return candidate


class SealedSolc:
    """A Linux memfd copy of the one verified compiler image, never a path."""
    def __init__(self, fd: int, digest: str):
        self.fd, self.digest = fd, digest
    def run(self, args: list[str], *, input: bytes | None = None) -> subprocess.CompletedProcess[bytes]:
        # `/proc/self/fd/N` names our sealed memfd in the child; pass_fds keeps
        # it open. The original SVM pathname is never resolved by subprocess.
        return subprocess.run([f"/proc/self/fd/{self.fd}", *args], input=input, capture_output=True, check=False, pass_fds=(self.fd,))
    def close(self) -> None:
        os.close(self.fd)

def _sealed_solc() -> SealedSolc | None:
    """Open, verify, seal, and execute a single compiler image without TOCTOU.

    Linux memfd seals are mandatory. Any unavailable kernel primitive, symlink,
    unexpected owner/mode, inode race, or hash mismatch fails closed.
    """
    required = getattr(os, "O_NOFOLLOW", 0)
    # CPython builds may omit os.memfd_create/fcntl constants despite Linux
    # supporting them. Use only the documented Linux syscall/constants here.
    if os.uname().sysname != "Linux" or os.uname().machine not in {"x86_64", "amd64"} or not required:
        return None
    memfd_create = getattr(os, "memfd_create", None)
    if memfd_create is None:
        libc = ctypes.CDLL(None, use_errno=True)
        def memfd_create(name: str, flags: int) -> int:  # type: ignore[no-redef]
            result = libc.syscall(319, name.encode(), flags)  # __NR_memfd_create x86_64
            if result < 0: raise OSError(ctypes.get_errno(), "memfd_create")
            return result
    add_seals = getattr(fcntl, "F_ADD_SEALS", 1033)
    seal_write, seal_shrink, seal_grow, seal_seal = 0x0008, 0x0002, 0x0004, 0x0001
    allow_sealing = getattr(os, "MFD_ALLOW_SEALING", 0x0002)
    cloexec = getattr(os, "MFD_CLOEXEC", 0x0001)
    try:
        fd = os.open(SOLC_PATH, os.O_RDONLY | os.O_CLOEXEC | required)
        before = os.fstat(fd)
        if (not stat.S_ISREG(before.st_mode) or before.st_uid != os.getuid()
                or before.st_mode & 0o022 or not before.st_mode & stat.S_IXUSR):
            os.close(fd); return None
        chunks: list[bytes] = []
        while True:
            chunk = os.read(fd, 1024 * 1024)
            if not chunk: break
            chunks.append(chunk)
        after = os.fstat(fd)
        image = b"".join(chunks)
        if (before.st_dev, before.st_ino, before.st_size) != (after.st_dev, after.st_ino, after.st_size) or hashlib.sha256(image).hexdigest() != SOLC_SHA256:
            os.close(fd); return None
        os.close(fd)
        memfd = memfd_create("vaultfire-solc-0.8.24", cloexec | allow_sealing)
        os.write(memfd, image)
        os.fchmod(memfd, 0o500)
        seals = seal_write | seal_grow | seal_shrink | seal_seal
        fcntl.fcntl(memfd, add_seals, seals)
        os.lseek(memfd, 0, os.SEEK_SET)
        sealed = os.read(memfd, len(image))
        if sealed != image or hashlib.sha256(sealed).hexdigest() != SOLC_SHA256:
            os.close(memfd); return None
        return SealedSolc(memfd, SOLC_SHA256)
    except (OSError, AttributeError):
        try: os.close(fd)  # type: ignore[name-defined]
        except Exception: pass
        return None


def _standard_output(solc: SealedSolc, standard_input: bytes) -> tuple[dict[str, Any] | None, list[str]]:
    result = solc.run(["--standard-json"], input=standard_input)
    if result.returncode != 0: return None, ["exact solc 0.8.24 rejected standard-json input"]
    try:
        value = load_json_bytes(result.stdout, "exact solc standard-json output")
    except ValueError: return None, ["exact solc 0.8.24 emitted duplicate or invalid standard-json output"]
    if not isinstance(value, dict): return None, ["exact solc output must be an object"]
    if any(item.get("severity") == "error" for item in value.get("errors", []) if isinstance(item, dict)):
        return None, ["exact solc standard-json output contains compiler errors"]
    return value, []


def _code(value: Any) -> bytes | None:
    """Decode artifact code accepting standard-json's bare and Foundry's 0x form."""
    if isinstance(value, dict):
        value = value.get("object")
    if not isinstance(value, str):
        return None
    raw = value[2:] if value.startswith("0x") else value
    if len(raw) % 2 or not re.fullmatch(r"[0-9a-fA-F]*", raw):
        return None
    return bytes.fromhex(raw)


def validate_artifact_bundle(value: dict[str, Any], manifest: dict[str, Any], committed: dict[str, bytes], archive_digest: str, bundle_path: Path | None, repo_root: Path | None = None) -> list[str]:
    errors: list[str] = []
    if bundle_path is None or not bundle_path.is_file(): return ["ready manifest requires --artifact-evidence-bundle"]
    try: bundle = load_json_file(bundle_path, "artifact evidence bundle")
    except ValueError as exc: return [str(exc)]
    _exact_object(bundle, BUNDLE_KEYS, "artifact evidence bundle", errors)
    if errors: return errors
    try: computed_bundle_hash = bundle_hash(bundle)
    except ValueError as exc: return [str(exc)]
    if bundle.get("schema") != BUNDLE_SCHEMA or bundle.get("bundleSha256") != computed_bundle_hash or not SHA.fullmatch(str(bundle.get("bundleSha256", ""))): errors.append("artifact evidence bundle must have an exact canonical digest")
    candidate = manifest["candidate"]
    if bundle.get("bundleSha256") != candidate.get("artifactEvidenceBundleSha256"): errors.append("candidate.artifactEvidenceBundleSha256 must bind supplied artifact bundle")
    for field, expected in (("sourceCommit", candidate.get("sourceCommit")), ("sourceTreeSha256", candidate.get("currentContractsSourceSha256")), ("sourceArchiveSha256", archive_digest), ("compiler", COMPILER), ("settings", SETTINGS)):
        if bundle.get(field) != expected: errors.append(f"artifact evidence bundle {field} does not bind canonical candidate")
    if bundle.get("solcBinarySha256") != SOLC_SHA256:
        errors.append("artifact evidence bundle does not pin the reviewed solc binary SHA-256")
    if any(path.startswith("v3-src/lib/openzeppelin-contracts/") for path in committed):
        # The exact gitlink is independently represented by all dependency blob
        # bytes in the closure; a READY caller must additionally supply this map.
        expected_gitlink = _submodule_gitlink(repo_root, str(candidate.get("sourceCommit")), "v3-src/lib/openzeppelin-contracts") if repo_root is not None else None
        if (not isinstance(bundle.get("submoduleGitlinks"), dict) or expected_gitlink is None
                or bundle["submoduleGitlinks"].get("v3-src/lib/openzeppelin-contracts") != expected_gitlink):
            errors.append("artifact evidence bundle lacks exact committed OpenZeppelin gitlink binding")
    elif bundle.get("submoduleGitlinks") != {}:
        errors.append("artifact evidence bundle has unexpected submodule gitlink binding")
    input_path = _rel_file(bundle_path, bundle.get("standardJsonInput"), "standardJsonInput", errors)
    output_path = _rel_file(bundle_path, bundle.get("standardJsonOutput"), "standardJsonOutput", errors)
    build_info_path = _rel_file(bundle_path, bundle.get("buildInfo"), "buildInfo", errors)
    if input_path is None or output_path is None or build_info_path is None: return errors
    input_bytes, output_bytes, build_info_bytes = input_path.read_bytes(), output_path.read_bytes(), build_info_path.read_bytes()
    try:
        standard_input = load_json_bytes(input_bytes, "standard-json input")
        supplied_output = load_json_bytes(output_bytes, "standard-json output")
        build_info = load_json_bytes(build_info_bytes, "Foundry build-info")
    except ValueError: return errors + ["standard-json input/output and build-info must be duplicate-free JSON"]
    if not isinstance(standard_input, dict) or not isinstance(supplied_output, dict) or not isinstance(build_info, dict): return errors + ["standard-json input/output and build-info must be objects"]
    if bundle.get("standardJsonInputSha256") != hashlib.sha256(input_bytes).hexdigest() or bundle.get("standardJsonOutputSha256") != hashlib.sha256(output_bytes).hexdigest(): errors.append("standard-json input/output byte digests mismatch")
    if bundle.get("buildInfoSha256") != hashlib.sha256(build_info_bytes).hexdigest(): errors.append("Foundry build-info byte digest mismatch")
    statement_path = _rel_file(bundle_path, bundle.get("deploymentVerificationStatement"), "deploymentVerificationStatement", errors)
    statement: dict[str, Any] | None = None
    if statement_path is not None:
        try:
            parsed = load_json_file(statement_path, "deployment verification statement", require_canonical_bytes=True)
            statement = parsed if isinstance(parsed, dict) else None
            if statement is None: errors.append("deployment verification statement must be an object")
        except ValueError as exc:
            errors.append(str(exc))
    build_id, build_sources = build_info.get("id"), build_info.get("source_id_to_path")
    if not isinstance(build_id, str) or not build_id or build_info.get("language") != "Solidity" or not isinstance(build_sources, dict):
        errors.append("Foundry build-info must provide id, Solidity language, and source path map")
    elif not set(SOURCE_NAMES.values()).issubset(set(build_sources.values())):
        errors.append("Foundry build-info source map does not identify all canonical contracts")
    settings = standard_input.get("settings", {})
    if not _metadata_settings_valid(settings): errors.append("standard-json input compiler settings are not exact 0.8.24 settings")
    sources = standard_input.get("sources", {})
    if not isinstance(sources, dict): errors.append("standard-json input lacks sources")
    expected_source_keys = {source_key_for_repo_path(path) for path in committed}
    if isinstance(sources, dict) and set(sources) != expected_source_keys:
        errors.append("standard-json source key set must exactly equal complete compilation closure")
    for source_path, source_bytes in committed.items():
        name = source_key_for_repo_path(source_path)
        source = sources.get(name, {}) if isinstance(sources, dict) else {}
        try:
            expected_content = source_bytes.decode("utf-8")
        except UnicodeDecodeError:
            errors.append(f"compilation source is not UTF-8: {name}"); continue
        if not isinstance(source, dict) or set(source) != {"content"} or source.get("content") != expected_content: errors.append(f"standard-json source does not exactly bind committed bytes: {name}")
    solc = _sealed_solc()
    if solc is None: return errors + ["sealed exact solc 0.8.24 compiler image is required to validate READY artifact evidence"]
    version = solc.run(["--version"])
    if version.returncode != 0 or b"0.8.24+commit.e11b9ed9" not in version.stdout:
        solc.close(); return errors + ["sealed exact solc 0.8.24 version check failed"]
    compiled_output, compile_errors = _standard_output(solc, input_bytes)
    solc.close()
    errors.extend(compile_errors)
    if compiled_output is None: return errors
    contracts = bundle.get("contracts", {})
    _exact_object(contracts, set(REQUIRED), "artifact evidence contracts", errors)
    runtime_templates: dict[str, bytes] = {}
    init_codes: dict[str, str] = {}
    immutable_maps: dict[str, list[dict[str, Any]]] = {}
    if not isinstance(contracts, dict): return errors
    for label in REQUIRED:
        item = contracts.get(label)
        _exact_object(item, BUNDLE_CONTRACT_KEYS, f"artifact evidence {label}", errors)
        if not isinstance(item, dict): continue
        if item.get("sourceName") != SOURCE_NAMES[label] or item.get("contractName") != label: errors.append(f"artifact evidence {label} contract identity mismatch")
        artifact_path = _rel_file(bundle_path, item.get("artifact"), f"artifact evidence {label}.artifact", errors)
        if artifact_path is None: continue
        artifact, artifact_errors = _artifact_object(artifact_path); errors.extend(artifact_errors)
        if artifact is None: continue
        if artifact.get("id") != build_id: errors.append(f"artifact evidence {label} is not bound to supplied Foundry build-info")
        metadata = artifact.get("metadata")
        if not isinstance(metadata, dict) or metadata.get("compiler", {}).get("version") != "0.8.24+commit.e11b9ed9" or not _metadata_settings_valid(metadata.get("settings")): errors.append(f"artifact evidence {label} lacks exact solc metadata/settings")
        compilation_target = metadata.get("settings", {}).get("compilationTarget", {}) if isinstance(metadata, dict) else {}
        if compilation_target != {SOURCE_NAMES[label]: label}: errors.append(f"artifact evidence {label} compilation target mismatch")
        creation, runtime = _code(artifact.get("bytecode")), _code(artifact.get("deployedBytecode"))
        if creation is None or runtime is None: errors.append(f"artifact evidence {label} has invalid bytecode"); continue
        creation_hash, runtime_hash = keccak256(creation), keccak256(runtime)
        runtime_templates[label] = runtime
        if item.get("creationCodeHash") != creation_hash or item.get("runtimeCodeHash") != runtime_hash: errors.append(f"artifact evidence {label} code hash mismatch")
        manifest_contract = manifest["contracts"][label]
        if manifest_contract.get("creationCodeHash") != creation_hash or manifest_contract.get("runtimeCodeHash") != runtime_hash: errors.append(f"manifest {label} code hash does not match recompiled artifact")
        supplied_contract = supplied_output.get("contracts", {}).get(SOURCE_NAMES[label], {}).get(label, {}) if isinstance(supplied_output, dict) else {}
        compiled_contract = compiled_output.get("contracts", {}).get(SOURCE_NAMES[label], {}).get(label, {})
        references = compiled_contract.get("evm", {}).get("deployedBytecode", {}).get("immutableReferences", {}) if isinstance(compiled_contract, dict) else {}
        if not isinstance(references, dict):
            errors.append(f"exact solc immutableReferences malformed for {label}"); references = {}
        immutable_maps[label] = [{"id": str(node_id), "ranges": [{"start": entry.get("start"), "length": entry.get("length")} for entry in locations]}
                                 for node_id, locations in sorted(references.items(), key=lambda pair: str(pair[0])) if isinstance(locations, list)]
        if _code(supplied_contract.get("evm", {}).get("bytecode")) != creation or _code(supplied_contract.get("evm", {}).get("deployedBytecode")) != runtime: errors.append(f"standard-json output does not match artifact {label}")
        if _code(compiled_contract.get("evm", {}).get("bytecode")) != creation or _code(compiled_contract.get("evm", {}).get("deployedBytecode")) != runtime: errors.append(f"exact solc output does not match artifact {label}")
        constructor = next((entry for entry in artifact.get("abi", []) if isinstance(entry, dict) and entry.get("type") == "constructor"), {"inputs": []})
        try: types = [_abi_type(entry) for entry in constructor.get("inputs", [])]
        except (TypeError, ValueError) as exc: errors.append(f"artifact evidence {label} constructor ABI invalid: {exc}"); continue
        if item.get("constructorTypes") != types: errors.append(f"artifact evidence {label} constructor ABI types mismatch")
        args = item.get("constructorArguments")
        if not isinstance(args, str) or not HEX.fullmatch(args): errors.append(f"artifact evidence {label} constructor args are invalid"); continue
        raw_args = bytes.fromhex(args[2:])
        try:
            if abi_encode(types, abi_decode(types, raw_args)) != raw_args: errors.append(f"artifact evidence {label} constructor args are not canonical ABI encoding")
        except Exception: errors.append(f"artifact evidence {label} constructor args do not decode against ABI")
        init_codes[label] = "0x" + (creation + raw_args).hex()
        init_hash = keccak256(creation + raw_args)
        if item.get("initCodeHash") != init_hash: errors.append(f"artifact evidence {label} init-code hash mismatch")
        if manifest_contract.get("constructorArguments") != args: errors.append(f"manifest {label} constructor arguments do not match ABI evidence")
        deployment = item.get("deploymentEvidence")
        _exact_object(deployment, EVIDENCE_KEYS, f"artifact evidence {label}.deploymentEvidence", errors)
        if not isinstance(deployment, dict): continue
        if deployment.get("address") != manifest_contract.get("address") or deployment.get("chainId") != CHAIN_ID or not TX_HASH.fullmatch(str(deployment.get("transactionHash", ""))) or deployment.get("initCodeHash") != init_hash or deployment.get("runtimeCodeHash") != runtime_hash: errors.append(f"artifact evidence {label} deployment record does not bind address/transaction/init-code/runtime")
    if statement is not None and isinstance(contracts, dict) and set(runtime_templates) == set(REQUIRED):
        errors.extend(validate_deployment_statement_shape(statement, manifest, bundle, runtime_templates, init_codes, immutable_maps))
        # Do not mistake an authenticated external assertion for local immutable
        # execution proof. Until every declared immutable can be reconstructed
        # from reviewed constructor/build-info/AST bindings, READY is impossible.
        for label, references in immutable_maps.items():
            if references:
                errors.append(f"{label}: deterministic immutable constructor/runtime binding is unavailable; READY fails closed")
        errors.extend(verify_signed_deployment_statement(statement, PRODUCTION_TRUST_ANCHOR_ED25519_HEX))
    return errors


def validate_ready_binding(value: dict[str, Any], repo_root: Path | None, source_archive: Path | None, artifact_bundle: Path | None) -> list[str]:
    candidate, errors = value.get("candidate", {}), []
    source_commit = candidate.get("sourceCommit")
    if not isinstance(source_commit, str) or not COMMIT.fullmatch(source_commit): return ["ready manifest requires a 40-hex candidate.sourceCommit"]
    if repo_root is None: errors.append("ready manifest validation requires --repo-root")
    if source_archive is None: errors.append("ready manifest validation requires --source-archive")
    if errors: return errors
    committed, committed_errors = committed_source_entries(repo_root, source_commit); errors.extend(committed_errors); errors.extend(check_clean_source_paths(repo_root, source_commit))
    archived, archive_errors = archive_entries(source_archive); errors.extend(archive_errors)
    if committed is not None and archived is not None:
        if committed != archived: errors.append("canonical source archive bytes do not equal candidate.sourceCommit sources")
        if candidate.get("currentContractsSourceSha256") != source_tree_hash(committed): errors.append("candidate.currentContractsSourceSha256 does not bind committed canonical source")
        archive_digest = hashlib.sha256(source_archive.read_bytes()).hexdigest()
        if candidate.get("sourceArchiveSha256") != archive_digest: errors.append("candidate.sourceArchiveSha256 does not bind supplied canonical archive")
        errors.extend(validate_artifact_bundle(value, value, committed, archive_digest, artifact_bundle, repo_root))
    return errors


def validate(value: dict[str, Any], repo_root: Path | None = None, source_archive: Path | None = None, artifact_bundle: Path | None = None) -> list[str]:
    errors: list[str] = []
    try: _assert_ascii_object_keys(value)
    except ValueError as exc: return [str(exc)]
    _exact_object(value, MANIFEST_KEYS, "manifest", errors)
    if not isinstance(value, dict): return errors
    if value.get("schema") != SCHEMA: errors.append("invalid schema")
    status = value.get("status")
    if status not in {BLOCKED, READY}: errors.append("manifest status must be blocked or READY_FOR_REHEARSAL")
    network = value.get("network", {}); _exact_object(network, NETWORK_KEYS, "network", errors)
    if not isinstance(network, dict) or network.get("chainId") != CHAIN_ID or network.get("mainnetRejected") is not True or network.get("productionFundsProhibited") is not True or network.get("rpcPolicy") != "READ_ONLY_BASE_SEPOLIA_ONLY": errors.append("must be Base Sepolia-only with mainnet rejection")
    candidate = value.get("candidate", {}); _exact_object(candidate, CANDIDATE_KEYS, "candidate", errors)
    if not isinstance(candidate, dict) or candidate.get("compiler") != COMPILER or candidate.get("settings") != SETTINGS: errors.append("exact 0.8.24 compiler settings required")
    rules = value.get("objectiveRuleSchema", {}); _exact_object(rules, RULE_KEYS, "objectiveRuleSchema", errors)
    if not isinstance(rules, dict) or rules.get("version") != 1 or rules.get("allowedClasses") != [] or rules.get("opaquePolicyHashesRejected") is not True: errors.append("Base pilot must expose no unsupported objective custody rule classes")
    elif set(rules.get("prohibitedCategories", [])) != PROHIBITED: errors.append("all prohibited semantic categories, including surveillance, must be enumerated")
    if not isinstance(rules, dict) or rules.get("mutualConsentSettlementOnly") is not True or rules.get("disputesChangeCustody") is not False: errors.append("Base pilot must declare mutual-consent-only custody and disabled dispute custody effect")
    if set(value.get("excludedSurface", [])) != FORBIDDEN: errors.append("excluded surface drift")
    contracts = value.get("contracts", {}); _exact_object(contracts, set(REQUIRED), "contracts", errors)
    if isinstance(contracts, dict):
        for label in REQUIRED:
            item = contracts.get(label, {}); _exact_object(item, CONTRACT_KEYS, label, errors)
            if isinstance(item, dict):
                address = item.get("address")
                if address is not None and (not isinstance(address, str) or not ADDRESS.fullmatch(address) or int(address, 16) == 0): errors.append(f"{label}.address must be a nonzero public address or null")
    _exact_object(value.get("roles", {}), ROLE_KEYS, "roles", errors)
    _exact_object(value.get("capsAtomic", {}), CAP_KEYS, "capsAtomic", errors)
    claimed = value.get("artifactHash")
    try: computed = content_hash(value)
    except ValueError as exc: errors.append(str(exc)); computed = None
    if not isinstance(claimed, str) or not SHA.fullmatch(claimed): errors.append("artifactHash must be a mandatory canonical SHA-256 content hash")
    elif claimed != computed: errors.append("artifactHash mismatch")
    if status == READY and isinstance(contracts, dict) and isinstance(candidate, dict):
        for key in ("currentContractsSourceSha256", "sourceArchiveSha256", "artifactEvidenceBundleSha256"):
            if not SHA.fullmatch(str(candidate.get(key, ""))): errors.append(f"ready candidate.{key} must be a canonical SHA-256")
        for label in REQUIRED:
            item = contracts.get(label, {})
            if not isinstance(item, dict) or not ADDRESS.fullmatch(str(item.get("address", ""))) or int(str(item.get("address", "0")), 16) == 0: errors.append(f"{label}.address must be a nonzero public address")
            elif not HASH32.fullmatch(str(item.get("creationCodeHash", ""))) or not HASH32.fullmatch(str(item.get("runtimeCodeHash", ""))) or not HEX.fullmatch(str(item.get("constructorArguments", ""))): errors.append(f"{label} must bind exact artifact code hashes and ABI constructor bytes")
        roles = value.get("roles", {})
        if not isinstance(roles, dict) or not roles.get("governanceSigners") or not roles.get("guardian") or not roles.get("designPartner"): errors.append("ready manifest lacks human-assigned roles")
        errors.extend(validate_ready_binding(value, repo_root, source_archive, artifact_bundle))
    return errors


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--manifest", type=Path, required=True); parser.add_argument("--repo-root", type=Path); parser.add_argument("--source-archive", type=Path); parser.add_argument("--artifact-evidence-bundle", type=Path)
    parser.add_argument("--artifact-evidence-attestation-out", type=Path)
    args = parser.parse_args()
    try:
        value = load_json_file(args.manifest, "manifest")
    except ValueError as exc:
        print(json.dumps({"valid": False, "ready": False, "errors": [str(exc)]}, indent=2))
        return 2
    errors = validate(value, args.repo_root, args.source_archive, args.artifact_evidence_bundle)
    if value.get("status") == READY and PRODUCTION_TRUST_ANCHOR_ED25519_HEX is None:
        errors.append("production READY is disabled until an independently pinned deployment-verification trust anchor exists")
    if args.artifact_evidence_attestation_out is not None:
        if errors or value.get("status") != READY or args.artifact_evidence_bundle is None:
            errors.append("validated READY evidence is required before writing an SDK attestation")
        else:
            try:
                bundle = load_json_file(args.artifact_evidence_bundle, "artifact evidence bundle")
                args.artifact_evidence_attestation_out.write_text(json.dumps(ready_attestation(value, bundle), sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8")
            except (OSError, KeyError, ValueError) as exc:
                errors.append(f"cannot write artifact evidence attestation: {exc}")
    print(json.dumps({"valid": not errors, "status": value.get("status"), "ready": value.get("status") == READY and not errors, "errors": errors, "computedArtifactHash": content_hash(value)}, indent=2, sort_keys=True))
    return 0 if not errors else 2

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