#!/usr/bin/env python3
#
# EVE CoreGuard - Offline Decision Certificate Verifier
# Copyright (c) 2024-2026 EVE NeuroSystems LLC. All rights reserved.
#
# LICENCE: EVE NeuroSystems grants a worldwide, royalty-free, non-exclusive
# licence to USE, RUN and REDISTRIBUTE this verifier VERBATIM for the sole
# purpose of independently verifying EVE-issued decision certificates.
# No other rights are granted. In particular this licence does NOT permit:
#   - creating, issuing, signing or purporting to issue EVE certificates;
#   - use of the EVE, EVE CoreGuard or EVE NeuroSystems marks; or
#   - deriving, reimplementing or reverse-engineering EVE's governance,
#     policy-evaluation or decision-making engine, which is NOT contained
#     in this file and is proprietary.
#
# This verifier reads PUBLIC material only: a published certificate and a
# published public key. It cannot create a certificate, and possession of it
# confers no ability to forge one - EVE's signing key is HSM/KMS-resident and
# never leaves that boundary.
#
# PATENT: EVE's governance and decision-certification methods are the subject
# of 93 filed U.S. provisional patent applications (Serial Nos. 63/988,235 -
# 64/114,718), with additional invention disclosures under review. No patent
# licence, express or implied, is granted by the distribution of this file.
#
# Contact: legal@evecore.com
#
"""
verify_eve_decision.py — Independently verify an EVE CoreGuard governance
decision evidence record. STANDALONE: this script imports NOTHING from EVE.

A bank's model-risk / audit team can run this with only:
    pip install cryptography
and EVE's published public key (https://evecore.com/.well-known/eve-pubkey).

It recomputes the decision hash and verifies the asymmetric signature against
the PUBLIC key. Production decision certificates are signed with **ECDSA P-384
(SHA-384), AWS KMS-resident** — the private key never leaves the KMS/HSM
boundary. Legacy records signed with **Ed25519** are also verified (the
published key selects the algorithm). No shared secret, no EVE service call, no
ability to forge. If any field of the signed record is altered, verification
FAILS.

Usage:
    # verify a record against the live published key
    python verify_eve_decision.py record.json

    # verify against a local public-key PEM (fully offline / air-gapped)
    python verify_eve_decision.py record.json --pubkey eve-pubkey.pem

    # read the record from stdin
    cat record.json | python verify_eve_decision.py -

The record may be a full evidence object (with a "decision_record" field, as
returned by /v1/decisions/evaluate?include_evidence=true), a bare
decision_record, an agent Decision Certificate (schema "eve.agent.action.v1",
as returned by /api/agent-proof/sample), or a bundle with a "certificates"
array (the first entry is verified).

Exit code: 0 = VERIFIED, 1 = FAILED/INVALID, 2 = usage/dependency error.
"""
from __future__ import annotations

import argparse
import hashlib
import json
import sys
import urllib.request

DEFAULT_PUBKEY_URL = "https://evecore.com/.well-known/eve-pubkey"

# These field names + the canonicalization below MUST match how EVE signs a
# decision (core/coreguard/audit.py: build_audit_record / verify_audit). They
# are reproduced here verbatim so this verifier stays independent of EVE code.
_HASH_FIELDS = ("audit_id", "request_id", "decision", "violations",
                "risk_score", "timestamp", "org_id",
                "audit_schema", "charter_hash", "policy_version")

# v3 content-binding signed fields — mirrors core/coreguard/audit._V3_FIELD_DEFAULTS
# EXACTLY (key set + defaults). Applies to audit_schema="3" records that declare
# payload_schema="eve.decision.v3". Order is irrelevant (keys sorted before hashing).
_V3_FIELD_DEFAULTS = (
    ("charter_hash", ""),
    ("policy_version", ""),
    ("payload_schema", "eve.decision.v3"),
    ("request_payload_sha256", ""),
    ("request_canonicalization", ""),
    ("response_payload_sha256", ""),
    ("response_canonicalization", ""),
    ("response_released", False),
    ("rule_results_sha256", ""),
    ("signature_key_id", ""),
    ("governance_verdict", ""),
    ("response_disposition", ""),
    ("business_decision", ""),
)

# v4 content-binding signed fields — mirrors the deployed engine's
# core/coreguard/audit._V4_CONTENT_DEFAULTS EXACTLY (aws-deploy lineage).
# Applies to audit_schema="4" records; charter/policy ride alongside and the
# eve.assurance.v1 attestation block is included only when present.
_V4_CONTENT_DEFAULTS = (
    ("payload_schema", "eve.decision.v4"),
    ("request_payload_sha256", ""),
    ("request_canonicalization", ""),
    ("response_payload_sha256", ""),
    ("response_canonicalization", ""),
    ("response_released", False),
    ("rule_results_sha256", ""),
    ("governance_verdict", ""),
    ("response_disposition", ""),
    ("business_decision", ""),
)

# eve.agent.action.v1 signing envelope — the agent Decision Certificate
# (core/agent_gateway / core/agent_authority cert_signing) hashes every field
# EXCEPT this envelope with COMPACT canonicalization
# (json.dumps(sort_keys=True, separators=(",", ":"))), stores it as
# ``content_hash``, and the signature covers that content_hash STRING.
_AGENT_ENVELOPE = (
    "content_hash", "signature", "algorithm", "kid",
    "key_fingerprint", "verification_key_url",
)


def _signed_fields_payload(record):
    """Payload named by a record's own signed ``signed_fields`` manifest, else None.

    Records that sign a different field set than the fixed schema (e.g. chat/demo
    decision records) list exactly the keys their hash covers; ``signed_fields``
    is itself one of them, so the list cannot be edited. A signed key that was
    removed comes back as None and fails the hash. Mirrors
    core/coreguard/audit.py:signed_fields_payload.
    """
    names = record.get("signed_fields")
    if (not isinstance(names, list) or "signed_fields" not in names
            or not all(isinstance(n, str) for n in names)):
        return None
    return {n: record.get(n) for n in names}


def _canonical_hash(record: dict) -> str:
    """Recompute the 'sha256-<hex>' content hash over the signed payload.

    Values are used AS EMITTED — EVE hashes the field types it received (e.g. a
    whole-number risk_score is signed as int ``0``, not ``0.0``), and those
    types survive a JSON round-trip, so no coercion is applied here. A record
    carrying a ``signed_fields`` manifest is rebuilt from that manifest.
    """
    manifest_payload = _signed_fields_payload(record)
    if manifest_payload is not None:
        canonical = json.dumps(manifest_payload, sort_keys=True, default=str)
        return "sha256-" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
    payload = {
        "audit_id":   record.get("audit_id"),
        "request_id": record.get("request_id", ""),
        "decision":   record.get("decision", ""),
        "violations": record.get("violations", 0),
        "risk_score": record.get("risk_score", 0),
        "timestamp":  record.get("timestamp", ""),
    }
    # org_id is part of the signed payload only when present (tenant records).
    if "org_id" in record:
        payload["org_id"] = record["org_id"]
    # Schema-aware binding, presence-gated like org_id so v1 records verify
    # unchanged. Exactly one branch runs per record; each mirrors its
    # authoritative backend byte-for-byte:
    #   "2"                                  — policy binding (charter+policy signed)
    #   "3" + payload_schema eve.decision.v3 — content binding (current v3 contract)
    #   "3" without it                        — legacy assurance binding
    #                                           (v2 fields + attestation)
    #   "4"                                  — deployed-engine content binding
    #                                           (eve.decision.v4)
    #   "5" / "6" / "7"                      — supersets of v4 that ALSO bind a
    #                                           nested block: governance_provenance
    #                                           (v5), model_authority (v6) and the
    #                                           institutional mandate (v7). Each
    #                                           lower block rides along when it was
    #                                           signed, which is why presence — not
    #                                           the tier number — decides inclusion.
    _schema = record.get("audit_schema")
    if _schema == "3" and record.get("payload_schema") == "eve.decision.v3":
        payload["audit_schema"] = "3"
        for _k, _dflt in _V3_FIELD_DEFAULTS:
            payload[_k] = record.get(_k, _dflt)
    elif _schema in ("2", "3", "4", "5", "6", "7"):
        payload["audit_schema"] = _schema
        payload["charter_hash"] = record.get("charter_hash", "")
        payload["policy_version"] = record.get("policy_version", "")
        if _schema == "3":
            payload["attestation"] = record.get("attestation")
        if _schema in ("4", "5", "6", "7"):
            if record.get("attestation") is not None:
                payload["attestation"] = record["attestation"]
        if _schema == "4" or (
            _schema in ("5", "6", "7") and record.get("payload_schema") is not None
        ):
            # v4 content binding is ALWAYS present on v4 and OPTIONAL on the
            # nested-block tiers, detected by the payload_schema marker.
            for _k, _dflt in _V4_CONTENT_DEFAULTS:
                payload[_k] = record.get(_k, _dflt)
        if _schema == "5":
            payload["governance_provenance"] = record.get("governance_provenance")
        if _schema == "6":
            if record.get("governance_provenance") is not None:
                payload["governance_provenance"] = record["governance_provenance"]
            payload["model_authority"] = record.get("model_authority")
        if _schema == "7":
            # v7 institutional mandate: provenance and model authority are each
            # optional (present iff signed); the mandate block DEFINES the tier.
            if record.get("governance_provenance") is not None:
                payload["governance_provenance"] = record["governance_provenance"]
            if record.get("model_authority") is not None:
                payload["model_authority"] = record["model_authority"]
            payload["mandate"] = record.get("mandate")
    canonical = json.dumps(payload, sort_keys=True, default=str)
    return "sha256-" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def _full_record_hash(record: dict) -> str:
    """Reconstruct the hash over EVERY field except 'hash'/'signature'.

    Some decision records (e.g. chat/demo records) bind MORE fields than the
    fixed eve.decision.v3/v4 signed-field set — the backend hashes the WHOLE
    payload (every field except hash/signature). This mirrors that exactly so
    such over-bound records verify. A tampered record fails BOTH this and the
    fixed-field reconstruction, so this fallback never weakens the check — it
    only recognizes records whose signature legitimately covers MORE fields.
    """
    payload = {k: v for k, v in record.items() if k not in ("hash", "signature")}
    canonical = json.dumps(payload, sort_keys=True, default=str)
    return "sha256-" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()


# ── eve.decision.cert.v8 ────────────────────────────────────────────────────
# The WHOLE certificate body is signed:
#   hash      = "sha256-" + hex(SHA-256(b"eve.decision.cert.v8\n" || JCS(body)))
#   signature = tagged signature over the UTF-8 bytes of ``hash``
# JCS = RFC 8785 (UTF-16 key order, ECMAScript number formatting). Implemented
# inline so this file stays standalone; identical to the EVE SDK / TS verifier.
CERT_V8_SCHEMA = "eve.decision.cert.v8"
CERT_V8_DOMAIN_TAG = b"eve.decision.cert.v8\n"
_MAX_SAFE_INT = 2 ** 53 - 1
_JCS_ESC = {'"': '\\"', "\\": "\\\\", "\b": "\\b", "\f": "\\f",
            "\n": "\\n", "\r": "\\r", "\t": "\\t"}

# flat mirror field -> signed body path (None = must be absent/empty)
_V8_MIRRORS = {
    "audit_schema": "audit_schema", "audit_id": "audit_id", "request_id": "request_id",
    "decision": "decision", "violations": "violations_count", "risk_score": "risk_score",
    "timestamp": "issued_at", "org_id": "org_id", "decision_trace": "reasons",
    "policy_version": "governance_framework_version", "charter_hash": "charter_hash",
    "payload_schema": None, "attestation": "attestation",
    "governance_provenance": "governance_provenance", "model_authority": "model_authority",
    "mandate": "mandate", "signing_key_id": "signing.kid",
    "signing_key_fingerprint": "signing.key_fingerprint",
    "request_payload_sha256": "request.digest",
    "request_canonicalization": "request.canonicalization",
    "response_payload_sha256": "response.digest",
    "response_canonicalization": "response.canonicalization",
    "response_released": "response.released", "rule_results_sha256": "rule_results.digest",
    "governance_verdict": "governance_verdict", "response_disposition": "response.disposition",
    "business_decision": "business_decision",
}


def _jcs_str(v: str) -> str:
    out = ['"']
    for ch in v:
        e = _JCS_ESC.get(ch)
        out.append(e if e is not None else ("\\u%04x" % ord(ch) if ord(ch) < 0x20 else ch))
    out.append('"')
    return "".join(out)


def _jcs_num(v: float) -> str:
    from decimal import Decimal
    if v != v or v in (float("inf"), float("-inf")):
        raise ValueError("non-finite number")
    if v == 0.0:
        return "0"
    if v < 0:
        return "-" + _jcs_num(-v)
    _s, dt, exp = Decimal(repr(v)).as_tuple()
    digits = "".join(str(d) for d in dt)
    stripped = digits.rstrip("0")
    exp += len(digits) - len(stripped)
    digits, k = stripped, len(stripped)
    n = k + int(exp)
    if k <= n <= 21:
        return digits + "0" * (n - k)
    if 0 < n <= 21:
        return digits[:n] + "." + digits[n:]
    if -6 < n <= 0:
        return "0." + "0" * (-n) + digits
    e = n - 1
    return (digits if k == 1 else digits[0] + "." + digits[1:]) + ("e+" if e >= 0 else "e-") + str(abs(e))


def _utf16(k: str) -> list:
    units = []
    for ch in k:
        cp = ord(ch)
        if cp > 0xFFFF:
            cp -= 0x10000
            units += [0xD800 + (cp >> 10), 0xDC00 + (cp & 0x3FF)]
        else:
            units.append(cp)
    return units


def jcs(v) -> str:
    """RFC 8785 canonical JSON text."""
    if v is None:
        return "null"
    if v is True:
        return "true"
    if v is False:
        return "false"
    if isinstance(v, str):
        return _jcs_str(v)
    if isinstance(v, int):
        if abs(v) > _MAX_SAFE_INT:
            raise ValueError("integer outside the I-JSON safe range")
        return str(v)
    if isinstance(v, float):
        return _jcs_num(v)
    if isinstance(v, dict):
        return "{" + ",".join(_jcs_str(k) + ":" + jcs(v[k]) for k in sorted(v, key=_utf16)) + "}"
    if isinstance(v, list):
        return "[" + ",".join(jcs(x) for x in v) + "]"
    raise TypeError(f"cannot canonicalize {type(v).__name__}")


def _v8_get(body, path):
    cur = body
    for part in path.split("."):
        if not isinstance(cur, dict):
            return None
        cur = cur.get(part)
    return cur


def _instant(value):
    from datetime import datetime, timezone
    try:
        dt = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
        return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
    except Exception:  # noqa: BLE001
        return None


def _v8_mirror_mismatches(record: dict, body: dict) -> list:
    bad = []
    for key, path in _V8_MIRRORS.items():
        if record.get(key) is None:
            continue
        flat = record[key]
        if path is None:
            if flat != "":
                bad.append(key)
            continue
        signed = _v8_get(body, path)
        if key == "timestamp":
            a, b = _instant(flat), _instant(signed)
            ok = a is not None and a == b
        elif key == "risk_score":
            try:
                ok = float(flat) == float(signed)
            except Exception:  # noqa: BLE001
                ok = False
        else:
            # Type-strict, as JCS / the TS verifier compare: true != 1.
            try:
                ok = jcs(flat) == jcs(signed)
            except Exception:  # noqa: BLE001
                ok = False
        if not ok:
            bad.append(key)
    return bad


def _is_v8(record: dict) -> bool:
    return record.get("audit_schema") == "8" or record.get("certificate") is not None


def _sig_alg(sig: str) -> str:
    for tag, alg in (("kms-ecdsa-p384-", "kms-ecdsa-p384"), ("ecdsa-p256-", "ecdsa-p256"),
                     ("ed25519-", "ed25519"), ("hmac-", "hmac-sha256")):
        if sig.startswith(tag):
            return alg
    return ""


def _verify_v8(record: dict, args) -> tuple:
    import base64
    body = record.get("certificate")
    if not isinstance(body, dict) or body.get("cert_schema") != CERT_V8_SCHEMA \
            or body.get("audit_schema") != "8" \
            or record.get("audit_schema") not in (None, "8"):
        return False, "VERIFICATION FAILED - record does not carry a valid eve.decision.cert.v8 body"
    stored_hash, stored_sig = record.get("hash", ""), record.get("signature", "")
    try:
        expected = "sha256-" + hashlib.sha256(
            CERT_V8_DOMAIN_TAG + jcs(body).encode("utf-8")).hexdigest()
    except Exception as exc:  # noqa: BLE001
        return False, f"VERIFICATION FAILED - body cannot be canonicalised: {exc}"
    if expected != stored_hash:
        return False, ("VERIFICATION FAILED - HASH MISMATCH: the certificate body was altered.\n"
                       f"  recomputed: {expected}\n  stored:     {stored_hash}")
    signing = body.get("signing") if isinstance(body.get("signing"), dict) else {}
    alg = signing.get("alg")
    if _sig_alg(stored_sig) != alg:
        return False, "VERIFICATION FAILED - signature algorithm differs from the one bound in the body"
    if alg == "hmac-sha256":
        return False, ("VERIFICATION FAILED - HMAC (symmetric) certificate: not independently "
                       "verifiable with a public key")
    pub = _load_pubkey(args)
    from cryptography.hazmat.primitives import serialization
    der = pub.public_bytes(serialization.Encoding.DER,
                           serialization.PublicFormat.SubjectPublicKeyInfo)
    digest = hashlib.sha256(der).digest()
    fp = "sha256:" + digest.hex()
    kid = f"{alg}:" + base64.urlsafe_b64encode(digest).decode().rstrip("=")[:16]
    ok, info = _verify_signature(pub, stored_sig, stored_hash.encode("utf-8"))
    if not ok:
        return False, "VERIFICATION FAILED - " + info
    if signing.get("key_fingerprint") != fp or signing.get("kid") != kid:
        return False, ("VERIFICATION FAILED - the signing key bound in the certificate "
                       f"(kid {signing.get('kid')}) is not the supplied public key ({kid})")
    bad = _v8_mirror_mismatches(record, body)
    if bad:
        return False, ("VERIFICATION FAILED - unsigned field(s) disagree with the signed "
                       "certificate body: " + ", ".join(bad))
    pol = body.get("policy") or {}
    return True, (f"VERIFIED (eve.decision.cert.v8) - {info} signature over the full "
                  "certificate body: decision, reasons, violations, modifications, evaluated "
                  f"policy {pol.get('ref')}, request/response/action digests, org "
                  f"{body.get('org_id')}, issued_at {body.get('issued_at')}, nonce and key id {kid}.")


def _canonical_agent_hash(record: dict) -> str:
    """Recompute the 'sha256-<hex>' content hash of an eve.agent.action.v1
    Decision Certificate. Compact canonicalization over every field except the
    signing envelope, byte-for-byte matching the backend cert signer.
    """
    payload = {k: v for k, v in record.items() if k not in _AGENT_ENVELOPE}
    canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str)
    return "sha256-" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def _is_agent_cert(record: dict) -> bool:
    return "content_hash" in record and str(record.get("schema", "")).startswith("eve.agent")


def _load_pubkey(args):
    if args.pubkey:
        with open(args.pubkey, "rb") as fh:
            pem = fh.read()
    else:
        # An explicit User-Agent is required: the CDN in front of eveaicore.com
        # rejects the default "Python-urllib/*" agent with HTTP 403. (For a
        # fully offline / air-gapped check, pass --pubkey with the bundled PEM.)
        req = urllib.request.Request(
            args.pubkey_url,
            headers={"User-Agent": "eve-verify/1.0 (+https://evecore.com)",
                     "Accept": "application/x-pem-file, */*"},
        )
        with urllib.request.urlopen(req, timeout=20) as resp:
            pem = resp.read()
    from cryptography.hazmat.primitives.serialization import load_pem_public_key
    return load_pem_public_key(pem)


def _verify_signature(pub, stored_sig: str, signed_message: bytes) -> tuple[bool, str]:
    """Verify an asymmetric signature over ``signed_message`` (the content-hash
    STRING bytes). Algorithm is selected by the signature prefix. Returns
    (ok, algo_label_or_error).
    """
    from cryptography.exceptions import InvalidSignature
    if stored_sig.startswith("hmac-"):
        return False, ("signature is HMAC (symmetric) — NOT independently "
                       "verifiable. Ask EVE to sign with an asymmetric key "
                       "(ECDSA P-384 / Ed25519).")
    try:
        if stored_sig.startswith("kms-ecdsa-p384-") or stored_sig.startswith("ecdsa-p384-"):
            from cryptography.hazmat.primitives.asymmetric import ec
            from cryptography.hazmat.primitives import hashes
            sig = bytes.fromhex(stored_sig.split("p384-", 1)[1])
            pub.verify(sig, signed_message, ec.ECDSA(hashes.SHA384()))
            return True, "ECDSA P-384 (SHA-384, AWS KMS)"
        if stored_sig.startswith("ed25519-"):
            sig = bytes.fromhex(stored_sig[len("ed25519-"):])
            pub.verify(sig, signed_message)  # Ed25519 takes no hash algorithm
            return True, "Ed25519"
        if stored_sig.startswith("ecdsa-p256-"):
            from cryptography.hazmat.primitives.asymmetric import ec
            from cryptography.hazmat.primitives import hashes
            sig = bytes.fromhex(stored_sig[len("ecdsa-p256-"):])
            pub.verify(sig, signed_message, ec.ECDSA(hashes.SHA256()))
            return True, "ECDSA P-256 (SHA-256)"
        return False, f"unsupported signature algorithm: {stored_sig.split('-')[0]}"
    except InvalidSignature:
        return False, ("SIGNATURE INVALID — the record was not produced by the "
                       "key behind the published public key (or was tampered with).")
    except Exception as exc:  # noqa: BLE001
        # Most commonly an algorithm/key-type mismatch (e.g. an Ed25519 record
        # checked against the ECDSA P-384 published key). That is a genuine
        # verification failure, surfaced clearly.
        return False, (f"verification error: {type(exc).__name__}: {exc} "
                       "(signature algorithm may not match the published key)")


def verify(record: dict, args) -> tuple[bool, str]:
    # --- Agent Decision Certificate (eve.agent.action.v1) --------------------
    if _is_agent_cert(record):
        stored_hash = record.get("content_hash", "")
        stored_sig = record.get("signature", "")
        if not stored_hash or not stored_sig:
            return False, "record is missing 'content_hash' or 'signature'"
        expected_hash = _canonical_agent_hash(record)
        if expected_hash != stored_hash:
            return False, (
                "HASH MISMATCH — a field in the certificate was altered.\n"
                f"  recomputed: {expected_hash}\n  stored:     {stored_hash}"
            )
        pub = _load_pubkey(args)
        ok, info = _verify_signature(pub, stored_sig, expected_hash.encode("utf-8"))
        if not ok:
            return False, info
        return True, (f"VERIFIED — hash recomputed and {info} signature confirmed "
                      "against the published public key. No EVE involvement required.")

    # --- eve.decision.cert.v8 (full body signed) -----------------------------
    if _is_v8(record):
        return _verify_v8(record, args)

    # --- Decision evidence record (eve.decision.v3 / v4 / legacy) ------------
    stored_hash = record.get("hash", "")
    stored_sig = record.get("signature", "")
    if not stored_hash or not stored_sig:
        return False, "record is missing 'hash' or 'signature'"

    # 1. Tamper check: recompute the hash over the decision fields. Try the fixed
    #    eve.decision.v3/v4 signed-field set first (legacy + /v1/decisions/evaluate
    #    records); if that does not match, fall back to the full-record
    #    reconstruction (every field except hash/signature) used by over-bound
    #    records (e.g. chat/demo records binding extra fixture/operator provenance).
    #    A genuine tamper fails BOTH reconstructions.
    expected_hash = _canonical_hash(record)
    if expected_hash != stored_hash:
        full_hash = _full_record_hash(record)
        if full_hash == stored_hash:
            expected_hash = full_hash
        else:
            return False, (
                "HASH MISMATCH — a field in the decision record was altered.\n"
                f"  recomputed (fixed-field): {expected_hash}\n"
                f"  recomputed (full-record): {full_hash}\n"
                f"  stored:                   {stored_hash}"
            )

    # 2. Signature check. Production certificates are ECDSA P-384 (AWS KMS);
    #    legacy records may be Ed25519. Both are third-party verifiable with the
    #    published public key. HMAC records require the shared secret (not
    #    third-party verifiable).
    pub = _load_pubkey(args)
    ok, info = _verify_signature(pub, stored_sig, expected_hash.encode("utf-8"))
    if not ok:
        return False, info
    _v = record.get("audit_schema") or "1"
    return True, (f"VERIFIED (legacy v{_v}: decision_trace/reasons, the violation list, "
                  "modifications and the signing key id are NOT bound; policy_version is "
                  "the governance-framework fingerprint, NOT the evaluated policy) - hash "
                  f"recomputed and {info} signature confirmed against the public key.")


def _extract_record(obj: dict) -> dict:
    """Accept a full evidence object, a bare decision_record, or a bundle with a
    'certificates' array (verify the first certificate)."""
    if isinstance(obj, dict):
        if isinstance(obj.get("decision_record"), dict):
            return obj["decision_record"]
        certs = obj.get("certificates")
        if isinstance(certs, list) and certs and isinstance(certs[0], dict):
            return certs[0]
    return obj


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description="Independently verify an EVE CoreGuard decision evidence record.")
    ap.add_argument("record", help="path to the evidence/decision JSON ('-' for stdin)")
    ap.add_argument("--pubkey", help="path to a public-key PEM (offline verification)")
    ap.add_argument("--pubkey-url", default=DEFAULT_PUBKEY_URL,
                    help=f"URL of the published public key (default: {DEFAULT_PUBKEY_URL})")
    args = ap.parse_args(argv)

    try:
        import cryptography  # noqa: F401
    except ImportError:
        print("ERROR: this verifier needs the 'cryptography' package: pip install cryptography",
              file=sys.stderr)
        return 2

    try:
        raw = sys.stdin.read() if args.record == "-" else open(args.record, "r", encoding="utf-8").read()
        obj = json.loads(raw)
    except Exception as exc:  # noqa: BLE001
        print(f"ERROR: could not read/parse record: {exc}", file=sys.stderr)
        return 2

    record = _extract_record(obj)
    ok, message = verify(record, args)
    decision = record.get("decision", record.get("disposition", "?"))
    ident = record.get("audit_id", record.get("certificate_id", "?"))
    print(f"Decision : {decision}")
    print(f"Record   : {ident}")
    print(f"Result   : {'[VERIFIED] ' if ok else '[FAILED] '}{message}")
    return 0 if ok else 1


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