Approvals queue
System → Approvals (/system/approvals) is where high-risk actions wait for a second admin’s sign-off.
What lands here
Section titled “What lands here”Anything hitting a two-person rule trigger:
| Action | Trigger |
|---|---|
| Refund | amount > $50 |
| Coin grant | amount > 2000 |
| Series deletion | always |
| Role changes | always |
| GDPR anonymize | always |
| Feature flag critical write (planned) | when marked sensitive=true |
Each row is one admin_approvals record with status='pending', action_type, payload (JSON of the pending change), requested_by, created_at.
The queue view
Section titled “The queue view”Columns:
- Action: human-readable label (
Refund $80,Grant 5000 coins,Delete series "X"). - Requester: admin who initiated.
- Payload preview: key fields inline.
- Age: how long it’s been pending.
- Detail: click through.
Detail page
Section titled “Detail page”Shows:
- Full JSON payload (the exact change that will execute).
- Requester’s audit trail (recent actions by same admin).
- Target context: a mini User 360 or transaction detail depending on the action type.
- Two buttons: Approve and Reject, both requiring an audit note.
Approving
Section titled “Approving”- Verify the payload matches the ticket / policy.
- Type your audit note.
- Click Approve.
- The service:
- Marks approval
status='approved',approved_by=<you>,resolved_at=now(). - Immediately executes the underlying action inside the same transaction.
- Writes audit entries for both the approval decision and the execution.
- Marks approval
- Response returns the executed result (e.g. the refund with
status='executed').
Rejecting
Section titled “Rejecting”- Type your audit note (required: say why).
- Click Reject.
- Approval flips to
status='rejected'. No action executes.
The requester sees the rejection in the approval detail (they may still have approvals:read).
Why can’t I approve my own request?
Section titled “Why can’t I approve my own request?”Server-side, admin-approvals.service.execute() refuses if requested_by === approved_by. Response: approver_must_differ (403). This is the whole point of the two-person rule.
Batching
Section titled “Batching”- Multiple pending approvals of the same action type (e.g. 5 refunds < $50 each but the reviewer wants oversight anyway) → work them individually. Bulk approve is intentionally not offered.
The audit trail
Section titled “The audit trail”Every approval decision + execution is audited:
AuditLogwithaction='approval.approve|reject',before=<the pending payload>,after=<the executed result or null>.- Search the audit log by
admin_idorref_type='approval'for a full compliance trail.
API calls
Section titled “API calls”| Action | Endpoint |
|---|---|
| List pending / recent | GET /v1/admin/approvals |
| Detail | GET /v1/admin/approvals/:id |
| Approve | POST /v1/admin/approvals/:id/approve |
| Reject | POST /v1/admin/approvals/:id/reject |
Full details in API: System.