Quota foundation: defaults, overrides, and resolution service #22

Closed
opened 2026-07-04 14:52:07 +02:00 by fable-5 · 1 comment
Collaborator

Context

Quota enforcement starts with pond creation (additional-ponds limit) and grows with uploads and membership; the three-level ladder (instance → user → pond) is a kickoff decision.

Scope

Implement quota_overrides + pond_usage models, instance-default quota keys in InstanceSettingsService (editors_per_pond=5, readers_per_pond=50, additional_ponds=0, storage_bytes=1GiB, max_file_bytes=25MiB), and a QuotaService with getEffective(quotaKey, {userId, pondId}) (pond override → user override → instance default) and checkAndConsume/release helpers updating pond_usage transactionally. Enforce additional_ponds in pond creation (#21). Admin endpoints for overrides come in M5 (#58).

Acceptance criteria

  • effective-value resolution covered by table-driven unit tests (all override combinations)
  • creating more shared ponds than allowed fails with a localized quota error naming the limit
  • usage counters survive concurrent consumption correctly (transactional test)
  • personal pond does not count against additional_ponds

Technical notes

  • data-model.md §Quotas, ADR 0011.
  • Quota checks must be race-safe: consume inside the same transaction as the guarded write.

Dependencies

Depends on #19, #21.

Size: ~1.5 days


Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add de and en), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.

## Context Quota enforcement starts with pond creation (additional-ponds limit) and grows with uploads and membership; the three-level ladder (instance → user → pond) is a kickoff decision. ## Scope Implement `quota_overrides` + `pond_usage` models, instance-default quota keys in `InstanceSettingsService` (`editors_per_pond=5`, `readers_per_pond=50`, `additional_ponds=0`, `storage_bytes=1GiB`, `max_file_bytes=25MiB`), and a `QuotaService` with `getEffective(quotaKey, {userId, pondId})` (pond override → user override → instance default) and `checkAndConsume`/`release` helpers updating `pond_usage` transactionally. Enforce `additional_ponds` in pond creation (#21). Admin endpoints for overrides come in M5 (#58). ## Acceptance criteria - [ ] effective-value resolution covered by table-driven unit tests (all override combinations) - [ ] creating more shared ponds than allowed fails with a localized quota error naming the limit - [ ] usage counters survive concurrent consumption correctly (transactional test) - [ ] personal pond does not count against `additional_ponds` ## Technical notes - data-model.md §Quotas, ADR 0011. - Quota checks must be race-safe: consume inside the same transaction as the guarded write. ## Dependencies Depends on #19, #21. **Size**: ~1.5 days --- *Conventions: English code/comments, clear human-readable code, no hard-coded UI strings (ADR 0012, add `de` **and** `en`), permission checks only via the shared guard (docs/architecture/permissions.md). Read the referenced ADRs before starting.*
fable-5 added this to the M2 — Ponds & pages, single-user editing milestone 2026-07-04 14:52:07 +02:00
fable-5 added the
auth
backend
labels 2026-07-04 14:52:07 +02:00
Author
Collaborator

Implemented in 64928f0, pipeline fully green.

Verification against acceptance criteria:

  • effective-value resolution: table-driven db tests for all override combinations, incl. 0 as a valid override (pond → user → instance)
  • exceeding additional_ponds yields 403 quota_exceeded with details={quotaKey, limit}; message localized de/en with the limit interpolated — covered by e2e (default 0 blocks, override 1 allows exactly one)
  • concurrency: two parallel checkAndConsume(600) against a 1000-byte limit — exactly one wins (pg_advisory_xact_lock inside the guarded transaction); release floors at 0
  • personal pond never counts against additional_ponds (count filters type=SHARED, deletedAt null)
  • live check on test.dorfteich.cloud: fixture-user (seeded override 100) created and trashed a shared pond via API

Note: instance default for additional_ponds is 0 by design — the seed grants fixtures an override of 100 so dev/stage flows remain exercisable. Admin endpoints for overrides land in M5 (#58).

Implemented in 64928f0, pipeline fully green. Verification against acceptance criteria: - effective-value resolution: table-driven db tests for all override combinations, incl. `0` as a valid override (pond → user → instance) - exceeding `additional_ponds` yields 403 `quota_exceeded` with `details={quotaKey, limit}`; message localized de/en with the limit interpolated — covered by e2e (default 0 blocks, override 1 allows exactly one) - concurrency: two parallel `checkAndConsume(600)` against a 1000-byte limit — exactly one wins (pg_advisory_xact_lock inside the guarded transaction); `release` floors at 0 - personal pond never counts against `additional_ponds` (count filters type=SHARED, deletedAt null) - live check on test.dorfteich.cloud: fixture-user (seeded override 100) created and trashed a shared pond via API Note: instance default for additional_ponds is 0 by design — the seed grants fixtures an override of 100 so dev/stage flows remain exercisable. Admin endpoints for overrides land in M5 (#58).
Sign in to join this conversation.
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#22
No description provided.