# CLAUDE.md — Phase 6: Push Notifications and Rules Engine

## Scope of This File

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

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

## Authoritative Specification

`PROJECT.md` §7.6 (Notification Delivery) and §8.11 (Notification System) are authoritative. Appendix C lists the built-in trigger types; Appendix D lists the recommended initial rules (shipped disabled). Read all three. Also read:
- `PROGRESS.md` and `DECISIONS.md`.
- The Phase 4 event bus and Phase 5 filter performance — Phase 6's rules engine subscribes to bus events and queries snapshot tables.

## Phase 6 Mission

A rules-engine-driven notification system that fires on threshold conditions and event-bus events, with web push as the primary delivery channel and operator-configurable alternative channels. Push subscriptions are managed per-device; the rules engine respects cooldowns to prevent notification storms.

After Phase 6, the operator gets actionable alerts on their phone for the conditions they actually care about, with cooldowns tuned to their tolerance for noise.

## Deliverables Checklist

Phase 6 is complete when every item below is true:

- [ ] VAPID key pair generated at first run if not present, stored in `secrets` keyed `push:vapid:private` and `push:vapid:public`. The public key is exposed via `GET /api/push/public-key` for the frontend to use during subscription.
- [ ] SQLite migration `007_phase6_notifications.sql` adds `notification_rules` and `notification_log` per Appendix A. The `push_subscriptions` table was created in Phase 2 (auth-related); Phase 6 starts writing rows to it.
- [ ] Push subscription endpoints (auth required):
    - `POST /api/push/subscribe` — store endpoint + p256dh + auth keys.
    - `DELETE /api/push/subscribe/:id`.
    - `GET /api/push/subscriptions` — list operator's devices.
- [ ] Rules engine (`internal/rules`):
    - Loads rule definitions from `config/notification-rules.yaml` (operator-managed, hot-reloaded).
    - Supports every Appendix C trigger type:
        - `ratio_threshold`, `ratio_velocity_change`, `unsatisfied_threshold`,
        - `hr_risk_imminent` (uses Phase 7's predictor when available; Phase 6 ships a simpler heuristic if Phase 7 hasn't landed yet — but Phase 7 IS later, so Phase 6 must ship the simpler version),
        - `automation_disconnected`, `client_unreachable`, `filter_grab`,
        - `torrent_completed`, `disk_threshold`, `tracker_scrape_error`,
        - `health_budget_burn` (stub — Phase 10 owns the actual budget tracker),
        - `decision_recommendation` (stub — Phase 7 fills this in),
        - `custom_query`.
    - Each rule fires at most once per cooldown window. Last-fired tracked in `notification_rules.last_fired_at`.
- [ ] Delivery dispatcher (`internal/notifications`):
    - Web Push via `github.com/SherClockHolmes/webpush-go` (or equivalent).
    - Discord webhook channel.
    - ntfy.sh channel.
    - Email channel (SMTP, configurable host/port/from).
    - Dashboard activity feed (always populated, regardless of channels).
    - Failed deliveries increment `push_subscriptions.failure_count`; subscriptions past a threshold are auto-cleaned with an audit entry.
- [ ] API endpoints:
    - `GET /api/notifications/rules`
    - `POST /api/notifications/test` — fire a test notification through every operator-configured channel.
    - `GET /api/notifications/log?range=...` — recent deliveries.
- [ ] Frontend:
    - PWA service worker handles push events, displays notifications, click-through routes to the relevant page.
    - Settings → Notifications: subscription enable/disable per device, channel configuration (Discord webhook URL, ntfy topic, SMTP details), rule visibility (operator sees current rule state but edits YAML).
    - On first authenticated visit on a device, prompt the operator to enable push.
- [ ] Tests: rules engine fires expected triggers from synthetic events; cooldown prevents storms; channel dispatchers handle failures gracefully; service worker renders notifications correctly (manual verification noted in TESTING.md).
- [ ] `config/notification-rules.example.yaml` ships every Appendix D recommended rule, all disabled. Operators copy and enable.
- [ ] `PROGRESS.md` updated. `DECISIONS.md` records: chosen web-push library, SMTP provider integration (if any), the cooldown algorithm.

## Phase 6 Database Scope

Migration `007_phase6_notifications.sql` creates:
- `notification_rules`
- `notification_log`

`push_subscriptions` already exists (Phase 2). Do not pre-create `health_budget_state` or `decision_log` — those belong to later phases.

## Repository Layout

```
internal/
  rules/
    engine.go                # the trigger evaluator
    types.go                 # typed rule configs
    loader.go                # YAML → typed rules with validation
    triggers/                # one file per Appendix C trigger type
      ratio_threshold.go
      ratio_velocity.go
      unsat_threshold.go
      hr_risk.go
      automation_disconnected.go
      client_unreachable.go
      filter_grab.go
      torrent_completed.go
      disk_threshold.go
      tracker_scrape_error.go
      custom_query.go
    *_test.go
  notifications/
    dispatcher.go            # rule fire → channel dispatch
    channels/
      webpush.go
      discord.go
      ntfy.go
      email.go
      dashboard.go
    *_test.go
config/
  notification-rules.example.yaml
```

Files added under existing packages:
- `internal/server/routes_notifications.go`
- `internal/server/routes_push.go`

Frontend additions:
- `web/src/sw-extensions.ts` — service worker push handler (wired into vite-plugin-pwa).
- `web/src/pages/Settings/Notifications.tsx`
- `web/src/lib/push.ts` — subscribe/unsubscribe helpers.

## Working Rules

**Rules engine doesn't poll.** Threshold rules (ratio_threshold, disk_threshold) evaluate on every event that could move the metric — they subscribe to specific topics on the bus. Periodic re-evaluation is a fallback every 5 minutes for slow-moving metrics.

**Cooldowns matter.** Default cooldown for every Appendix D rule is recorded in the example file. Operators can override per rule. The engine never fires a rule whose `(now - last_fired_at) < cooldown`.

**Recommended rules ship disabled.** Per Appendix D, every shipped rule starts `enabled: false`. Operators must opt in.

**Test endpoint sends real notifications.** `POST /api/notifications/test` exercises every channel the operator has configured. The dispatcher's test path is the same as the production path.

**Failure handling is structured.** Push failures classify into `gone` (410 endpoint), `transient` (5xx, retried), and `unknown`. Each gets its own audit signature.

**Service worker is the one Phase 6 surface the embedded frontend has to register correctly.** vite-plugin-pwa generates the registration; ensure the `injectManifest` strategy is configured so our custom push handler is injected without breaking workbox precaching.

## What "Phase 6 Complete" Looks Like

After Phase 6 ships, the operator can:

1. Open the PWA on their iPhone. Grant push permission. Subscription registered.
2. Enable the "real ratio dropped below threshold" rule for one tracker; tighten the threshold to test it. Wait for the next scrape that triggers; receive a push notification within seconds.
3. Tap the notification to deep-link into the affected tracker's detail page.
4. Manually disconnect autobrr; receive the "automation disconnected" alert after the configured grace period.
5. Configure a Discord webhook URL in Settings; receive the same alerts in Discord.
6. Reset a notification: edit `notification-rules.yaml`, change the cooldown, observe the new value reflected in the next evaluation cycle without restart.

## Begin

Read Appendices C and D. Start by generating the VAPID keypair and storing in `secrets`. Build the rules engine skeleton, then the trigger types one at a time, then the dispatcher and channels. Service worker last. Stop at the end of Phase 6.
