#!/usr/bin/env python3
"""Fail-closed validation for the additive V3 governance/operations package."""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from Crypto.Hash import keccak


PACKAGE = Path("v3-operations")
FILES = {
    "selectors": PACKAGE / "governance-selector-policy.json",
    "roles": PACKAGE / "role-control-policy.json",
    "operations": PACKAGE / "operations-control.json",
    "release": PACKAGE / "release-status.json",
    "supersession": PACKAGE / "readiness-supersession.json",
    "enforcement": PACKAGE / "governance-enforcement.json",
    "commitment": PACKAGE / "policy-commitment.json",
    "release_schema": PACKAGE / "schemas/release-status.schema.json",
    "role_schema": PACKAGE / "schemas/role-control.schema.json",
    "selector_schema": PACKAGE / "schemas/selector-policy.schema.json",
    "supported": Path("v3-migration/completeness/supported-v3-modules.json"),
    "dispositions": Path("v3-migration/completeness/v2-contract-dispositions.json"),
    "pilot": Path("pr8/pilot/base_v3_deployment_manifest.json"),
    "authorityFacts": Path("v3-migration/completeness/current-authority-facts.json"),
}
REQUIRED_ROLES = {
    "owner",
    "governance",
    "guardian",
    "treasury",
    "attesters",
    "adjudicators",
    "release-attester",
    "incident-responders",
    "reconciliation-responders",
}
REQUIRED_EXTERNAL = {
    "partner-acceptance",
    "privacy",
    "counsel",
    "independent-audit",
}
PROHIBITED_INPUTS = {
    "belief",
    "flourishing",
    "reputation",
    "trust-score",
    "behavioral-routing",
}
ROLE_MINIMA = {
    "owner": (3, 5),
    "governance": (3, 5),
    "guardian": (1, 1),
    "treasury": (2, 3),
    "attesters": (2, 3),
    "adjudicators": (2, 3),
    "release-attester": (1, 1),
    "incident-responders": (2, 3),
    "reconciliation-responders": (2, 3),
}
REQUIRED_SEPARATION = {
    tuple(sorted(pair))
    for pair in [
        ("guardian", "governance"), ("guardian", "treasury"),
        ("guardian", "attesters"), ("guardian", "adjudicators"),
        ("guardian", "release-attester"), ("governance", "treasury"),
        ("governance", "attesters"), ("governance", "adjudicators"),
        ("governance", "release-attester"), ("treasury", "attesters"),
        ("treasury", "adjudicators"), ("treasury", "release-attester"),
        ("attesters", "adjudicators"), ("attesters", "release-attester"),
        ("adjudicators", "release-attester"),
        ("release-attester", "incident-responders"),
        ("release-attester", "reconciliation-responders"),
        ("incident-responders", "reconciliation-responders"),
    ]
}
GUARDIAN_FORBIDDEN = {
    "unpause", "execute governance proposal", "change roles", "change caps",
    "enable deploy or writes", "move funds", "rotate guardian",
}


class GateError(ValueError):
    """Raised when one or more fail-closed gates reject a package."""


def _read_json(path: Path) -> Any:
    def reject_duplicates(pairs):
        result = {}
        for key, value in pairs:
            if key in result:
                raise GateError(f"duplicate JSON key in {path}: {key}")
            result[key] = value
        return result

    with path.open(encoding="utf-8") as handle:
        return json.load(handle, object_pairs_hook=reject_duplicates)


def load_package(root: Path) -> dict[str, Any]:
    return {name: _read_json(root / path) for name, path in FILES.items()}


def _selector(signature: str) -> str:
    digest = keccak.new(digest_bits=256)
    digest.update(signature.encode("ascii"))
    return "0x" + digest.hexdigest()[:8]


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


def _schema_resolve(schema: dict[str, Any], ref: str) -> dict[str, Any]:
    node: Any = schema
    for token in ref.removeprefix("#/").split("/"):
        node = node[token.replace("~1", "/").replace("~0", "~")]
    return node


def _validate_schema(
    value: Any,
    rule: dict[str, Any],
    schema: dict[str, Any],
    path: str,
    errors: list[str],
) -> None:
    if "$ref" in rule:
        _validate_schema(value, _schema_resolve(schema, rule["$ref"]), schema, path, errors)
        return
    if "const" in rule and value != rule["const"]:
        errors.append(f"schema {path}: expected constant {rule['const']!r}")
    if "enum" in rule and value not in rule["enum"]:
        errors.append(f"schema {path}: value is outside enum")
    expected = rule.get("type")
    type_ok = (
        expected is None
        or (expected == "object" and isinstance(value, dict))
        or (expected == "array" and isinstance(value, list))
        or (expected == "string" and isinstance(value, str))
        or (expected == "boolean" and isinstance(value, bool))
        or (expected == "integer" and isinstance(value, int) and not isinstance(value, bool))
    )
    if not type_ok:
        errors.append(f"schema {path}: expected {expected}")
        return
    if isinstance(value, dict):
        for required in rule.get("required", []):
            if required not in value:
                errors.append(f"schema {path}: missing {required}")
        properties = rule.get("properties", {})
        if rule.get("additionalProperties") is False:
            for key in value:
                if key not in properties:
                    errors.append(f"schema {path}: unexpected property {key}")
        for key, child in properties.items():
            if key in value:
                _validate_schema(value[key], child, schema, f"{path}.{key}", errors)
    if isinstance(value, list):
        if len(value) < rule.get("minItems", 0):
            errors.append(f"schema {path}: too few items")
        if "maxItems" in rule and len(value) > rule["maxItems"]:
            errors.append(f"schema {path}: too many items")
        if rule.get("uniqueItems"):
            serial = [json.dumps(item, sort_keys=True) for item in value]
            if len(serial) != len(set(serial)):
                errors.append(f"schema {path}: duplicate items")
        if "items" in rule:
            for index, item in enumerate(value):
                _validate_schema(item, rule["items"], schema, f"{path}[{index}]", errors)
    if isinstance(value, str):
        if len(value) < rule.get("minLength", 0):
            errors.append(f"schema {path}: string too short")
        if rule.get("pattern") and not re.search(rule["pattern"], value):
            errors.append(f"schema {path}: pattern mismatch")
    if isinstance(value, int) and not isinstance(value, bool):
        if value < rule.get("minimum", value):
            errors.append(f"schema {path}: below minimum")
        if value > rule.get("maximum", value):
            errors.append(f"schema {path}: above maximum")


def _target_signatures(root: Path, module: str, module_paths: dict[str, str]) -> set[str]:
    artifact = root / f"v3-src/out/{module}.sol/{module}.json"
    source_path = module_paths.get(module)
    source_file = root / source_path if source_path else None
    if artifact.is_file() and source_file and artifact.stat().st_mtime_ns >= source_file.stat().st_mtime_ns:
        abi = _read_json(artifact).get("abi", [])
        return {
            f"{item['name']}({','.join(arg['type'] for arg in item.get('inputs', []))})"
            for item in abi
            if item.get("type") == "function"
        }
    if not source_path or not (root / source_path).is_file():
        return set()
    source = (root / source_path).read_text(encoding="utf-8")
    signatures = set()
    for match in re.finditer(r"\bfunction\s+(\w+)\s*\((.*?)\)\s*(?:external|public)", source, re.S):
        name, params = match.groups()
        types = []
        for param in ([] if not params.strip() else params.split(",")):
            tokens = re.sub(r"\s+", " ", param.strip()).split(" ")
            type_name = tokens[0]
            if len(tokens) > 1 and tokens[1] in {"memory", "calldata", "storage"}:
                pass
            types.append(type_name)
        signatures.add(f"{name}({','.join(types)})")
    return signatures


def validate_proposal_candidate(
    data: dict[str, Any], proposal: dict[str, Any]
) -> list[str]:
    """Validate a candidate proposal's committed selector/value/canonical ABI bytes."""
    errors: list[str] = []
    if proposal.get("candidateId") != data["commitment"].get("candidateId"):
        errors.append("proposal candidate commitment mismatch")
    try:
        raw = bytes.fromhex(proposal.get("calldataHex", "").removeprefix("0x"))
    except ValueError:
        return ["proposal calldata is not hex"]
    if len(raw) < 4:
        return ["proposal calldata is shorter than a selector"]
    selector = "0x" + raw[:4].hex()
    matches = [
        row for row in data["selectors"].get("allowlist", [])
        if row.get("targetModule") == proposal.get("targetModule")
        and row.get("selector") == selector
    ]
    if len(matches) != 1:
        return ["proposal target/selector is not uniquely allowlisted"]
    rule = matches[0]
    if str(proposal.get("valueWei")) != rule.get("maxValueWei") or proposal.get("valueWei") != "0":
        errors.append("proposal native value is prohibited")
    if len(raw) > rule.get("maxCalldataBytes", 0):
        errors.append("proposal calldata exceeds policy limit")
    signature = rule["signature"]
    types_text = signature.split("(", 1)[1].rsplit(")", 1)[0]
    types = [] if not types_text else types_text.split(",")
    words = raw[4:]
    if types == ["address[]", "uint256"]:
        if len(words) < 96 or int.from_bytes(words[0:32], "big") != 64:
            errors.append("setSigners has noncanonical dynamic offset")
            return errors
        count = int.from_bytes(words[64:96], "big")
        expected = 96 + count * 32
        if len(words) != expected:
            errors.append("setSigners has trailing, truncated, or noncanonical calldata")
            return errors
        signers = [words[96 + i * 32 + 12:96 + (i + 1) * 32] for i in range(count)]
        threshold = int.from_bytes(words[32:64], "big")
        if not 5 <= count <= 32 or threshold < 3 or threshold > count:
            errors.append("setSigners violates population or threshold policy")
        if any(s == b"\0" * 20 for s in signers) or len(signers) != len(set(signers)):
            errors.append("setSigners contains zero or duplicate subjects")
        return errors
    if types == ["address", "bytes"]:
        if len(words) < 96 or int.from_bytes(words[32:64], "big") != 64:
            errors.append("dynamic bytes call has noncanonical offset")
            return errors
        address_word = int.from_bytes(words[0:32], "big")
        length = int.from_bytes(words[64:96], "big")
        padded = ((length + 31) // 32) * 32
        if address_word == 0 or address_word >> 160 != 0:
            errors.append("argument 0 is not a canonical nonzero address")
        if len(words) != 96 + padded or any(words[96 + length:]):
            errors.append("dynamic bytes call has trailing, truncated, or nonzero padding")
        return errors
    if len(words) != 32 * len(types):
        errors.append("proposal has trailing, truncated, or noncanonical static calldata")
        return errors
    for index, type_name in enumerate(types):
        word = words[index * 32:(index + 1) * 32]
        number = int.from_bytes(word, "big")
        if type_name == "address" and (number >> 160 != 0 or number == 0):
            errors.append(f"argument {index} is not a canonical nonzero address")
        elif type_name == "bool" and number not in (0, 1):
            errors.append(f"argument {index} is not a canonical bool")
        elif type_name == "uint8" and number > 255:
            errors.append(f"argument {index} is not a canonical uint8")
        elif type_name == "uint96" and number >= 2**96:
            errors.append(f"argument {index} is not a canonical uint96")
        elif type_name == "bytes4" and any(word[4:]):
            errors.append(f"argument {index} is not canonical bytes4")
    return errors


def _parse_utc(value: str, label: str, errors: list[str]) -> datetime | None:
    try:
        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
        if parsed.tzinfo is None:
            raise ValueError("timezone missing")
        return parsed.astimezone(timezone.utc)
    except (TypeError, ValueError) as exc:
        errors.append(f"{label}: invalid UTC timestamp: {exc}")
        return None


def validate_package_data(
    data: dict[str, Any], *, now: datetime | None = None, root: Path | None = None
) -> list[str]:
    errors: list[str] = []
    root = (root or Path(__file__).resolve().parents[1]).resolve()
    now = (now or datetime.now(timezone.utc)).astimezone(timezone.utc)
    selectors = data["selectors"]
    roles = data["roles"]
    operations = data["operations"]
    release = data["release"]
    enforcement = data["enforcement"]
    commitment = data["commitment"]
    pilot = data["pilot"]
    supported = data["supported"]
    dispositions = data["dispositions"]
    authority_facts = data["authorityFacts"]

    # Invoke the checked-in schemas before semantic checks.
    for value_key, schema_key in (
        ("selectors", "selector_schema"),
        ("roles", "role_schema"),
        ("release", "release_schema"),
    ):
        _validate_schema(
            data[value_key], data[schema_key], data[schema_key], value_key, errors
        )

    # Canonical source links and inventory closure.
    modules = supported.get("modules", [])
    module_names = {entry.get("module") for entry in modules}
    module_paths = {entry.get("path") for entry in modules}
    path_by_module = {entry.get("module"): entry.get("path") for entry in modules}
    if len(modules) != 15 or len(module_names) != 15:
        errors.append("supported module inventory must contain 15 unique remediated modules")
    for entry in modules:
        if not (root / entry.get("path", "")).is_file():
            errors.append(f"supported module path does not exist: {entry.get('path')}")
        tags = set(entry.get("capabilityTags", []))
        decisions = set(entry.get("allowedProtectedDecisions", []))
        if tags & PROHIBITED_INPUTS and decisions:
            errors.append(f"unsafe supported module influences protected decisions: {entry.get('module')}")
        if entry.get("module") in {"StreetCredV3", "ERC8004ValidationRegistryV3Safe"} and decisions:
            errors.append(f"informational module has protected decisions: {entry.get('module')}")
        if not entry.get("boundaryRule"):
            errors.append(f"supported module lacks boundary rule: {entry.get('module')}")
        if decisions:
            source = (root / entry.get("path", "")).read_text(encoding="utf-8")
            if re.search(r'import\s+[^;]*(?:/reputation/|StreetCredV3|ERC8004ValidationRegistryV3Safe)', source):
                errors.append(f"protected-decision module imports informational reputation source: {entry.get('module')}")
            if re.search(r'\b(?:StreetCredV3|ERC8004ValidationRegistryV3Safe)\s*\(', source):
                errors.append(f"protected-decision module calls informational reputation source: {entry.get('module')}")
    enabled = set(pilot.get("scope", {}).get("enabled", []))
    unsupported_pilot = enabled - module_names
    scope_blocker = next(
        (
            item
            for item in release.get("blockers", [])
            if item.get("id") == "BASE-PILOT-SUPPORTED-SCOPE-001"
        ),
        None,
    )
    if unsupported_pilot:
        if not scope_blocker or scope_blocker.get("status") != "open" or scope_blocker.get("category") != "source":
            errors.append(
                f"pilot enables unsupported modules without an open source blocker: {sorted(unsupported_pilot)}"
            )
    elif scope_blocker and scope_blocker.get("status") == "open":
        errors.append("BASE-PILOT-SUPPORTED-SCOPE-001 is stale after scope reconciliation")
    if dispositions.get("recordCount") != 36 or len(dispositions.get("records", [])) != 36:
        errors.append("disposition authority must contain exactly 36 records")
    source_authorities = selectors.get("sourceAuthorities", {})
    if source_authorities.get("supportedModules", {}).get("path") != str(FILES["supported"]):
        errors.append("selector policy is not linked to the canonical supported-module manifest")
    if source_authorities.get("supportedModules", {}).get("requiredSchemaVersion") != supported.get("schemaVersion"):
        errors.append("supportedModules requiredSchemaVersion does not match canonical manifest")
    if source_authorities.get("contractDispositions", {}).get("path") != str(
        FILES["dispositions"]
    ):
        errors.append("selector policy is not linked to the canonical 36-contract disposition manifest")
    if source_authorities.get("contractDispositions", {}).get("requiredRecordCount") != 36:
        errors.append("contract disposition requiredRecordCount must remain 36")
    for record in dispositions.get("records", []):
        for target in record.get("exactTargetModules", []):
            if target not in module_paths:
                errors.append(f"disposition target is not supported: {target}")

    # Selector scope, value, calldata, and mission boundary.
    if selectors.get("defaultAction") != "deny":
        errors.append("selector policy default action must be deny")
    if set(selectors.get("prohibitedInputClasses", [])) != PROHIBITED_INPUTS:
        errors.append("selector policy must carry the complete prohibited input set")
    classes = {
        item.get("id"): item for item in selectors.get("missionBoundaryClasses", [])
    }
    seen_rules: set[tuple[str, str]] = set()
    seen_rule_ids: set[str] = set()
    for rule in selectors.get("allowlist", []):
        label = f"selector rule {rule.get('id', '<missing>')}"
        target = rule.get("targetModule")
        signature = rule.get("signature", "")
        key = (str(target), str(rule.get("selector")))
        if rule.get("id") in seen_rule_ids:
            errors.append(f"{label}: duplicate allowlist id")
        seen_rule_ids.add(rule.get("id"))
        if target not in module_names:
            errors.append(f"{label}: target is outside supported-v3-modules.json")
        if key in seen_rules:
            errors.append(f"{label}: duplicate target/selector scope")
        seen_rules.add(key)
        try:
            expected = _selector(signature)
        except (UnicodeEncodeError, TypeError):
            expected = "<invalid>"
        if rule.get("selector") != expected:
            errors.append(f"{label}: selector does not match canonical signature")
        if signature not in _target_signatures(root, str(target), path_by_module):
            errors.append(f"{label}: signature absent from compiled ABI or exact target source")
        if rule.get("maxValueWei") != "0":
            errors.append(f"{label}: governance native value must be zero")
        calldata = rule.get("maxCalldataBytes")
        if not isinstance(calldata, int) or not 4 <= calldata <= 24576:
            errors.append(f"{label}: calldata limit is missing or out of bounds")
        if rule.get("timelockSecondsMinimum", 0) < 86400:
            errors.append(f"{label}: timelock is below 24 hours")
        boundary = classes.get(rule.get("missionBoundaryClass"))
        if not boundary:
            errors.append(f"{label}: unknown mission-boundary class")
        elif boundary.get("unsafeInputsAllowed") is not False:
            errors.append(f"{label}: mission-boundary class permits unsafe inputs")
        if rule.get("requiredAuthority") != "governance-threshold-timelock":
            errors.append(f"{label}: authority weakened")
        constraints = rule.get("argumentConstraints", [])
        if not constraints:
            errors.append(f"{label}: argument constraints missing")
        if signature != "setSigners(address[],uint256)" and calldata != 4 + 32 * signature.count(",") + (32 if "(" in signature and signature.split("(",1)[1] != ")" else 0):
            # Exact static calldata is independently verified by ABI arity below.
            arity = 0 if signature.endswith("()") else signature.split("(", 1)[1].rsplit(")", 1)[0].count(",") + 1
            if calldata != 4 + 32 * arity:
                errors.append(f"{label}: static calldata limit is not canonical exact length")
        if signature == "setSigners(address[],uint256)" and calldata != 1124:
            errors.append(f"{label}: setSigners must support the contract maximum of 32 signers")
    deny_ids = {item.get("id") for item in selectors.get("denylist", [])}
    if len(deny_ids) != len(selectors.get("denylist", [])):
        errors.append("selector denylist contains duplicate ids")
    for deny in selectors.get("denylist", []):
        if not deny.get("match"):
            errors.append(f"deny rule {deny.get('id')}: empty match semantics")
    for required in {"DENY-UNKNOWN", "DENY-NATIVE-VALUE", "DENY-UNSAFE-INPUT"}:
        if required not in deny_ids:
            errors.append(f"selector denylist is missing {required}")

    # Role threshold, staffing truth, separation, and responder coverage.
    role_rows = roles.get("roles", [])
    by_role = {row.get("id"): row for row in role_rows}
    if set(by_role) != REQUIRED_ROLES or len(role_rows) != len(REQUIRED_ROLES):
        errors.append("role policy must define each required role exactly once")
    for role_id, row in by_role.items():
        threshold = row.get("threshold", {})
        members = row.get("members", [])
        minimum = threshold.get("minimum", 0)
        population = threshold.get("populationMinimum", 0)
        expected_minimum, expected_population = ROLE_MINIMA.get(role_id, (999, 999))
        if (minimum, population) != (expected_minimum, expected_population):
            errors.append(f"role {role_id}: immutable minimum must be {expected_minimum}-of-{expected_population}")
        if minimum < 1 or population < minimum:
            errors.append(f"role {role_id}: invalid threshold/population minimum")
        status = row.get("assignmentStatus")
        if status == "evidenced" and len(members) < population:
            errors.append(f"role {role_id}: evidenced staffing is below population minimum")
        if status == "unassigned-blocking" and members:
            errors.append(f"role {role_id}: unassigned role cannot list members")
    for rule in roles.get("separationRules", []):
        left, right = rule.get("left"), rule.get("right")
        if left not in by_role or right not in by_role:
            errors.append(f"role separation references unknown role: {left}/{right}")
            continue
        overlap = set(by_role[left].get("members", [])) & set(
            by_role[right].get("members", [])
        )
        if overlap:
            errors.append(f"role overlap prohibited for {left}/{right}: {sorted(overlap)}")
    actual_separation = {
        tuple(sorted((row.get("left"), row.get("right"))))
        for row in roles.get("separationRules", [])
        if row.get("overlapAllowed") is False
    }
    if actual_separation != REQUIRED_SEPARATION:
        errors.append("complete required role separation matrix changed")
    if by_role.get("owner", {}).get("binding") != "governance-contract":
        errors.append("owner must be a governance-contract binding, not a parallel roster")
    lifecycle = roles.get("keyLifecycle", {})
    rotation = lifecycle.get("rotation", {})
    revocation = lifecycle.get("revocation", {})
    if lifecycle.get("inventoryRequired") is not True or lifecycle.get("keyMaterialProhibited") is not True:
        errors.append("key lifecycle inventory/key-material controls weakened")
    if rotation.get("scheduledMaximumAgeDays") != 180 or len(rotation.get("preconditions", [])) < 5 or len(rotation.get("postconditions", [])) < 4:
        errors.append("key rotation controls weakened")
    if revocation.get("maximumAcknowledgeMinutes") != 15 or len(revocation.get("triggerClasses", [])) < 6:
        errors.append("key revocation controls weakened")
    conflict = roles.get("recusalAndConflict", {})
    quorum = conflict.get("quorumAlgorithm", {})
    if (
        conflict.get("disclosureRequiredBeforeAction") is not True
        or conflict.get("recusalRequiredFor") is None
        or len(conflict.get("recusalRequiredFor", [])) < 5
        or conflict.get("recusedSubjectsExcludedFromNumeratorAndDenominator") is not True
        or conflict.get("minimumNonConflictedThresholdStillRequired") is not True
        or set(quorum) != {"eligiblePopulation", "approvalNumerator", "requiredApprovals", "failClosedCondition"}
    ):
        errors.append("recusal/conflict controls weakened")
    responder_roles = roles.get("responderCoverage", {}).get("requiredRoles", [])
    if set(responder_roles) != {"incident-responders", "reconciliation-responders"}:
        errors.append("both incident and reconciliation responder roles are mandatory")
    coverage = roles.get("responderCoverage", {})
    if (
        coverage.get("minimumDistinctSubjectsPerRole") != 3
        or coverage.get("minimumAcknowledgersPerIncident") != 2
        or coverage.get("continuousCoverageEvidenceRequired") is not True
        or coverage.get("escalationRouteEvidenceRequired") is not True
    ):
        errors.append("responder minima or evidence controls weakened")

    # Operations definitions must retain bounded recovery and required observations.
    guardian = operations.get("guardian", {})
    if guardian.get("allowedAction") != "guardianPause(address)":
        errors.append("guardian authority must be pause-only")
    recovery = guardian.get("recovery", {})
    if recovery.get("minimumDelaySeconds", 0) < 86400 or not recovery.get(
        "noEmergencyBypass"
    ):
        errors.append("guardian recovery must be timelocked at least 24 hours with no bypass")
    if set(guardian.get("forbiddenActions", [])) != GUARDIAN_FORBIDDEN:
        errors.append("guardian forbidden-action controls weakened")
    if len(guardian.get("pauseEffectsRequired", [])) < 4 or len(recovery.get("requiredBeforeUnpause", [])) < 7:
        errors.append("guardian pause/recovery prerequisites weakened")
    monitors = {row.get("id") for row in operations.get("monitoring", [])}
    expected_monitors = {
        "MON-LIABILITIES",
        "MON-CREDITS",
        "MON-CAPS",
        "MON-PAUSED-STATES",
        "MON-RECEIPT-STORE",
        "MON-REORG",
        "MON-SOLANA-PROGRAM",
        "MON-X402-SETTLEMENT",
    }
    if not expected_monitors <= monitors:
        errors.append(f"monitoring definitions missing: {sorted(expected_monitors - monitors)}")
    drills = {row.get("id") for row in operations.get("drills", [])}
    if drills != {"DRILL-DEPLOYMENT", "DRILL-ROLLBACK"}:
        errors.append("deployment and rollback drills must both be defined")
    for drill in operations.get("drills", []):
        if len(drill.get("steps", [])) < 6 or len(drill.get("passCriteria", [])) < 4:
            errors.append(f"drill {drill.get('id')}: steps or pass criteria weakened")
        if set(drill.get("prohibited", [])) != {"wallet signing", "RPC broadcast", "mainnet activation"}:
            errors.append(f"drill {drill.get('id')}: prohibited actions weakened")
    freshness = operations.get("freshness", {})
    expected_freshness = {
        "canonicalStatusMaximumAgeHours": 24,
        "sourceEvidenceMaximumAgeHours": 24,
        "operationsEvidenceMaximumAgeHours": 24,
        "externalSignoffMaximumAgeDays": 30,
        "authorizationMaximumAgeHours": 24,
        "futureTimestampsRejected": True,
    }
    if freshness != expected_freshness:
        errors.append("freshness policy changed or weakened")
    if not PROHIBITED_INPUTS <= set(operations.get("prohibitedEvidenceInputs", [])):
        errors.append("operations controls omit prohibited evidence inputs")

    # Release progression is strictly monotonic and blocked by open prerequisites.
    if release.get("canonicalAuthority") is not True:
        errors.append("release status must identify itself as canonical authority")
    stages = [
        "sourceComplete",
        "evidenceComplete",
        "deploymentAuthorized",
        "deployed",
        "activated",
    ]
    if release.get("partnerReady") is True:
        errors.append("partnerReady overclaim: repository partner evidence remains blocked")
    for later, earlier in zip(stages[1:], stages):
        if release.get(later) and not release.get(earlier):
            errors.append(f"readiness overclaim: {later} requires {earlier}")
    external = release.get("externalBlockers", [])
    if {row.get("category") for row in external} != REQUIRED_EXTERNAL:
        errors.append("external blockers must be exactly partner acceptance, privacy, counsel, and independent audit")
    for blocker in external:
        if blocker.get("status") == "satisfied" and not blocker.get("evidenceRefs"):
            errors.append(f"external blocker {blocker.get('id')} is satisfied without evidence")
    all_blockers = external + release.get("blockers", [])
    blocker_ids = [row.get("id") for row in all_blockers]
    if len(blocker_ids) != len(set(blocker_ids)):
        errors.append("release status contains duplicate blocker ids")
    open_blockers = [row.get("id") for row in all_blockers if row.get("status") == "open"]
    if release.get("sourceComplete") and any(
        row.get("status") == "open" and row.get("category") == "source"
        for row in all_blockers
    ):
        errors.append("readiness overclaim: sourceComplete with an open source blocker")
    if release.get("evidenceComplete") and open_blockers:
        errors.append("readiness overclaim: evidenceComplete with open blockers")
    authorization = release.get("humanAuthorization", {})
    if authorization.get("recorded") and not authorization.get("evidenceRefs"):
        errors.append("human authorization is recorded without evidence")
    if release.get("deploymentAuthorized") and not authorization.get("recorded"):
        errors.append("deployment authorization requires recorded human authorization")
    deployment_auth = release.get("deploymentAuthorization", {})
    activation_auth = release.get("activationAuthorization", {})
    if release.get("deploymentAuthorized") and not deployment_auth.get("recorded"):
        errors.append("deployment authorization requires separate deployment authorization evidence")
    if release.get("activated") and not activation_auth.get("recorded"):
        errors.append("activation requires separate activation authorization evidence")
    if release.get("activated") and not authorization.get("recorded"):
        errors.append("mainnet activation without human authorization is prohibited")
    if (release.get("deploymentAuthorized") or release.get("activated")) and open_blockers:
        errors.append("authorization or activation is prohibited while blockers are open")
    prohibited_observed = set(release.get("observedInputClasses", [])) & PROHIBITED_INPUTS
    if prohibited_observed:
        errors.append(
            "prohibited inputs observed in release evidence: "
            + ", ".join(sorted(prohibited_observed))
        )

    as_of = _parse_utc(release.get("asOfUtc"), "release asOfUtc", errors)
    if as_of and as_of > now:
        errors.append("release status timestamp is in the future")
    if as_of and (now - as_of).total_seconds() > freshness.get("canonicalStatusMaximumAgeHours", 0) * 3600:
        errors.append("canonical release status is stale")
    evidence_records = release.get("evidenceRecords", [])
    if release.get("evidenceComplete") and not evidence_records:
        errors.append("evidenceComplete requires fresh evidence records")
    for record in evidence_records:
        observed = _parse_utc(
            record.get("observedAtUtc"), f"evidence {record.get('id')}", errors
        )
        maximum = record.get("maximumAgeHours", 0)
        if observed and observed > now:
            errors.append(f"evidence {record.get('id')}: future timestamp")
        if observed and (now - observed).total_seconds() > maximum * 3600:
            errors.append(f"stale evidence: {record.get('id')}")
        if not record.get("evidenceRefs"):
            errors.append(f"evidence {record.get('id')}: missing evidence reference")

    # Every populated reference must exist. Satisfied evidence is candidate-bound.
    for record in all_blockers:
        for reference in record.get("evidenceRefs", []):
            if not (root / reference).is_file():
                errors.append(f"blocker {record.get('id')}: evidence reference does not exist: {reference}")
        if record.get("status") == "satisfied":
            for reference in record.get("evidenceRefs", []):
                evidence = _read_json(root / reference)
                if evidence.get("candidateBinding") != commitment.get("candidateId"):
                    errors.append(f"blocker {record.get('id')}: evidence is not candidate-bound")

    # Canonical JSON/raw digests and generated Solidity constants bind the candidate.
    commitment_rows = commitment.get("artifacts", [])
    if len({row.get("path") for row in commitment_rows}) != len(commitment_rows):
        errors.append("policy commitment contains duplicate artifact paths")
    for row in commitment_rows:
        path = root / row.get("path", "")
        if not path.is_file():
            errors.append(f"committed artifact missing: {row.get('path')}")
            continue
        raw = path.read_bytes()
        parsed = _read_json(path)
        if hashlib.sha256(raw).hexdigest() != row.get("sha256"):
            errors.append(f"raw artifact digest mismatch: {row.get('path')}")
        if hashlib.sha256(_canonical_json(parsed)).hexdigest() != row.get("canonicalJsonSha256"):
            errors.append(f"canonical artifact digest mismatch: {row.get('path')}")
    implementation_rows = commitment.get("implementationBindings", [])
    required_implementation_paths = {
        "v3-src/contracts/governance/MultisigGovernanceV3.sol",
        "v3-src/test/governance/MultisigGovernanceV3.t.sol",
        "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/MultisigGovernanceV3.json",
        "ci/manifests/base-v3-remediation-source.sha256",
        "ci/check_base_v3_source_snapshot.py",
        "ci/manifests/base-v3-governance-policy.json",
    }
    implementation_paths = {row.get("path") for row in implementation_rows}
    if len(implementation_paths) != len(implementation_rows):
        errors.append("policy commitment contains duplicate implementation paths")
    if implementation_paths != required_implementation_paths:
        errors.append("policy commitment does not bind governance implementation and immutable Base snapshot")
    for row in implementation_rows:
        path = root / row.get("path", "")
        if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != row.get("sha256"):
            errors.append(f"implementation digest mismatch: {row.get('path')}")
    candidate_payload = {
        "canonicalization": commitment.get("canonicalization"),
        "artifacts": commitment_rows,
        "implementationBindings": implementation_rows,
        "baseSnapshot": commitment.get("baseSnapshot"),
        "ledgerBindings": commitment.get("ledgerBindings"),
        "authorityBindings": commitment.get("authorityBindings"),
    }
    candidate_id = hashlib.sha256(_canonical_json(candidate_payload)).hexdigest()
    if candidate_id != commitment.get("candidateId"):
        errors.append("candidate commitment digest mismatch")
    base_snapshot = commitment.get("baseSnapshot", {})
    ledger_rows = commitment.get("ledgerBindings", [])
    required_ledger_paths = {
        "v3-migration/completeness/ledger.json",
        "v3-migration/completeness/check_ledger.py",
        "v3-migration/completeness/tests/test_checker.py",
        "v3-migration/completeness/artifact-checksums.sha256",
    }
    if (
        len(ledger_rows) != len(required_ledger_paths)
        or {row.get("path") for row in ledger_rows} != required_ledger_paths
    ):
        errors.append("candidate does not bind the exact canonical ledger validation set")
    for row in ledger_rows:
        path = root / row.get("path", "")
        if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != row.get("sha256"):
            errors.append(f"ledger binding digest mismatch: {row.get('path')}")
    authority_rows = commitment.get("authorityBindings", [])
    required_authority_paths = {
        "v3-migration/completeness/current-authority-facts.json",
        "repos/vaultfire-sdk/V3_SDK_ABI_RECONCILIATION_20260826.txt",
        "repos/vaultfire-sdk/tests/fixtures/v3-reviewed-abi-signatures.json",
        "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/AccountabilityBondV3Safe.json",
        "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/PartnershipBondV3Safe.json",
        "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/MultisigGovernanceV3.json",
        "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/MandateRegistryV3.json",
        "v3-open-spec/ARTIFACTS.sha256",
        "v3-open-spec/mappings/canonical-v3.json",
        "v3-open-spec/interfaces/canonical-v3-interfaces.v1.json",
        "v3-open-spec/reports/conformance-report.json",
        "pr10/partner-package/THIRTEENTH_INDEPENDENT_AUDIT_CLOSURE_20260826.json",
        "pr10/partner-package/manifests/organizational-readiness.template.json",
        "release-evidence/VAULTFIRE_V3_FINAL_CROSS_CHAIN_READINESS_20260822.json",
        "ci/manifests/base-v3-governance-policy.json",
    }
    if (
        len(authority_rows) != len(required_authority_paths)
        or {row.get("path") for row in authority_rows} != required_authority_paths
    ):
        errors.append("candidate does not bind the exact current authority set")
    authority_by_path = {row.get("path"): row.get("sha256") for row in authority_rows}
    for row in authority_rows:
        path = root / row.get("path", "")
        if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != row.get("sha256"):
            errors.append(f"authority binding digest mismatch: {row.get('path')}")
    facts_path = root / "v3-migration/completeness/current-authority-facts.json"
    if (
        hashlib.sha256(facts_path.read_bytes()).hexdigest()
        != authority_by_path.get(
            "v3-migration/completeness/current-authority-facts.json"
        )
        or authority_facts != _read_json(facts_path)
    ):
        errors.append("current authority facts digest mismatch")
    facts = authority_facts
    sdk_expected = {
        "accountabilityBond": (36, "0x0c18d05c868716fad4bfc1fcfad2da51c83f247de06a7e3871dcece768a9526f"),
        "partnershipBond": (55, "0x954aeee609e92a621e726dcae77be193085651626941d582ca2f7f3b7c073d8c"),
        "governance": (45, "0xcf81d69fed252f21bfd7661ff53868aa81b9ce1a4a1c89edbec9eaabffa7ad0a"),
        "mandateRegistry": (24, "0xe31e9d196bf5a37c328313f688b0d6eff881847e6c46437c1fc617faff8e80e3"),
    }
    identity_manifest = _read_json(root / facts.get("sdk", {}).get("identityPath", ""))
    identity_rows = identity_manifest.get("deployments", {})
    for key, (count, identity) in sdk_expected.items():
        fact = facts.get("sdk", {}).get("abis", {}).get(key, {})
        identity_row = identity_rows.get(key, {})
        artifact = _read_json(root / fact.get("artifactPath", ""))
        abi = artifact.get("abi", artifact)
        function_count = sum(1 for row in abi if row.get("type") == "function")
        if (
            fact.get("functionCount") != count
            or fact.get("identity") != identity
            or len(identity_row.get("functions", [])) != count
            or identity_row.get("identity") != identity
            or function_count != count
        ):
            errors.append(f"SDK ABI authority mismatch: {key}")
    open_spec = facts.get("openSpec", {})
    open_report = _read_json(root / open_spec.get("reportPath", ""))
    checks = open_report.get("checks", [])
    failed = [row.get("id") for row in checks if row.get("status") == "fail"]
    if (
        len(checks) != 29
        or sum(row.get("status") == "pass" for row in checks) != 28
        or failed != ["EXACT_COMMIT"]
        or open_report.get("summary", {}).get("conformant") is not False
        or open_report.get("truth", {}).get("specificationCompliant") is not False
        or open_spec.get("specificationCompliant") is not False
    ):
        errors.append("open-spec authority must remain fail-closed on EXACT_COMMIT")
    partner = facts.get("partnerPackage", {})
    partner_closure = _read_json(root / partner.get("repositoryEvidencePath", ""))
    partner_template = _read_json(root / "pr10/partner-package/manifests/organizational-readiness.template.json")
    human_count = sum(
        row.get("status") == "open" and row.get("externalHumanAction") is True
        for row in partner_template.get("blockers", [])
    )
    if (
        partner.get("fourteenthRepositoryArtifactPresent") is not False
        or partner.get("finalAuditEvidenceStatus") != "handoff-reported-artifact-not-present"
        or partner.get("partnerReady") is not False
        or partner.get("mandatoryEvidenceBlockers") != 25
        or partner.get("externalHumanBlockers") != 14
        or partner_closure.get("remainingBlockerCount") != 25
        or partner_closure.get("remainingHumanBlockerCount") != 14
        or partner_template.get("partnerReady") is not False
        or partner_template.get("status") != "blocked"
        or len(partner_template.get("blockers", [])) != 25
        or human_count != 14
    ):
        errors.append("partner authority must remain blocked at 25 evidence and 14 human blockers")
    external_facts = facts.get("externalEvidence", {})
    if external_facts != {
        "x402": "fail-closed-external-evidence-incomplete",
        "solana": "fail-closed-external-evidence-incomplete",
        "deploymentAuthorized": False,
        "readinessAuthorized": False,
    }:
        errors.append("external Solana/x402 authority facts changed or were overclaimed")
    snapshot_manifest = root / base_snapshot.get("manifestPath", "")
    snapshot_checker = root / base_snapshot.get("checkerPath", "")
    if (
        base_snapshot.get("fileCount") != 117
        or not snapshot_manifest.is_file()
        or hashlib.sha256(snapshot_manifest.read_bytes()).hexdigest()
        != base_snapshot.get("manifestSha256")
        or not snapshot_checker.is_file()
        or hashlib.sha256(snapshot_checker.read_bytes()).hexdigest()
        != base_snapshot.get("checkerSha256")
    ):
        errors.append("immutable Base snapshot metadata is stale or incomplete")
    else:
        rows = [
            line.split(maxsplit=1)
            for line in snapshot_manifest.read_text(encoding="utf-8").splitlines()
            if line.strip()
        ]
        overrides = base_snapshot.get("ledgerReconciliationOverrides", [])
        if (
            len(rows) != 117
            or len({row[1] for row in rows}) != 117
            or overrides != []
            or base_snapshot.get("verifiedUnchangedFileCount") != 117
        ):
            errors.append(
                "immutable Base snapshot must bind exactly 117 unique Base-only files "
                "with zero overrides"
            )
        else:
            for expected, relative in rows:
                path = root / relative
                if relative.startswith(("v3-migration/", "v3-operations/")):
                    errors.append(f"mutable downstream authority in Base snapshot: {relative}")
                actual = (
                    hashlib.sha256(path.read_bytes()).hexdigest()
                    if path.is_file()
                    else ""
                )
                if actual != expected:
                    errors.append(f"immutable Base snapshot mismatch: {relative}")
    evidence_bindings = {
        row.get("path"): row for row in commitment.get("evidenceBindings", [])
    }
    for path_text, row in evidence_bindings.items():
        path = root / path_text
        if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != row.get("sha256"):
            errors.append(f"evidence digest mismatch: {path_text}")
        if row.get("candidateBinding") != commitment.get("candidateId"):
            errors.append(f"evidence index candidate mismatch: {path_text}")
    ledger_evidence_path = root / "v3-operations/evidence/canonical-ledger-reconciliation-20260826.json"
    ledger_evidence = _read_json(ledger_evidence_path)
    if (
        ledger_evidence.get("candidateBinding") != commitment.get("candidateId")
        or ledger_evidence.get("result") != "pass"
        or ledger_evidence.get("validatedItemCount") != 2147
        or ledger_evidence.get("testCount") != 30
        or ledger_evidence.get("checksumCount") != 21
        or ledger_evidence.get("checksumPassCount") != 21
    ):
        errors.append("canonical ledger reconciliation evidence is stale or incomplete")
    ledger_by_path = {row.get("path"): row.get("sha256") for row in ledger_rows}
    for item in ledger_evidence.get("bindings", []):
        if ledger_by_path.get(item.get("path")) != item.get("sha256"):
            errors.append(f"canonical ledger evidence binding mismatch: {item.get('path')}")
    solidity = (root / enforcement.get("commitmentInterface", "")).read_text(encoding="utf-8")
    for required_digest in (
        commitment.get("candidateId"),
        next((r.get("canonicalJsonSha256") for r in commitment_rows if r.get("path") == str(FILES["selectors"])), None),
        next((r.get("canonicalJsonSha256") for r in commitment_rows if r.get("path") == str(FILES["supported"])), None),
        next((r.get("canonicalJsonSha256") for r in commitment_rows if r.get("path") == str(FILES["dispositions"])), None),
        *(row.get("sha256") for row in implementation_rows),
    ):
        if not required_digest or required_digest not in solidity:
            errors.append("generated Solidity commitment is stale or incomplete")
    integrated = (
        enforcement.get("integrationStatus") == "integrated-evidenced"
        and enforcement.get("rawGovernancePolicyEnforced") is True
        and bool(enforcement.get("integrationEvidence"))
    )
    if not integrated and any(release.get(stage) for stage in stages):
        errors.append("governance policy commitment is not integrated; all release stages must remain false")
    governance_source = (root / enforcement.get("governanceSource", "")).read_text(encoding="utf-8")
    governance_source_sha256 = hashlib.sha256(governance_source.encode("utf-8")).hexdigest()
    governance_test_path = root / "v3-src/test/governance/MultisigGovernanceV3.t.sol"
    governance_test_sha256 = hashlib.sha256(governance_test_path.read_bytes()).hexdigest()
    for hook in (
        'bytes32 public constant CANONICAL_POLICY_SHA256 =',
        "function governancePolicyCommitment() external pure returns (bytes32)",
        "function isGovernanceCallAllowed(address target, uint256 value, bytes calldata data)",
        "mapping(address => mapping(bytes4 => CallPolicy)) public callPolicy",
        "bytes32 public policyCommitment",
        "function setCallPolicy(",
        "if (data.length > MAX_PROPOSAL_DATA_LENGTH) revert ProposalTooLarge()",
        "if (!_isGovernanceCallAllowed(to, value, data))",
        "proposalPolicyCommitment[id] = policyCommitment",
        "if (proposalPolicyCommitment[id] != policyCommitment) revert StaleProposal()",
        "if (!_isGovernanceCallAllowed(p.to, p.value, p.data))",
        "target == address(this) ? _validateSelfCall(selector, data) : _validateExternalCall(target, selector, data)",
        "return policy.allowed && value <= policy.maxValue",
    ):
        if hook not in governance_source:
            errors.append(f"observed Base governance policy hook missing: {hook}")
    base_observation = enforcement.get("baseHookObservation", {})
    if (
        base_observation.get("defaultDenyTargetSelector") is not True
        or base_observation.get("valueBound") is not True
        or base_observation.get("proposeAndExecuteRevalidation") is not True
        or base_observation.get("rollingOnchainCommitmentPresent") is not True
        or base_observation.get("exactCanonicalPolicyCommitmentBound") is not True
        or base_observation.get("perRuleCalldataAndArgumentEnforcement") is not True
        or base_observation.get("proposalLifetimeEnforced") is not True
    ):
        errors.append("Base hook observation does not describe exact integrated enforcement")
    source_assessment = enforcement.get("sourceAssessment", {})
    expected_assessment = {
        "governanceSourceSha256": governance_source_sha256,
        "governanceTestSha256": governance_test_sha256,
        "exactGeneratedPolicyDigestConsumed": True,
        "exactGeneratedPolicyDigestExposed": True,
        "rollingRuntimePolicyCommitmentExposed": True,
        "targetSelectorCheckedAtProposal": True,
        "targetSelectorCheckedAtExecution": True,
        "valueCheckedAtProposal": True,
        "valueCheckedAtExecution": True,
        "globalCalldataLimitCheckedAtProposal": True,
        "perRuleCalldataLimitCheckedAtProposal": True,
        "perRuleCalldataLimitCheckedAtExecution": True,
        "argumentPredicatesCheckedAtProposal": True,
        "argumentPredicatesCheckedAtExecution": True,
        "policySnapshotCheckedAtConfirmationAndExecution": True,
        "proposalLifetimeCheckedAtExecution": True,
    }
    if source_assessment != expected_assessment:
        errors.append("final Base governance source assessment is stale or misstates enforcement")
    policy_digest = next(
        (
            row.get("canonicalJsonSha256")
            for row in commitment_rows
            if row.get("path") == str(FILES["selectors"])
        ),
        "",
    )
    base_policy = _read_json(root / "ci/manifests/base-v3-governance-policy.json")
    base_policy_digest = hashlib.sha256(_canonical_json(base_policy)).hexdigest()
    if base_policy_digest not in governance_source or "governancePolicyCommitment" not in governance_source:
        errors.append("Base source does not expose the exact canonical Base policy commitment")
    external_policy_selectors = {
        row.get("selector", "").removeprefix("0x").lower()
        for row in selectors.get("allowlist", [])
        if row.get("targetModule") != "MultisigGovernanceV3"
    }
    selector_function = governance_source.split(
        "function _isCanonicalExternalSelector", 1
    )[1].split("function _targetMatchesRule", 1)[0]
    source_external_selectors = set(re.findall(r"0x([0-9a-fA-F]{8})", selector_function))
    if source_external_selectors != external_policy_selectors:
        errors.append("Base canonical external selector set differs from committed allowlist")
    governance_test = governance_test_path.read_text(encoding="utf-8")
    for test_name in (
        "test_exposesExactCanonicalPolicyDigest",
        "test_rejectsTrailingMalformedAndForbiddenArguments",
        "test_policyDriftInvalidatesPendingProposalAtExecution",
    ):
        if test_name not in governance_test:
            errors.append(f"required Base governance enforcement test missing: {test_name}")
    governance_artifact_path = root / "repos/vaultfire-sdk/tests/fixtures/reviewed-artifacts/MultisigGovernanceV3.json"
    governance_artifact = _read_json(governance_artifact_path)
    abi_functions = {
        row.get("name") for row in governance_artifact.get("abi", []) if row.get("type") == "function"
    }
    if not {
        "CANONICAL_POLICY_SHA256",
        "governancePolicyCommitment",
        "isGovernanceCallAllowed",
        "setCallPolicy",
        "proposalPolicyCommitment",
    } <= abi_functions:
        errors.append("compiled governance ABI omits required policy enforcement surface")
    for required_lifetime_hook in (
        "uint256 public constant PROPOSAL_LIFETIME = 30 days",
        "if (block.timestamp > uint256(p.eta) + PROPOSAL_LIFETIME) revert ProposalExpired()",
    ):
        if required_lifetime_hook not in governance_source:
            errors.append(f"observed Base governance lifetime hook missing: {required_lifetime_hook}")
    source_evidence_path = root / "v3-operations/evidence/governance-source-reconciliation-20260825.json"
    source_evidence = _read_json(source_evidence_path)
    implementation_by_path = {row.get("path"): row.get("sha256") for row in implementation_rows}
    for evidence_key in ("governanceSource", "governanceTest", "compiledArtifact"):
        item = source_evidence.get(evidence_key, {})
        if implementation_by_path.get(item.get("path")) != item.get("sha256"):
            errors.append(f"governance reconciliation evidence has stale {evidence_key} binding")
    expected_observations = {
        "argumentPredicatesAtProposalAndExecution": True,
        "exactGeneratedPolicyDigestConsumed": True,
        "exactGeneratedPolicyDigestExposed": True,
        "globalCalldataLimitAtProposal": True,
        "perRuleCalldataLimitAtProposalAndExecution": True,
        "proposalLifetimeAtExecution": True,
        "rollingRuntimePolicyCommitment": True,
        "targetSelectorValueAtProposalAndExecution": True,
    }
    if source_evidence.get("observations") != expected_observations:
        errors.append("governance reconciliation evidence misstates final Base enforcement")
    evidence_snapshot = source_evidence.get("baseSnapshot", {})
    if evidence_snapshot != base_snapshot:
        errors.append("governance reconciliation evidence has stale Base snapshot binding")
    if source_evidence.get("integrationVerified") is not True:
        errors.append("governance reconciliation evidence does not verify integration")
    required_behavior = set(enforcement.get("requiredProductionBehavior", []))
    if not any("noncanonical ABI calldata" in item for item in required_behavior):
        errors.append("governance enforcement omits canonical calldata re-encoding")

    required_superseded = {
        "release-evidence/VAULTFIRE_V3_FINAL_CROSS_CHAIN_READINESS_20260822.json",
        "release-evidence/VAULTFIRE_V3_FINAL_CROSS_CHAIN_READINESS_20260822.md",
        "pr11/base-sepolia-rehearsal/PREDEPLOYMENT_STATUS.md",
        "pr11/base-sepolia-rehearsal/VALIDATION_RESULTS.md",
        "v3-migration/completeness/VALIDATION_RESULTS.md",
        "pr7/BAZAAR_READINESS.md",
        "pr7/generated/bazaar-readiness.json",
        "repos/vaultfire-solana/MAINNET_READINESS.json",
        "repos/vaultfire-solana/MAINNET_READINESS.md",
        "repos/theloopbreaker-site/FINAL_READINESS_REPORT.md",
        "repos/theloopbreaker-site/PRODUCTION_READINESS.md",
        "repos/theloopbreaker-site/PRODUCTION_READINESS_AUDIT.md",
        "repos/theloopbreaker-site/TESTNET_READINESS_REPORT.md",
        "repos/theloopbreaker-site/pilot-kit/readiness-summary.md",
    }
    superseded = {row.get("path") for row in data["supersession"].get("historicalEvidence", [])}
    if not required_superseded <= superseded:
        errors.append(f"readiness supersession incomplete: {sorted(required_superseded - superseded)}")
    for path_text in superseded:
        if not (root / path_text).is_file():
            errors.append(f"superseded status artifact missing: {path_text}")

    if release.get("evidenceComplete") or release.get("deploymentAuthorized") or release.get(
        "activated"
    ):
        for responder in ("incident-responders", "reconciliation-responders"):
            row = by_role.get(responder, {})
            if row.get("assignmentStatus") != "evidenced" or len(
                row.get("members", [])
            ) < row.get("threshold", {}).get("populationMinimum", 999):
                errors.append(f"missing responders: {responder}")

    return errors


def check(root: Path, *, now: datetime | None = None) -> None:
    errors = validate_package_data(load_package(root), now=now, root=root)
    if errors:
        raise GateError("\n".join(f"- {error}" for error in errors))


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
    parser.add_argument("--now", help="fixed ISO-8601 UTC time for deterministic checks")
    args = parser.parse_args(argv)
    now = datetime.fromisoformat(args.now.replace("Z", "+00:00")) if args.now else None
    try:
        check(args.root.resolve(), now=now)
    except (GateError, FileNotFoundError, json.JSONDecodeError, KeyError) as exc:
        print(f"V3 operations gates: FAIL\n{exc}", file=sys.stderr)
        return 1
    print("V3 operations gates: PASS (control package valid; release remains fail-closed)")
    return 0


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