Treeship
CLI reference

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

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

Options

OptionDescription
--certificate <path-or-url>Cross-verify against an Agent Certificate (.agent package or URL)
--no-chainVerify 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.
--fullShow full chain timeline with box-drawn cards. Artifact-ID form only.
--format jsonGlobal flag. Machine-readable output for CI pipelines.
--quietGlobal flag. Exit code only, no output.

Exit codes

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

Examples

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.

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.

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:

treeship trust add <key_id> <pubkey> --kind agent_cert
# or sync from your hub:
treeship hub sync-trust
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.

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.

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:

{
  "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.

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 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 for the full semantics.

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.

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.

RowWhat it provesAvailable when
replay-package-localNo duplicate uses inside this packageAlways
replay-local-journalThe 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-checkpointAn embedded JournalCheckpoint's record_digest recomputes — record range wasn't tampered after sealingPackage carries one or more checkpoints
replay-hub-orgA signed Hub checkpoint validates global single-use across machinesPackage 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:

RowWhat it proves
approval-use-record-digestEvery embedded use record's record_digest recomputes from canonical form
approval-use-nonce-bindingEach use's nonce_digest equals sha256(grant.nonce) for a content-addressed grant in the package
approval-use-action-bindingEach 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-continuityEmbedded 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

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:

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 and Approval Authority.