Builds the API + service layer the P1 schema migration 0084 set up:
- src/lib/validators/reports.ts: new schemas for list/create on runs +
full CRUD on schedules. Locked enums for kind / output / cadence /
status so the route layer can reject invalid combinations early.
- src/lib/services/report-runs.service.ts: list with kind/status/template
filters, create with cross-port template guard + config.kind
discriminator check, updateReportRunStatus for the future P3 worker to
flip status through pending/rendering/complete/failed.
- src/lib/services/report-schedules.service.ts: full CRUD plus
nextRunFor() deterministic cadence math. nextRunAt is recomputed on
cadence change or on re-enable (off->on) but left untouched on no-op
edits so a mid-cycle recipient swap doesn't slip the fire-time.
- /api/v1/reports/runs (GET + POST) + /api/v1/reports/runs/[id] (GET)
- /api/v1/reports/schedules (GET + POST) +
/api/v1/reports/schedules/[id] (GET + PATCH + DELETE)
- tests/integration/report-runs-schedules.test.ts: 9 cases covering the
cross-port FK guard, the config.kind cross-check, listing filters,
cadence math for all three v1 cadences, the no-op-doesn't-slip rule,
and the ON DELETE SET NULL contract on schedule deletion.
Permission gating: list/get on reports.view_dashboard (read), all mutations
on reports.export (write). Matches the existing /reports/templates routes.
P3 (the BullMQ render+email queue) is the next slice; it'll consume the
pending rows produced here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>