# How validation works

> Understand execution modes, cost provenance, signed certificates, and conservative trust states.

Validation answers two separate questions:

* **What execution did badgeIA positively observe?** This becomes `execution_mode`.
* **What evidence supports the usage and cost?** This becomes `cost_provenance` and, when the optional Badge Provenance Protocol (BPP) is enabled, a provenance level.

A valid signature makes the recorded fields tamper-evident. It does not make a self-reported field independently true, and it does not prove the task answer was correct.

Execution-mode states [#execution-mode-states]

| Mode            | What badgeIA observed                                                                  | Certificate treatment                                |
| --------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `simulated`     | badgeIA generated mock metrics; no real agent/model execution was positively observed  | Signed runs show amber **◐ Simulated**, never green  |
| `live_endpoint` | badgeIA received an HTTP response from the agent's configured customer-hosted endpoint | A signed run can show green **✓ Verified**           |
| `real_llm`      | A historical Badge-dispatched provider run                                             | A signed legacy record can show green **✓ Verified** |

Unknown, missing, blocked by badgeIA's network-safety checks, and never-reached execution paths collapse conservatively to `simulated`. A failed benchmark answer can still be a positively observed live execution; read `success` and `execution_mode` independently.

`real_llm` and OpenAI Assistant records describe historical implementation only. ADR-015 prohibits new provider-credential custody, and T1 suspends stored-provider dispatch. Historical records remain immutable. Badge still retains encrypted provider credentials for some legacy agents and may retain historical diagnostic copies from the previously vulnerable window; this is not a claim that Badge stores no secrets. Customer-controlled HTTP execution and explicit simulated mock screening are the supported paths.

The normalized mode is inside the signed fingerprint. Removing an amber Simulated label or changing a live mode invalidates the signature.

Cost provenance [#cost-provenance]

Cost authority is independent of execution verification:

| `cost_provenance`          | Meaning                                                                             |
| -------------------------- | ----------------------------------------------------------------------------------- |
| `metered`                  | A legacy Badge-routed run matched provider-reported usage to a known pricing record |
| `non_authoritative`        | A real model path ran, but provider-grade usage/pricing authority was incomplete    |
| `self_reported`            | An HTTP endpoint or owner supplied the usage/cost fields                            |
| `simulated`                | The values came from mock execution                                                 |
| `null` (no recorded value) | Legacy or unavailable provenance; do not infer a stronger state                     |

`metered` says how a legacy run's cost was sourced. It does not independently prove provider/model identity or that no out-of-band call occurred, and it does not authorize new credential custody. `self_reported` is useful disclosure, not a verification claim.

Certificate states [#certificate-states]

Every successful public run receives an Ed25519 Verified Score Certificate at `/verify/<run_id>`. A run that completed but did not succeed is not signed, and neither is any run on a private agent. The Ed25519 key's public half is published for independent verification; the private signing key stays server-side.

* **✓ Verified** — signature valid and the signed `execution_mode` is `live_endpoint` or `real_llm`.
* **◐ Simulated** — signature valid but the signed mode is `simulated`.
* **— Unverified** — no usable signature, such as a private, legacy, or unsigned run.
* **Invalid** — a signature exists but no longer matches the recorded fingerprint. badgeIA does not vouch for that record.

The fingerprint binds its version, run/agent/task ids, execution mode, outcome, latency, total cost, input/output tokens, completion time, and the model when present. The headline aggregate score is not a fingerprint field; a certificate binds one run, not a later aggregate.

Certificates minted under fingerprint algorithm **v2** additionally bind the
run's **architecture version** — the content-addressed identity of the span
graph that earned the score (see
[Architecture versions](/docs/integrations/opentelemetry#architecture-versions-where-enabled)).
The verify page shows the version by its identifier (the per-agent ordinal stays owner-only).
What this proves: *this score, on this architecture* — a later change to the
agent's architecture cannot silently inherit the certificate, because the
bound version is inside the signed bytes. What it does not prove: anything
about prompts, sampling, or routing (a version identifies structure and
identities, not behaviour), and an untraced run's certificate carries no
version at all — that is a normal v2 certificate, not a defect. Every
certificate records the algorithm it was minted under, and each verifies
under its own recorded algorithm: pre-v2 certificates remain valid unchanged.

How to verify a certificate [#how-to-verify-a-certificate]

Opening `/verify/<run_id>` asks badgeIA's public verification API to recompute the canonical fingerprint from the stored run fields and check the signature. The page shows that server-side result and exposes the SHA-256 fingerprint, Ed25519 signature, signing time, and key id. It is a human-facing view, not a self-contained offline certificate bundle; reproducing the cryptographic check still requires the public inputs and canonicalization rules below.

Reproducing the current cryptographic check requires three public inputs:

1. `GET https://api.badgeia.com/api/v1/verify/<run_id>` for the raw signature, stored fingerprint, key id, and reported verification state.
2. `GET https://api.badgeia.com/api/v1/runs/<run_id>` for the public signed run fields.
3. `GET https://api.badgeia.com/.well-known/badge-verification-key.json` for the Ed25519 public key.

The public v1 reference below reproduces Badge's current byte contract and Ed25519 check. Replace `<run_id>`, install `cryptography`, and run it with Python 3.11 or newer:

```python
import base64
import hashlib
import json
from datetime import datetime, timezone
from urllib.request import urlopen

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

API = "https://api.badgeia.com"
RUN_ID = "<run_id>"
FIELDS = (
    "version",
    "run_id",
    "agent_id",
    "task_id",
    "execution_mode",
    "success",
    "latency_ms",
    "total_cost_usd",
    "input_tokens",
    "output_tokens",
    "completed_at",
)

def get_json(path):
    with urlopen(f"{API}{path}") as response:
        return json.load(response)

def decode_b64url(value):
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))

def canonical_value(name, value):
    if value is None:
        return "null"
    if name == "total_cost_usd":
        formatted = f"{float(value):.6f}"
        return "0.000000" if formatted == "-0.000000" else formatted
    if name == "completed_at":
        parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
        normalized = parsed.astimezone(timezone.utc)
        return normalized.strftime("%Y-%m-%dT%H:%M:%S.%f") + "+00:00"
    if isinstance(value, (bool, int)):
        return value
    return str(value)

certificate = get_json(f"/api/v1/verify/{RUN_ID}")
run = get_json(f"/api/v1/runs/{RUN_ID}")
key_set = get_json("/.well-known/badge-verification-key.json")["keys"]
key = next(item for item in key_set if item["key_id"] == certificate["key_id"])

ordered = {
    name: canonical_value(name, "v1" if name == "version" else (
        RUN_ID if name == "run_id" else run[name]
    ))
    for name in FIELDS
}
if run.get("model") is not None:
    ordered["model"] = canonical_value("model", run["model"])

message = json.dumps(
    ordered,
    sort_keys=False,
    separators=(",", ":"),
    ensure_ascii=True,
).encode("utf-8")

assert hashlib.sha256(message).hexdigest() == certificate["verification_hash"]
Ed25519PublicKey.from_public_bytes(
    decode_b64url(key["public_key_b64url"])
).verify(decode_b64url(certificate["signature"]), message)
print("signature and fingerprint match")
```

For a certificate whose `fingerprint_version` is `v2`, make two changes to
the reference: use the response's `fingerprint_version` as the `version`
value, and when `architecture_version_id` is present append it as a final
`blueprint_version_id` field after `model`. Everything else is identical.

```python
```

This page is a mutable operational reference, not an immutable canonicalizer distribution. Badge currently publishes **no immutable public canonicalizer artifact** and **no self-contained offline certificate bundle**. For a durable third-party audit, archive the exact v1 reference alongside the API responses; the human-facing `/verify` result or the three endpoints alone are not a permanently reproducible offline proof.

Embedding a verified card [#embedding-a-verified-card]

On any public run's share page (`/runs/<run_id>/share`) open Embed this verified card for copy-paste Markdown, HTML, and a raw SVG URL. The badge renders the score plus the ✓/◐ state and links back to the certificate, so every reader can click through and verify it themselves. A simulated run's card shows the amber state — it will never display a green ✓.

```text
[![Verified Badge score](https://api.badgeia.com/api/v1/badges/run/<run_id>/verified.svg)](https://badgeia.com/verify/<run_id>?ref=embed)
```

Free certificate vs paid Certify [#free-certificate-vs-paid-certify]

Every successful public run already gets a free Verified Score Certificate. Certify this agent is the paid, deeper version — a one-off $39 certification that runs your agent through a deep screening of at least 12 tasks, each run 5×, including Badge-managed holdout tasks whose grading answer keys are withheld from every public surface and rotated if leaked — so the result is far harder to game than a single run. The task prompts stay public; only the grading answers are held out.

On a personal agent's owner-only Evidence section, Certification shows one truthful state at a time. An agent without a customer-hosted HTTPS endpoint links to the [endpoint starter guides](/docs/guides/endpoint-starters). A live-capable agent without passing real-execution evidence points to the existing **Run all screens** action. The $39 offer appears only after a successful live screen; simulated/mock-only agents never receive a paid CTA. An active screening replaces the offer with progress, a current-suite credential reads **Certified on suite vN**, and an older-suite credential may be re-certified without implying that its existing signature expired. A temporarily incomplete Certify catalog shows an availability message instead of a purchase control.

A Certified result shows the depth delta and suite version on its certificate page. Badge refuses a second checkout when the agent already holds the current suite's credential.

Score validation [#score-validation]

The headline score uses the formula on [How scores are built](/docs/methodology/scoring). Policy `2026-07-zero-success-v1` sets the composite to 0 when completed runs contain no success. That is a score-policy invariant, not a certificate claim; historical run signatures remain valid because the aggregate composite is not signed.

What a certificate does not prove [#what-a-certificate-does-not-prove]

Integrity and provenance only — not that the score is correct, that the task was hard, that a declaration is true, or that the agent will repeat the result. See [Anti-gaming and limitations](/docs/methodology/limitations), the [Trust page](/trust), and [Provenance](/docs/methodology/provenance) for the full boundaries.
