Approvals
An approval is a cryptographic authorization for an action, bound by a nonce and constrained by a signed scope.
An approval answers "who authorized this, to do what, against which subject" -- signed, scoped, and verifiable.
How approvals work
# Approver issues a scoped approval
treeship attest approval \
--approver human://alice \
--description "approve stripe charge max $500 to acme-corp" \
--allowed-actor agent://payments \
--allowed-action stripe.charge.create \
--max-uses 1 \
--expires 2026-03-26T18:00:00Z
# Returns:
# ✓ approval attested
# id: art_7f8e9d0a1b2c3d4e5f6a7b8c9d0e1f2a
# nonce: 32d2676d40a35fa3af7fc801a2de20a6
# scope: actors=["agent://payments"], actions=["stripe.charge.create"], max_uses=1The nonce is the binding token. Pass it to your agent.
# Agent acts under the approval
treeship attest action \
--actor agent://payments \
--action stripe.charge.create \
--approval-nonce 32d2676d40a35fa3af7fc801a2de20a6 \
--meta '{"amount":450,"vendor":"acme-corp"}'What gets checked
Treeship's verify pass enforces three independent properties and reports each separately. Conflating them would let a fooled audit reader trust a guarantee that wasn't actually evaluated.
| Property | What it proves | Stateless? |
|---|---|---|
| Binding | The action's approvalNonce matches a real signed approval | Yes |
| Scope | Action's actor, action, and subject are inside the approval's signed allow-lists; approval not expired | Yes |
| Replay | The nonce has not been consumed before | Verifier-state-dependent |
✓ approval binding nonce matched a signed approval
✓ approval scope actor / action / subject matched approval scope
⚠ replay check package-local only -- no global ledger consultedReplay posture
--max-uses is enforced when the grant is consumed. treeship attest action --approval-nonce reserves a use in the workspace's Approval Use Journal before anything is signed (approval use reserved: use_… (use 1/1)), and a use past the limit is refused (would exceed max_uses (1/1)). treeship verify reports the journal's answer as ✓ replay check local Approval Use Journal passed, use 1/1. The journal lives beside the keystore, so every config that shares a ship shares its journal; a project stub that extends the global config cannot spend a grant a second time. It cannot speak for another machine: that is the hub-org level, with a Hub-signed checkpoint. The four levels are on the replay levels page.
Approval flags
| Flag | Required | Description |
|---|---|---|
--approver <uri> | Yes | Human or identity URI, e.g. human://alice |
--description <text> | No | Plain text scope of what is authorized |
--allowed-actor <uri> | No, repeatable | Actor URIs permitted to consume this approval |
--allowed-action <label> | No, repeatable | Action labels permitted under this approval |
--allowed-subject <uri> | No, repeatable | Subject URIs permitted as the action's target |
--max-uses <n> | No | Signed into the grant and enforced at consume time by the workspace's Approval Use Journal |
--unscoped | No | Required to mint a bearer approval (no scope axes set). Without it the CLI refuses, since unscoped approvals authorize any actor / action / subject |
--expires <timestamp> | No | RFC 3339 expiry time |
--subject <id> | No | Artifact ID being approved (the subject of the approval itself) |
Verifying an approved action
treeship verify art_charge --format json | jq '{outcome, approver, approval_description}'
# {
# "outcome": "pass",
# "approver": "human://alice",
# "approval_description": "approve stripe charge max $500 to acme-corp"
# }