# verify
Source: https://docs.treeship.dev/cli/verify

> Verify a receipt by URL, file path, or local artifact ID. Optionally cross-verify against an Agent Certificate.

`treeship verify` accepts three target shapes and one optional cross-check:

* a **URL** to a receipt JSON document served by the Hub
* a **file path** to a local `.treeship` or `.agent` package directory
* an **artifact ID** from local storage (the original v0.1 path; chain walk + signature checks)

Pair any of those with `--certificate` to also confirm the session stayed inside the envelope of an Agent Certificate.

## Usage

```bash
treeship verify <url-or-path-or-artifact-id> [OPTIONS]
```

## Options

| Option                        | Description                                                                     |
| ----------------------------- | ------------------------------------------------------------------------------- |
| `--certificate <path-or-url>` | Cross-verify against an Agent Certificate (`.agent` package or URL)             |
| `--no-chain`                  | Verify only this artifact, do not walk the parent chain. Artifact-ID form only. |
| `--max-depth <N>`             | Maximum chain depth to walk (default 20). Artifact-ID form only.                |
| `--full`                      | Show full chain timeline with box-drawn cards. Artifact-ID form only.           |
| `--format json`               | Global flag. Machine-readable output for CI pipelines.                          |
| `--quiet`                     | Global flag. Exit code only, no output.                                         |

## Exit codes

| Code | Meaning                                                                                 |
| ---- | --------------------------------------------------------------------------------------- |
| `0`  | Verified (`pass` on the local artifact path; `structural-pass` on URL/package targets)  |
| `1`  | Verification failed (signature, Merkle, inclusion proof, determinism, or empty receipt) |
| `2`  | Cross-verification failed (cert mismatch, unauthorized tool call, expired cert)         |
| `3`  | Network or filesystem error (could not fetch URL or read file)                          |

## Examples

<Tabs items={['URL', 'File path', '.agent', 'Cross-verify', 'Artifact ID', 'JSON for CI']}>
  <Tab value="URL">
    ```bash
    treeship verify https://api.treeship.dev/v1/receipt/ssn_abc123
    ```

    ```
      ✓  Downloaded receipt
      ✓  Merkle root verified
      ✓  2/2 inclusion proofs passed
      ✓  Leaf count matches artifact count
      ✓  Timeline ordering verified
      ✓  Chain linkage intact

    Structurally consistent.

      ⚠  signatures and issuer were NOT verified from this source
           checked:      Merkle root, inclusion proofs, leaf count, timeline order
           not checked:  who signed this receipt (no signature or trust-root check)

      hint: to verify signatures against your trust roots, use the local
            artifact form: treeship verify <artifact-id>

      Session:   ssn_abc123
      Ship:      ship_demo
      Agent:     researcher
      Duration:  4m 22s
      Actions:   28
    ```

    A fetched receipt earns **`structural-pass`, never `pass`**: this surface runs only keyless, self-referential checks and never opens the trust store, so it deliberately refuses to call the result "authentic". Signature verification against your trust roots requires the local artifact-ID form. An empty receipt (zero artifacts) is `fail`, not a vacuous pass.

    The human-readable mirror at `treeship.dev/receipt/...` is also accepted; the CLI rewrites `/receipt/` to `/v1/receipt/` automatically.
  </Tab>

  <Tab value="File path">
    ```bash
    treeship verify .treeship/sessions/ssn_abc123.treeship
    ```

    Loads the on-disk package, runs the full bag of checks (determinism, Merkle root, inclusion proofs, leaf count, timeline ordering), and prints the same checkmark output as the URL form.
  </Tab>

  <Tab value=".agent">
    ```bash
    treeship verify ./researcher.agent
    ```

    Loads the certificate from `researcher.agent/certificate.json`, checks that the issuer key is pinned in your local trust store under kind `agent_cert`, verifies the Ed25519 signature, and prints the certificate metadata.

    Trust pinning is mandatory and fail-closed: if the issuer key is not in your trust store, verification fails — with `no trust roots configured for agent certificates` when the store has nothing for kind `agent_cert`, or `untrusted issuer` when it has other keys. Pin an issuer first:

    ```bash
    treeship trust add <key_id> <pubkey> --kind agent_cert
    # or sync from your hub:
    treeship hub sync-trust
    ```
  </Tab>

  <Tab value="Cross-verify">
    ```bash
    treeship verify https://api.treeship.dev/v1/receipt/ssn_abc123 \
      --certificate ./researcher.agent
    ```

    Verifies the receipt, then runs cross-verification:

    ```
      ✓  Certificate verified
      ✓  Ship IDs match
      ✓  All 12 tool calls authorized by certificate

    Complete trust loop verified.
    ```

    Fails with exit code `2` if the certificate's issuer key is not pinned in your trust store (kind `agent_cert`), the receipt's `ship_id` does not match the certificate's `identity.ship_id`, the certificate is expired or not yet valid at the verify time, or any tool the session called is missing from the certificate's `capabilities.tools`.
  </Tab>

  <Tab value="Artifact ID">
    ```bash
    treeship verify art_f7e6d5c4 --full
    ```

    The original local-artifact path. Walks the parent chain, verifies every Ed25519 signature, checks approval nonce binding, and prints a box-drawn timeline.
  </Tab>

  <Tab value="JSON for CI">
    ```bash
    treeship verify $RECEIPT_URL --format json --certificate $CERT_PATH \
      | jq -e '.cross_verify.ok == true'
    ```

    For URL and package targets the JSON object looks like:

    ```json
    {
      "outcome": "structural-pass",
      "signatures_verified": false,
      "issuer_verified": false,
      "note": "structural checks only (Merkle root, inclusion proofs, leaf count, timeline). Signatures and issuer are NOT verified from a fetched receipt; use the local artifact-ID verify path for signature verification.",
      "receipt": { "session_id": "ssn_abc123", "ship_id": "ship_demo", "schema_version": "1", "artifact_count": 28 },
      "checks": [ { "name": "merkle_root", "status": "pass", "detail": "..." } ]
    }
    ```

    `outcome` on this surface is `"structural-pass"` or `"fail"` — never `"pass"` — and `signatures_verified` / `issuer_verified` are always `false`. **Do not gate CI on `.outcome == "pass"` for a URL or package target; it will never match.** Gate on `.outcome == "structural-pass"` (or just the exit code), and on `.cross_verify.ok == true` when `--certificate` is used. The `cross_verify` block carries `ship_id_status`, `certificate_status`, the three tool-call lists, and the roll-up `ok` boolean.

    The local artifact-ID form emits `"outcome": "pass" | "fail"` with per-check results — that is the surface where real signature verification happens.
  </Tab>
</Tabs>

## What the verifier checks

The exact set of checks depends on what's available at the target:

* **URL mode** runs every check that's derivable from the receipt JSON alone: Merkle root recomputation, inclusion proofs, leaf count, timeline ordering, chain linkage. Per-artifact Ed25519 signature checks need the original envelope bytes, which only the local-storage and `.treeship` package paths have.
* **`.treeship` package mode** runs everything URL mode runs plus determinism (the on-disk `receipt.json` round-trips byte-identical) and any signature checks the package preserves.
* **`.agent` package mode** verifies the certificate's Ed25519 signature and **requires the issuer key to be pinned in your trust store** (kind `agent_cert`). A certificate is never accepted on its embedded key alone — that would make every certificate self-signed. Fails closed when no trust roots are configured.
* **Artifact-ID mode** is the original local-storage path: walks the parent chain, verifies every signature, enforces nonce binding on approvals, checks expiry. See [the chain-verification guide](/docs/concepts/artifacts) for full semantics.

The cross-verification path additionally verifies that the receipt's `session.ship_id` matches the certificate's `identity.ship_id`, the certificate was valid at verify time (`issued_at <= now <= valid_until`), and every tool the session called is present in the certificate's `capabilities.tools` list. See the [cross-verification concept](/docs/concepts/cross-verification) for the full semantics.

<Callout type="info">
  Pre-v0.9.0 receipts (before `schema_version` and `session.ship_id` were added) verify cleanly under the URL and package paths but cannot complete cross-verification because the receipt has no `ship_id`. The CLI reports `Receipt has no ship_id (legacy receipt; cannot cross-verify)` and exits `2`.
</Callout>

## Approval Authority replay rows (v0.9.10)

When the package contains evidence of consumed `ApprovalUse` records, `verify` emits one row per replay level — each row pinned to a specific invariant. The strongest level the package's evidence supports wins; nothing silently downgrades.

| Row                          | What it proves                                                                                            | Available when                                                                                                                                                     |
| ---------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `replay-package-local`       | No duplicate uses inside this package                                                                     | Always                                                                                                                                                             |
| `replay-local-journal`       | The workspace's local journal has not exceeded `max_uses` for the recorded `(grant_id, nonce_digest)`     | Verifier has Treeship workspace (`treeship package verify` from a workspace)                                                                                       |
| `replay-included-checkpoint` | An embedded `JournalCheckpoint`'s `record_digest` recomputes — record range wasn't tampered after sealing | Package carries one or more checkpoints                                                                                                                            |
| `replay-hub-org`             | A signed Hub checkpoint validates global single-use across machines                                       | Package carries a `kind: hub-org` checkpoint that signature-verifies AND covers every embedded `use_id`. The Hub server itself is out of scope for v0.9.9-v0.9.10. |

Plus four bundle-level integrity rows added in v0.9.10:

| Row                             | What it proves                                                                                                                                                                                                                                                      |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approval-use-record-digest`    | Every embedded use record's `record_digest` recomputes from canonical form                                                                                                                                                                                          |
| `approval-use-nonce-binding`    | Each use's `nonce_digest` equals `sha256(grant.nonce)` for a content-addressed grant in the package                                                                                                                                                                 |
| `approval-use-action-binding`   | Each consuming action's `meta.approval_use_id` resolves to a use in the package, and the action's `approval_nonce` hashes to that use's `nonce_digest`. The action envelope is content-addressed against its filename — substituted/forged envelopes fail this row. |
| `approval-use-chain-continuity` | Embedded uses + checkpoints form a single connected linked list from one genesis: no forks, cycles, or disconnected subchains.                                                                                                                                      |

### The honesty rule

A row reports `✓` only when the matching evidence is present and verified. `✗` only when the evidence is present and a check failed. `-` (or absent) when the evidence isn't in the package at all — never silent pass. Treeship will physically refuse to print "global single-use enforced" or "replay-hub-org passed" without a real Hub-signed checkpoint that verifies cleanly AND covers every use\_id.

### Strict mode

```bash
treeship package verify --strict <pkg>
```

Promotes every approval-evidence warning to a hard failure. `replay-*`, `approval-use-record-digest`, `approval-use-nonce-binding`, `approval-use-action-binding`, and `approval-use-chain-continuity` all participate. Existing receipt-determinism / event-log warnings stay warnings.

Useful in CI:

```bash
treeship package verify --strict ./session.treeship
echo "exit code: $?"   # non-zero on any approval anomaly
```

For the full ladder and the v0.9.10 bypass paths the binding rows close, see [Replay levels](/guides/replay-levels) and [Approval Authority](/concepts/approval-authority).