Plans and approvals
Risk classes, which changes need a person, and how a passkey approval binds to the exact plan.
Change kinds and their risk
| Change | Risk class | Notes |
|---|---|---|
deploy | PROD_CHANGE in production, otherwise REVERSIBLE | Can send data out when it ships new code. Optional canary |
rollback | REVERSIBLE | Returns to an earlier deployment. Never reverses a migration |
scale | REVERSIBLE | 0–50 replicas |
env_change | PROD_CHANGE in production, otherwise REVERSIBLE | Values that look like secrets are refused; pass a secret handle instead |
webhook_change | PROD_CHANGE in production, otherwise REVERSIBLE | HTTPS only |
domain_change | IRREVERSIBLE | |
migration (expand) | PROD_CHANGE | Needs a down-migration digest |
migration (contract) | IRREVERSIBLE | Cannot share a plan with a deploy |
drop_database | IRREVERSIBLE | |
export | IRREVERSIBLE | Always needs a passkey approval |
A plan takes the risk class of its riskiest change.
When a person must approve
IRREVERSIBLEplans and exports: always.PROD_CHANGEplans: when the plan is tainted, or when a deploy in it has no canary.- Anything else: only when the plan is tainted and can send data out (a preview of new code, settings, webhooks, domains, exports).
How many people must approve depends on your tier and policy. Business and above can require two for production database changes and irreversible actions. See Pricing.
The approval ceremony
- Create an approval request for a plan. Only named approvers can be asked, and the requester is never one of them.
- A WhatsApp message (falling back to SMS or email) carries a single-use link. Opening it shows what changes, what it affects, how it rolls back and who asked.
- The approver’s browser asks the server for a challenge bound to the request, the plan hash and the approver.
- The approver signs it with a passkey. If the plan has changed since, the approval is refused.
Create a plan
POST /v1/plans
x-aspian-tenant: <tenant uuid>
Content-Type: application/json
{
"riskClass": "PROD_CHANGE",
"summary": "Add partial refunds to payments",
"steps": [{ "kind": "deploy", "idempotent": true, "params": { … } }]
}Then POST /v1/plans/{planId}/approval-requests to ask for approval, and POST /v1/plans/{planId}/apply once it is approved. Full list: API reference.