fix(quota): correct forgejo lfs quota so users can push and tiers hold #21

Sammanfogat
supernaut sammanfogade 5 incheckningar från fix/quota in i main 2026-07-07 22:38:21 +00:00
Ägare

What & why

Users in an LFS quota group (i.e. every user — the reconciler assigns one)
could not push or create repositories: Forgejo returned 413 "quota exceeded".
Root cause was a chain of issues in how the reconciler provisions Forgejo quota,
compounded by two non-obvious Forgejo quota semantics we verified empirically:

  • Quota rules are global by name. Every group's rule was named lfs, so all
    three tiers shared one rule pinned at limit 0 → LFS blocked for everyone.
  • [quota.default] does not apply to users in a group, and any subject a
    group's rules don't cover is blocked (0), not unlimited. The groups only
    covered size:git:lfs, so repo/asset storage and repo creation were blocked.
  • Overlapping rules combine most-permissively. A broad size:all=-1 rule
    unblocks everything but also overrides (defeats) the per-tier LFS cap.

Changes (per commit)

  • fix(reconciler) give each LFS group its own rule (rule name = group name) and
    reconcile its limit every run — fixes the shared-rule-at-0 collision.
  • feat(reconciler) give the free tier a 0.05 GiB LFS allowance (was a hard block).
  • fix(reconciler) attach a shared base rule so group members aren't blocked on
    uncovered subjects (repo/asset storage, repo creation).
  • docs(forgejo) correct the [quota.default] comment to reflect the real model.
  • fix(reconciler) scope base to size:repos:all + size:assets:all (not
    size:all) so non-LFS stays unlimited and the size:git:lfs tier cap binds.

Resulting model

Each LFS group has: base (size:repos:all+size:assets:all = -1, unlimited
non-LFS) + a per-tier size:git:lfs rule (0.05 / 5 / 50 GiB). ensure_group is
now idempotent and self-healing (PATCHes limits/subjects every run).

Verification

  • Applied live to prod during the incident; the affected user confirmed pushes now
    succeed. main will match the deployed host state on merge.
  • Repo create-with-content in the real lfs-xl group → 201 (non-LFS unblocked).
  • Rule-combination semantics confirmed via isolated, self-cleaning throwaway-user
    tests.

Caveat

The LFS tier cap is enforced by the verified rule-combination semantics (LFS is
covered by only the tier rule → its limit binds) plus the non-LFS-works test; it
was not confirmed by an actual over-limit LFS push (impractical to stage).

## What & why Users in an LFS quota group (i.e. every user — the reconciler assigns one) could not push or create repositories: Forgejo returned `413` "quota exceeded". Root cause was a chain of issues in how the reconciler provisions Forgejo quota, compounded by two non-obvious Forgejo quota semantics we verified empirically: - **Quota rules are global by name.** Every group's rule was named `lfs`, so all three tiers shared one rule pinned at limit `0` → LFS blocked for everyone. - **`[quota.default]` does not apply to users in a group**, and any subject a group's rules don't cover is **blocked (0)**, not unlimited. The groups only covered `size:git:lfs`, so repo/asset storage and repo creation were blocked. - **Overlapping rules combine most-permissively.** A broad `size:all=-1` rule unblocks everything but also overrides (defeats) the per-tier LFS cap. ## Changes (per commit) - `fix(reconciler)` give each LFS group its own rule (rule name = group name) and reconcile its limit every run — fixes the shared-rule-at-0 collision. - `feat(reconciler)` give the free tier a 0.05 GiB LFS allowance (was a hard block). - `fix(reconciler)` attach a shared `base` rule so group members aren't blocked on uncovered subjects (repo/asset storage, repo creation). - `docs(forgejo)` correct the `[quota.default]` comment to reflect the real model. - `fix(reconciler)` scope `base` to `size:repos:all` + `size:assets:all` (not `size:all`) so non-LFS stays unlimited **and** the `size:git:lfs` tier cap binds. ## Resulting model Each LFS group has: `base` (`size:repos:all`+`size:assets:all` = -1, unlimited non-LFS) + a per-tier `size:git:lfs` rule (0.05 / 5 / 50 GiB). `ensure_group` is now idempotent and self-healing (PATCHes limits/subjects every run). ## Verification - Applied live to prod during the incident; the affected user confirmed pushes now succeed. **main will match the deployed host state on merge.** - Repo create-with-content in the real `lfs-xl` group → `201` (non-LFS unblocked). - Rule-combination semantics confirmed via isolated, self-cleaning throwaway-user tests. ## Caveat The LFS tier cap is enforced by the verified rule-combination semantics (LFS is covered by only the tier rule → its limit binds) plus the non-LFS-works test; it was **not** confirmed by an actual over-limit LFS push (impractical to stage).
supernaut lade till 5 incheckningar 2026-07-07 22:38:07 +00:00
Forgejo quota rules are global by name, so naming every group's rule "lfs" made all three tiers share one rule pinned at limit 0 — silently blocking LFS for Pro/XL users (kofish couldn't push to hitta_ami). Give each group a distinct rule (name = group name), PATCH the limit every run so drift self-heals, and detach the legacy shared "lfs" rule on migration.
Raise lfs-basic from 0 (block LFS) to 53687091 bytes (0.05 GiB). ensure_group PATCHes each rule's limit every run, so this takes effect on the next reconcile after deploy.
Forgejo does not apply [quota.default] to users who are in a quota group, and blocks any subject the group's rules don't cover. The lfs groups defined only a size:git:lfs rule, so members couldn't create repos or push non-LFS content (413, even after the lfs limit itself was fixed). Attach a shared "base" rule (size:all = -1) to every group; the per-tier size:git:lfs rule still caps LFS.
[quota.default] applies only to users in no quota group; Forgejo does not compose it with a member's group, and blocks any subject the group's rules don't cover. Note that the lfs_* groups therefore carry a base rule for non-LFS storage.
Forgejo combines overlapping quota rules most-permissively, so a base rule at size:all=-1 overrode the per-tier size:git:lfs caps — LFS was effectively unlimited for every tier. Scope base to size:repos:all + size:assets:all: non-LFS storage stays unlimited while size:git:lfs is covered only by the tier rule and actually binds. The PATCH reconciles an older size:all base in place.
supernaut sammanfogade incheckning 385aa42136 till main 2026-07-07 22:38:21 +00:00
supernaut tog bort grenen fix/quota 2026-07-07 22:38:21 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra!21
Ingen beskrivning angiven.