Quota enforcement at the storage write chokepoint (v0.3 #14) #34

Merged
Cordy merged 3 commits from feat/quota-enforcement into main 2026-07-25 18:25:14 +00:00
Owner

Closes the last v0.3 functional gap: quota enforcement (reporting shipped in v0.2.1).

Design: a internal/storage/quota decorator around the single storage Write chokepoint (same pattern as the encrypt decorator), so REST, WebDAV PUT, and TUS finalize are all covered by one implementation.

  • MaxFileBytes — rejects a single upload over the cap, enforced during the stream (works on any backend, incl. S3).
  • MaxTotalBytes / ReserveBytes — global capacity guard, feature-detected via storage.Usager (POSIX statfs); silently skipped on backends without it (S3).
  • New sentinel storage.ErrQuotaExceeded507 Insufficient Storage on WebDAV, JSON error on REST, 507 on TUS finalize.
  • Config: storage.quota { enabled, maxFileBytes, maxTotalBytes, reserveBytes } + CAIRN_QUOTA_* env overrides. Wired as the outermost storage decorator in main.go (counts plaintext size, sees forwarded Usage).

Bonus fix: the encrypt decorator now forwards Usage, so quota reporting (and this enforcement's capacity guard) work on POSIX+encryption deployments — previously the decorator hid the Usager capability and /quota fell to 501. Also corrects the stale "age is NOT post-quantum" comment in encrypt.go.

Per-user quotas are deferred to v0.4 (they need the per-user home-dir/accounting model, #16). Unit tests cover max-file, total/reserve guards, usage forwarding, non-Usager skip, and passthrough.

Closes the last v0.3 functional gap: quota *enforcement* (reporting shipped in v0.2.1). **Design:** a `internal/storage/quota` decorator around the single storage `Write` chokepoint (same pattern as the encrypt decorator), so REST, WebDAV PUT, and TUS finalize are all covered by one implementation. - **MaxFileBytes** — rejects a single upload over the cap, enforced *during* the stream (works on any backend, incl. S3). - **MaxTotalBytes / ReserveBytes** — global capacity guard, feature-detected via `storage.Usager` (POSIX statfs); silently skipped on backends without it (S3). - New sentinel `storage.ErrQuotaExceeded` → **507 Insufficient Storage** on WebDAV, JSON error on REST, 507 on TUS finalize. - Config: `storage.quota { enabled, maxFileBytes, maxTotalBytes, reserveBytes }` + `CAIRN_QUOTA_*` env overrides. Wired as the outermost storage decorator in `main.go` (counts plaintext size, sees forwarded Usage). **Bonus fix:** the encrypt decorator now forwards `Usage`, so quota *reporting* (and this enforcement's capacity guard) work on POSIX+encryption deployments — previously the decorator hid the `Usager` capability and `/quota` fell to 501. Also corrects the stale "age is NOT post-quantum" comment in `encrypt.go`. Per-user quotas are deferred to v0.4 (they need the per-user home-dir/accounting model, #16). Unit tests cover max-file, total/reserve guards, usage forwarding, non-Usager skip, and passthrough.
Cordy merged commit 738a7593fa into main 2026-07-25 18:25:14 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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: Cordy/Cairn#34
No description provided.