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