The gate
Every sensitive route delegates torunGuardedAction(req, { actionType, payload }),
which resolves the subject (tenant_user + AccountKit) and the actor, then hands the
whole thing to the governor — the closed enforcement engine — for a verdict. The
governor runs four stages in this order:
- Capability —
capabilityAllowed(kit, actionType). Not allowed →deny / capability_denied. - Budget pre-check — committed spend + this action’s amount, against the kit’s
window cap. Over a hard cap →
deny / budget_exceeded. Over a soft cap → fall through to stage 3 with approval forced. - Approval requirement — see the exact condition below.
- Freeze — persist the
pendingrow and return202 { status: "pending_approval", approval_id }.
resolveApprovalRequirement starts from the built-in DEFAULT_APPROVAL_ACTIONS set,
then applies the per-primitive requiresApproval override on the kit. A soft budget
cap forces approval regardless of that answer.
The same gate runs for MCP tools that call mcpGuard — the card, trading, formation,
verification, domain-purchase, phone/mobile provisioning, compute, browser-signup,
connections-connect and send-email verbs, plus the three destructive brain verbs. A
tool that does not call mcpGuard gets no capability check and no approval
requirement; the revocation check and budget scope still apply to every MCP tool call —
see the governance gateway.
Deferred replay (single execution path)
There is one executor registry mappingaction_type → service call
(cards.create → createCard, domains.purchase → purchaseDomain, …). Both the
immediate path and the post-approval path dispatch through it, so a frozen
action runs exactly as it would have live.
Each registration has a live leg and an optional sandbox leg; the governor’s
verdict carries which one to run. A missing sandbox leg is meaningful, not an
oversight — the action refuses with not_configured rather than falling through to the
live one. See Environments enforcement.
Data
Pending and resolved approvals live in theapprovals table, scoped to a
tenant_user: action_type, primitive, the frozen payload,
requested_by_*, status (pending | executed | failed | denied | expired),
result / error, and resolved_by_*. Creating, approving, and denying each
write an activity_events row (approval.requested, approval.approved, …)
and publish a live event for dashboards.
The
approvals row is the authoritative record — the decision and its outcome are
committed there before anything executes. The activity_events row beside it is
best-effort: logTenantEvent catches and logs its own insert failure and returns
false, deliberately, so that an audit-log write can never fail the business action it
is describing. Reconcile against approvals, not against the activity log. See
the decision ledger.Approvers
Anyone authenticated for the company can resolve an approval: developers via the dashboard or CLI (any of their tenant users), and end-users via your app (scoped to their owntenant_user). PII-heavy payloads (e.g. KYC members) are
stored minimally and re-fetched at execution time.