Treeship
Get started

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=1

The 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.

PropertyWhat it provesStateless?
BindingThe action's approvalNonce matches a real signed approvalYes
ScopeAction's actor, action, and subject are inside the approval's signed allow-lists; approval not expiredYes
ReplayThe nonce has not been consumed beforeVerifier-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 consulted

Replay 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

FlagRequiredDescription
--approver <uri>YesHuman or identity URI, e.g. human://alice
--description <text>NoPlain text scope of what is authorized
--allowed-actor <uri>No, repeatableActor URIs permitted to consume this approval
--allowed-action <label>No, repeatableAction labels permitted under this approval
--allowed-subject <uri>No, repeatableSubject URIs permitted as the action's target
--max-uses <n>NoSigned into the grant and enforced at consume time by the workspace's Approval Use Journal
--unscopedNoRequired to mint a bearer approval (no scope axes set). Without it the CLI refuses, since unscoped approvals authorize any actor / action / subject
--expires <timestamp>NoRFC 3339 expiry time
--subject <id>NoArtifact 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"
# }