Entitlement Model
How Ductor scopes tenants and enforces plan and quota limits across the API and hot path.
Ductor is multi-tenant from the ground up. Every pool, rule, connection, and workflow run is scoped to a tenant, and entitlements decide what each tenant is allowed to do and how much of it. Entitlements sit on top of tenant isolation — isolation is what makes a limit meaningful, entitlements are the limits themselves.
Plans and quotas
Entitlements combine two ideas:
- Plan — the set of features and ceilings a tenant is provisioned for (integrates with Stripe billing where enabled). For how plan tiers are populated from Stripe billing and enforced, see Billing & Usage → Plans & entitlement enforcement and the Stripe integration.
- Quota — the runtime budget a tenant may consume: request volume, capacity, concurrency, and similar counters.
When a tenant exceeds what it is entitled to, Ductor fails visibly rather
than silently degrading. Quota exhaustion surfaces as a domain error
(ErrQuotaExceeded, wrapped by QuotaError) that the transport layer maps to a
precise HTTP / Connect status — so clients get a 429-style signal they can act
on, not a mysterious empty result.
Fail visibly, never silently
This mirrors Ductor's core principle: every path either works correctly or fails observably. An over-quota tenant gets a clear, typed error — never a dropped request.
Enforcement on the hot path
Entitlement checks run where the load is. Capacity is tracked with atomic Redis Lua increments and decrements, so concurrency and rate ceilings are enforced consistently across every pod without a central lock. Because the check is cheap and colocated with routing, entitlements gate the routing pipeline without adding a round-trip.
The same atomic-counter approach appears in the tiered queue, where per-tenant and per-pool concurrency limits are enforced at lease time. Whether work arrives through a synchronous routing call or an asynchronous workflow step, the ceiling is the same and it's enforced the same way.
Errors you'll see
| Error | Meaning |
|---|---|
ErrQuotaExceeded / QuotaError | The tenant is over its runtime budget. |
ErrExhausted / ExhaustedError | A capacity-limited target has no headroom. |
ErrConflict / StateError | The requested change conflicts with current state. |
All of these are declared in domain/errors/ and mapped to protocol codes in
the transport layer, so behavior is identical whether a client speaks REST or
Connect-RPC. Because the errors are typed domain sentinels rather than ad-hoc
strings, a client can branch on them programmatically — an over-quota condition
is distinguishable from a state conflict without string matching.
Enforcement postures
This page is the what and why of entitlements. The runtime enforcement
posture — how a missing, stale, or unavailable entitlement is handled (report
vs strict vs off), the unprovisioned bootstrap grace, and exactly which
surfaces are gated (workflow publish/schedule, strategy selection, queue
admission, connector execution) — is documented in
Auth & Security → Entitlements.
Where to go next
- Auth & Security → Entitlements — enforcement postures and the gated surfaces.
- Tenancy — the isolation that entitlements build on.
- The Routing Pipeline — where quota gates the hot path.
- The Tiered Fair Queue — concurrency limits on asynchronous work.