feat(budget): multi-period per-workspace LLM budget (hourly/daily/weekly/monthly) #2009
Reference in New Issue
Block a user
Delete Branch "feat/mc-multiperiod-workspace-budget"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Extends the per-workspace LLM budget from a single monthly ceiling to four independent rolling windows — hourly / daily / weekly / monthly (#49). Gives the canvas Budget tab a real lever against runaway LLM spend (origin: the reno-stars opus drain).
SSOT design
budget_periods.go— single source of truth: the period set + rolling windows, one FILTERed per-period spend query over the ledger, and the pureparse/encode/exceededPeriodslogic. Adding a period = one line.workspaces.budget_limitsjsonb (canonical config, backfilled from the legacy monthlybudget_limit) +workspace_spend_eventsledger.registry.go) — derives the spend increment from the agentʼs existing cumulative report (delta vs prev; reset-aware) → ledger row. Server owns windowing; no runtime change.budget.go) — per-period limit/spend/remaining; accepts new{budget_limits:{…}}and legacy{budget_limit}(→ monthly); legacy fields still emitted +budget_limitkept synced (rollout back-compat).0= block-all (preserved); null/absent = no limit.a2a_proxy.go) —402if any configured periodʼs rolling-window spend ≥ its limit; fail-open on DB error.BudgetSection— four period rows (USD limit input + spend/limit + bar).Tests
Pure SSOT (parse/encode/exceededPeriods); GET/PATCH + multi-period + A2A enforcement (sqlmock migrated to the new two-query flow; shared
expectBudgetCheckhelpers updated); canvas behavioral + per-period progress/aria.go build+vet+ full handlers suite + migrations + canvas vitest all green locally.Note: the duplicate
components/__tests__/BudgetSection.test.tsx(old single-limit UI) was repurposed to a focused per-period progress/aria suite — behavioral coverage now lives intabs/__tests__/BudgetSection.test.tsx(one component, no parallel identical suites).Refs #49.
🤖 Generated with Claude Code
qa-review APPROVE. SSOT design is sound: budget_periods.go is the single source for periods/windows + pure parse/encode/exceededPeriods (table-tested incl. 0=block-all, null-clears, negative/unknown dropped). Spend is a server-owned rolling-window ledger derived from the existing heartbeat delta — no runtime change, reset-aware. GET/PATCH cover multi-period + legacy back-compat; A2A enforcement blocks on ANY period over (fail-open on DB error). Test migration is thorough: pure + sqlmock GET/PATCH/multi-period/enforcement (two-query flow), shared expectBudgetCheck helpers updated, canvas behavioral + per-period progress/aria. go build/vet/full handlers suite/migrations + canvas vitest all green. Coverage matches the surface.
security-review APPROVE. No credential/secret surface in the diff. Input validation: PATCH rejects negative limits + unknown periods (400); agent-reported spend is clamped upstream (#615 maxMonthlySpend) and the ledger delta is non-negative (reset-aware). Enforcement fail-OPEN on DB error is the existing, intentional contract (a budget-check outage must not block legitimate traffic) — acceptable since the cap is a cost guardrail, not an authz boundary. 0=block-all preserved. jsonb config parsed safely (tolerates NULL/malformed). No SSRF/path/token surface touched. LGTM.
/qa-recheck
/security-recheck