ScaiBunker Quota Reference
Profile-based resource quotas for bunker creation. Profiles bundle caps; assignments link them to users or groups; the resolver enforces the most-restrictive cap when multiple profiles apply.
For concepts, see ScaiBunker → Quotas.
Object model#
Profile#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
| Field | Meaning |
|---|---|
tenant_id |
null for platform-default profiles managed by super-admin; tenant UUID for tenant-scoped profiles |
max_concurrent_bunkers |
Cap on the number of active (non-terminal) bunkers in the bucket |
max_persistent_bunkers |
Cap on the persistent-mode subset |
max_cpu_millicores |
Sum of CPU across the bucket's active bunkers |
max_memory_mb / max_disk_mb / max_gpu_count |
Same, for those resources |
per_bunker_max_* |
Single-bunker ceiling; null = no per-bunker cap |
Assignment#
1 2 3 4 5 6 7 8 | |
target_kind |
mode |
Bucket key | Meaning |
|---|---|---|---|
user |
individual |
user:{user_id} |
Direct per-user quota |
group |
shared |
group:{group_id} |
All members share one bucket |
group |
per_user |
user:{user_id} |
Each member gets their own copy of the profile |
A target (user or group ID) can have at most one assignment — UNIQUE(target_kind, target_id).
Resolution algorithm#
At bunker create:
- Collect all profiles applying to the caller:
- The caller's direct user assignment, if any.
- Every group assignment whose
target_idmatches a group the caller is a transitive member of (ScaiKey nested-group expansion).
- If nothing matches, fall back to the platform-default profile.
- For each profile:
- Per-bunker checks: if any
per_bunker_max_*is non-null and the request exceeds it, fail. - Aggregate checks: read the bucket's current usage from
Redis-backed counters. If
current + requestexceeds any aggregate cap, fail.
- Per-bunker checks: if any
- Most-restrictive wins because every profile's check has to pass.
- The platform
PLATFORM_MAX_*constants are an absolute outer ceiling; no profile can grant past them.
When a check fails, the response is BUNKER_QUOTA_EXCEEDED with a
message naming the cap and bucket (e.g. "Per-bunker CPU 8000m exceeds profile 'team-shared' cap of 4000m" or "Concurrent bunker limit (5) reached on group:G1 (profile 'team-shared')").
Endpoints#
GET /v1/modules/scaibunker/quota-profiles#
List visible profiles.
- Super-admin: all profiles platform-wide.
- Tenant caller: tenant-owned profiles plus platform defaults.
- Anyone with
scaibunker:createmay read (the assignment dropdowns and the resolver fallback both need them).
Response:
1 2 3 4 5 6 7 8 | |
POST /v1/modules/scaibunker/quota-profiles#
Create a profile.
- Super-admin → creates a platform-default (
tenant_id: null). - Tenant admin (
scaibunker:admin:tenant) → creates a tenant-scoped profile.tenant_idis auto-set from the caller's tenant.
Request body:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Returns 201 Created with the full profile. Fails with
QUOTA_PROFILE_CONFLICT if the (tenant_id, name) pair already exists.
GET /v1/modules/scaibunker/quota-profiles/{id}#
Fetch one profile. Visibility rules same as list.
PATCH /v1/modules/scaibunker/quota-profiles/{id}#
Partial update. Only fields present in the body are touched.
- Tenant-scoped profile → tenant admin in same tenant, or super-admin.
- Platform-default → super-admin only.
1 | |
DELETE /v1/modules/scaibunker/quota-profiles/{id}#
Delete a profile. Same auth rules as PATCH. Cascades to assignments;
affected users fall back to the platform default.
GET /v1/modules/scaibunker/quota-profiles/{id}/assignments#
List assignments for one profile.
1 2 3 4 5 6 7 8 | |
POST /v1/modules/scaibunker/quota-profiles/{id}/assignments#
Assign a user or group to a profile.
1 2 3 4 5 | |
or
1 2 3 4 5 | |
Validation:
target_kind: user⇒modemust beindividual.target_kind: group⇒modemust besharedorper_user.
Returns 409 QUOTA_ASSIGNMENT_CONFLICT if (target_kind, target_id) is
already assigned to any profile.
DELETE /v1/modules/scaibunker/quota-profiles/{id}/assignments/{assignment_id}#
Remove an assignment. Idempotent — already-deleted assignments return
204 rather than 404.
Error codes#
| Code | HTTP | Cause |
|---|---|---|
QUOTA_PROFILE_NOT_FOUND |
404 | Profile doesn't exist or isn't visible to caller |
QUOTA_ASSIGNMENT_CONFLICT |
409 | Target already has an assignment |
BUNKER_QUOTA_EXCEEDED |
400 | A profile's cap would be exceeded by the request |
AUTHZ_PERMISSION_DENIED |
403 | Caller lacks scaibunker:admin:tenant (tenant-scope) or super-admin (platform-default) |
Worked example#
A tenant admin builds a quota plan for the ML team:
- Create a
team-sharedprofile capping the ML group's combined usage at 16 GPU, 64 CPU, 64 GiB memory, 16 concurrent bunkers, plus a per-bunker cap of 4 GPU / 16 CPU / 16 GiB so no single user can monopolise. (POST /quota-profiles) - Assign the ML group with
mode=shared. (POST /quota-profiles/{id}/assignments) - Create a
senior-mlprofile with bigger per-bunker caps (8 GPU, 32 CPU) for senior engineers who need to run large training jobs. - Assign individual senior engineers with
mode=individual. Their direct assignment stacks with the groupsharedassignment — they get the bigger per-bunker headroom and still consume from the shared aggregate bucket. Most-restrictive wins, so the group bucket is still the binding constraint on aggregate usage.
Result: the team's collective spend is bounded; juniors can't spawn giant bunkers; seniors can but still share the team budget with everyone else.