Repository navigation
Spend Pipeline
check → reserve cap → PayPal AUTHORIZE hold → escrow registered
→ evidence in → predicate evaluated → idempotent capture
SpendPipeline (permit/flow.py) is the only caller of paypal.authorize() and verifier.register(). ReleaseVerifier (settlement/verifier.py) is the only caller of paypal.capture().
| Step | What happens | On failure |
|---|---|---|
| 6a Merchant binding | If the rail has a merchant_account_id, the merchant must equal it |
BLOCKED merchant_not_bound, no reservation |
| 6a2 Approval gate | Above the permit's approval threshold → request approval instead of spending | Returns pending_principal_approval, no reservation. See Principal Approvals
|
6b check()
|
Four-clause gate. ALLOWED reserves cap | BLOCKED receipt, PayPal untouched |
| 6c Track | Operation recorded before the external call | — |
| 6d Authorize | PayPal hold, idempotency key {permit_id}:{auth_id}:authorize
|
Sandbox may raise NeedsPayerApproval → ApprovalRequired (reservation stays held) |
| 6e Recheck | Permit revoked or expired during authorize? | Void the hold, release the reservation: revoked_during_auth / expired_during_auth
|
| 6f Register | Escrow created → AUTHORIZED receipt |
Registration re-checks the permit too, and fails closed |
- Unknown escrow →
REFUSED unknown_escrow. - Already resolved → return the recorded outcome. Never re-capture.
-
UNKNOWN→unknown_reconcile_first.CLEANUP_PENDING→cleanup_pending. No PayPal calls. - Admission recheck. If the permit is missing, revoked or expired → REFUSED and the hold is voided.
-
Ledger chain must verify → otherwise REFUSED
broken_chain:*and the hold is voided. -
Predicate must pass → otherwise REFUSED
predicate:*and the hold is voided. -
Capture with
PayPal-Request-Id={permit_id}:{auth_id}:capture.
If a void times out, the escrow goes CLEANUP_PENDING and the reservation stays held.
| Type | Value | Passes when |
|---|---|---|
| D | delivery_hash |
sha256(delivered_bytes) equals the precommitted artifact_hash (constant-time compare) |
| A | acceptance_signature |
HMAC-SHA256 over escrow_id|artifact_hash|amount_cents with the acceptance key. It is bound to one escrow |
The acceptance key in code is a hard-coded demo key, labelled not for production.
AUTHORIZED → CAPTURED | VOIDED | REFUSED, plus UNKNOWN (reconcile) and CLEANUP_PENDING (retry cleanup).
Sandbox orders need a payer. spend() raises ApprovalRequired. The HTTP service answers 202 with operation_id, order_id and approval_url. POST /api/operations/<id>/resume authorizes the same order. Never call spend again for the same purchase: that would double-reserve the cap.
Permit · payment authority for AI agents · MIT · Wiki facts verified against commit 9900817 on 2026-10-07 (America/Edmonton). Where this wiki and the code disagree, the code wins.
Concepts
- Overview and Problem
- The Four-Clause Gate
- Spend Pipeline
- Delegation and Sub-Permits
- Principal Approvals
- Tighten-Only Narrowing
- E-Stop and Revocation
- Claim Ledger
- UNKNOWN and Reconcile
Using it
Design and status
Reference