Context
Wave 10 needed three things the fixed order has no slot for. The approval park of 0098 decision 3 needs a step. A declaredrateLimit
(0137) needs a step. And plan limits must be checked at commit
time, not from a snapshot, so two creates racing at a limit cannot both pass (0215, owner
round 3 item 18): the identity builder could not wrap creates in LimitReservationService because the pipeline had
no commit hook. @stackra/actions now carries all three (PipelineStage.Approval, PipelineStage.RateLimit, the
LimitReservation port), and the audit entry of a successful call is written inside the unit of work
(0212 decision 4).
Decision
- The order. tenancy (with the
LiveConfigurationGuardof a structural action), permission, precondition, entitlement, flag, approval, idempotency (with a device’s offline cap), rate limit, interceptors, thenhandle. Nothing reorders it; an app’s gate stays a precondition. - Approval. The step asks the
ApprovalGateport with the validated input, the key, the permission, the risk and the effect tags. Which calls need approval and who approves is a rule set the driver evaluates. The step fails closed: an action with no gate bound is an error, and an app with no approvals bindsOpenApprovalGateDriveron purpose (X2-14). A query is never parked. - Rate limit. The area’s and the action’s token buckets run after idempotency, so a replayed call never spends a token.
- Reserve-then-commit. The action’s unit of work runs inside
LimitReservation.reserve(call, input, commit).@stackra/entitlementsbinds it overLimitReservationService: for a call that creates a counted model, it holds the limit’s lock, checks grant and count, runs the commit and releases. A call that counts against no limit commits as it is; an app with no plan limits binds none and the hook is skipped. - Audit and usage inside the commit. A successful call’s audit entry and a metered action’s usage event are written in its unit of work, so they commit or roll back with the writes; refusal and replay entries are written outside.
- Values. None in code. Approval rules and approvers are rule sets; limits are plan-catalogue data; rate-limit budgets are settings with platform-floor values; the effect tags are declared per action.
Consequences
- No action writes reservation or approval code itself.
- A service without an approval binding does not start an action, which makes the binding a deliberate choice.
Enforcement
packages/application/actions/src/core/services/action-pipeline.service.ts refuses a reordered chain; its tests sit in
packages/application/actions/src/core/services/__tests__/. planned — gen refuses a service that declares
financial, irreversible, bulk, pii or external-communication actions without an ApprovalGate binding.
Supersedes and sources
Supersedes: none. Amends, when accepted: 0138 decision 1 as amended by 0294. Sources:notes/handover/build-wave10-2026-10-25.md (identity builder, not built item 4 and question 2; runtime builder,
questions 5 and 6); notes/handover/review-round-2/cross.md (X2-14).