Skip to content

cold-store-reaper: project-budget enforcement in put-backup never counts backup bytes — cumulative backups can cross the 3 TiB budget unrefused (PR #988) #1028

Closed

Description

@dezren39

Finding

PR #988 added project-budget enforcement to put_backup(), but the accounting it enforces against never counts backup bytes — so the cumulative backup stream can cross the 3 TiB project budget without ever being refused, and status's within_budget verdict has the same blind spot.

Evidence (verified on current main 76672b97df023607d8469d462f21420604fe92d5, via gh pr diff 988 and the local clone)

pkgs/cold-store-reaper/cold_store.py:

  • _upload_accounting() (lines ~459–482) defines cold_bytes as verified bundles only:
    SELECT coalesce(sum(bytes),0) n FROM bundles WHERE state='verified'
  • _check_upload_limits() (lines ~485–507) refuses when cold_bytes + nbytes > project_budget_bytes.
  • put_backup() (lines ~933–960) calls _check_upload_limits(conn, size, …) before uploading, then records the backup only in the transfer ledger (ledger_add(conn, "upload", size, "backup-upload", name)). Backups are unbundled Drive files (db-backups/<db>/); they never enter the bundles table.

Consequences:

  1. For a put_backup call, the budget side of the check compares bundle bytes + this one backup against the budget. Yesterday's backups contribute 0. A backup stream of any cumulative size passes as long as each individual file fits under budget − bundle_bytes and the rolling-24h hard stop (the only cumulative accounting backups do get, and it forgets after 24h).
  2. This is now the main growth path: backup-d1 and backup-supabase go through put_backup, and PR cold-store: nightly Turso exports via HTTP APIs #993 added a nightly Turso export through the same path. Retention (prune-backups) bounds count by age class, but nothing bounds total backup bytes against the budget the PR claims to enforce.
  3. status_data() reuses the same accounting, so its cold_bytes / within_budget readout also excludes every DB backup sitting in Drive.

PR #988's tests cover a single put_backup against a tiny budget (cold = 0 bundles + one file) and the bundle-path refusals, but no sequence of backups accumulating past the budget.

Deduped 2026-10-26: gh issue list --search 'put-backup project budget' returns only #938 (closed by PR #988 — this is a remainder of that enforcement, not a re-file of the original "never enforced" report) and #939 (retention, unrelated mechanism).

Why it matters

#938 was filed because the limits were "reported but never enforced". After PR #988 the daily hard stop is genuinely enforced for all paths, but the project budget is still effectively unenforced for the backup class — the class that grows every night — while the README ("Upload guardrails (enforced, not just reported)") and status both present it as enforced. A silent Drive blow-out past 3 TiB is the failure the budget exists to prevent.

Acceptance criteria

  • Triage decides the correct cumulative accounting for backups (e.g. a backups total tracked in the manifest/meta, Drive folder totals, or ledger sums over the retention window — whatever can be kept truthful), and put_backup's budget check uses it so that a sequence of backups whose cumulative total crosses project_budget_bytes is refused.
  • Regression test: two or more put_backup calls that individually fit but cumulatively cross the budget — the crossing call is refused with a ColdStoreError naming the project budget, and no Drive writes occur for it.
  • status's budget readout reflects the same accounting the enforcement uses (one implementation, per PR cold-store-reaper: enforce upload hard stop + budget (#938), stop persisting access token (#940), retention verified (#939) #988's own design principle), or explicitly scopes its claim to bundles.
  • Bundle-path enforcement and the daily hard stop behave exactly as before; existing TestUploadLimits tests still pass.

Source: PR #988 — #988 (commits a6ac921, 78f2707, 0789776); backup-stream growth via PR #993 — #993. Relates to closed #938. Filed by the pr-diff-review job, 2026-10-26.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Oct 26, 2026
  2. developing-today commented on Oct 26, 2026

    @developing-today
    Contributor
  3. added
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/cold-store-reaperPackage/app: cold-store-reaper (pkgs/ or apps/ in this repo)
    on Oct 26, 2026
  4. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 26, 2026
  6. 11 remaining items

  7. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. added a commit that references this issue on Oct 27, 2026
  10. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/cold-store-reaperPackage/app: cold-store-reaper (pkgs/ or apps/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/pr-diff-reviewFiled by the pr-diff-review jobstatus:donetask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions