brain/decisions/000013-active-user-seat-floor-is-enforced-db-authoritatively.md
A platform may not have more active Users than its plan's seat limit (usersLimit). Two moments
need enforcing: (1) adding/inviting a user when already at the limit, and (2) lowering the limit
(plan downgrade, cancel-to-Free, or seat decrease) below the current active-user count.
The plan/seat catalog and billing live in Autumn (via console.activepieces.com, which holds the master
key). An earlier design tried to make the console an independent backstop — the AP server pushed the
active-user count into Autumn (balances.update / "setUsage") and the console re-read it to refuse
over-floor plan changes. That was abandoned:
balances.update (403), so the write had to be proxied through the
console master key — adding a whole endpoint + client method purely to write a number back to Autumn.Enforce the floor in one place: the AP server, against its own database.
checkUsersExceededLimit — used seats vs the projected limit (throws
QUOTA_EXCEEDED / metric USERS). Wired on invite and on reactivation (INACTIVE→ACTIVE). The count
and limit bases later evolved: the count is usedSeats = active users + reserved invites (decision 000014),
and the limit is effectiveUsersLimit = min(usersLimit, scheduledUsersLimit) (decision 000017).assertSeatsNotBelowActiveUsers — the same usedSeats count vs the
target seat limit — on plan downgrade (/checkout), cancel-to-Free, and seat decrease.usersLimit itself is projected from Autumn's balance.granted (included + purchased prepaid seats) — a
read that needs no usage push. No active-user usage is written to Autumn, and the console performs no
seat check.
setUsage, no console backstop, no freshness
contract, no cross-service sync to keep aligned.on_decrease: prorate — the customer is credited the unused portion). This is an interim setting: the
intended behavior is deferred-to-cycle-end with no refund, but a deferred decrease is invisible in the UI
because Autumn's customer response does not yet expose the pending scheduled quantity. The interim makes the
decrease land in the normal balances.usersLimit immediately, so the AP projection reflects it without any
scheduled-state read. Revisit (flip back to deferred/no-refund) once Autumn ships the scheduled quantity in
getCustomer. See the Billing feature doc.