# CLAUDE.md — Phase 10: Health Budget Framework

## Scope of This File

This file instructs you (Claude Code) to implement **Phase 10 only** of the Seedbox Command Center.

**Do not implement phases 11 through 15.** Stay in scope.

## Authoritative Specification

`PROJECT.md` §8.6 (Health Budget Framework) is authoritative. Read it. Also read:
- `PROGRESS.md` and `DECISIONS.md`.
- The Phase 6 `health_budget_burn` trigger stub — Phase 10 turns the stub into a real trigger.

## Phase 10 Mission

SRE-style error budgets, applied to seedbox operations. The operator sets monthly (or weekly, or quarterly) tolerances against specific failure modes — "up to one hit-and-run violation per quarter", "ratio below 0.5 for no more than 4 hours per week", etc. The system tracks consumption against each budget and surfaces warnings when burn rate is too fast relative to time elapsed.

After Phase 10, the operator catches problems before they become incidents because the burn-rate signal precedes the threshold breach.

## Deliverables Checklist

Phase 10 is complete when every item below is true:

- [ ] SQLite migration `010_phase10_budgets.sql` adds the `health_budget_state` table per Appendix A.
- [ ] Budget definitions live in `config/health-budgets.yaml`. Schema:
    ```yaml
    budgets:
      - name: "hr_violations_per_quarter"
        capacity: 1.0                        # 1 violation total
        period: "quarter"                    # week | month | quarter | custom
        unit: "violations"
        consumed_by:                         # which events count
          event_type: "h_and_r_violation_recorded"
          per_event_cost: 1.0
        notify_at:                           # burn-rate thresholds for alerts
          - { burn_pct_of_period: 50, when_pct_of_capacity_consumed: 25 }
          - { burn_pct_of_period: 75, when_pct_of_capacity_consumed: 50 }
      - name: "ratio_below_0_5_hours_per_week"
        capacity: 4.0                        # 4 hours
        period: "week"
        unit: "hours"
        consumed_by:
          metric_query: "..."                # SQL/DuckDB query producing duration
    ```
- [ ] Budget tracker (`internal/budgets`):
    - On every relevant event (h&r recorded, scrape failure, etc.), the tracker increments `health_budget_state.consumed` for matching budgets in the current period.
    - For metric-based budgets (duration-of-condition), the tracker queries DuckDB at a configurable cadence (default 1 minute) and updates the cumulative consumed value.
    - Period rollover is automatic: when the current period ends, a new `health_budget_state` row is inserted for the next period; the prior period's row is retained for history.
- [ ] Burn-rate calculation: at any moment, burn_rate = (consumed / capacity) / (elapsed_in_period / period_length). A burn rate > 1.0 means consuming faster than budget allows.
- [ ] API endpoints (auth required, under `/api/budgets`):
    - `GET /api/budgets` — list with current state + burn rate.
    - `GET /api/budgets/:name/state` — full current state.
    - `GET /api/budgets/:name/history?periods=N` — past N periods' final consumption.
- [ ] Phase 6's `health_budget_burn` trigger is upgraded to fire when burn rate crosses a configured threshold (per the `notify_at` clauses in the YAML).
- [ ] Frontend:
    - Dashboard: a "Budgets" panel showing each budget's current consumption with a sparkline of recent burn rate, color-coded green/yellow/red.
    - `/budgets/:name`: full history view with overlaid actuals across the last N periods.
    - Recommendations: when a budget's burn rate has been high for a while, the Phase 7 recommendation engine emits "Consider widening budget X / investigating root cause".
- [ ] Tests: period rollover at boundary; concurrent event-driven and metric-driven budgets do not double-count; burn-rate calculation correct at edge cases (start of period, end of period, exactly at capacity).
- [ ] `PROGRESS.md` and `DECISIONS.md` updated. `DECISIONS.md` records: how period boundaries align (UTC midnight? operator-configured timezone?), the cadence for metric-based budget queries, the policy on retroactive consumption when a metric query catches up state from a downtime.

## Phase 10 Database Scope

Migration `010_phase10_budgets.sql` creates:
- `health_budget_state`

## Repository Layout

```
internal/
  budgets/
    tracker.go               # event-driven + metric-driven consumption
    period.go                # period math, rollover
    types.go                 # typed YAML schema
    loader.go                # YAML loader with hot reload
    *_test.go
config/
  health-budgets.yaml
  health-budgets.example.yaml
```

Files added under existing packages:
- `internal/server/routes_budgets.go`
- `internal/rules/triggers/health_budget_burn.go` — upgraded from Phase 6 stub.
- `internal/intelligence/recommendations.go` — extended with budget-driven recommendations.

Frontend additions:
- `web/src/pages/Budgets.tsx`
- `web/src/pages/BudgetDetail.tsx`
- `web/src/components/BudgetSparkline.tsx`

## Working Rules

**Periods align to UTC midnight by default.** Operators can override per budget (`period_anchor: "operator"` and a configurable offset). Document the default explicitly so an operator in a non-UTC timezone is not surprised.

**Budgets are observation tools, not enforcement.** Phase 10 does NOT auto-disable filters when a budget is over capacity. It alerts; the operator decides. Auto-enforcement is a non-goal.

**Metric-based budgets are stateful.** When the binary restarts, the metric tracker queries DuckDB for the time-range since the last update timestamp; do not lose consumption over restarts.

**Retroactive consumption is allowed.** If the metric query finds new evidence (e.g., a tracker that was down for 2 hours overnight contributing to a "tracker scrape failure for no more than 30 minutes per day" budget), the consumption is added to the period it occurred in, even if that period has already ended. Past-period rows are mutable until period_end + 24h, then frozen.

**Budget definitions can reference any metric in the system.** The `metric_query` field accepts an opaque DuckDB query (sandboxed via prepared statements where possible; document the security implications). Operators are trusted; the query language is the operator's escape hatch when the typed event sources don't cover their case.

## What "Phase 10 Complete" Looks Like

After Phase 10 ships, the operator can:

1. Define a "no more than 4 hours of ratio < 0.5 per week" budget in `config/health-budgets.yaml`.
2. Open the dashboard. See the budget at 0% / 4 hours; burn rate green.
3. A tracker has a bad day; ratio dips below 0.5 for 1.5 hours. Watch the budget tick up to 37.5%; burn rate yellow (depending on time elapsed in the week).
4. Receive a push notification when burn rate crosses the alarm threshold (Phase 6's trigger fires).
5. The Phase 7 recommendation engine surfaces "Consider investigating Tracker T scrape reliability — burning the ratio budget at 2x expected rate."
6. The week ends. The budget rolls over. The prior week's final state is preserved in history; the new week starts at 0.

## Begin

Read `PROJECT.md §8.6`. Start with the period math (it's the part most likely to have edge cases). Then the event-driven tracker, then the metric-driven tracker. Wire the upgraded trigger. UI last. Stop at the end of Phase 10.
