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_approval_abc123
#   nonce: nce_7f8e9d0a1b2c3d4e
#   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 nce_7f8e9d0a1b2c3d4e \
  --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 as of v0.9.6

Stateless verifiers cannot detect replay across artifacts they never saw. The current implementation observes replay only within a single verified package -- if the same nonce appears on two actions in one package, the second fails. A --max-uses value is signed into the grant for future enforcement, but no global ledger is consulted yet.

Roadmap:

  • v0.10: local Approval Use Journal (append-only, hash-chained, with signed Merkle checkpoints) for device/workspace replay enforcement.
  • v0.11+: Hub/org checkpoints for distributed single-use across machines and teams.

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 for future ledger enforcement
--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"
# }