diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 4a28503..0706cbd 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -29,10 +29,10 @@ Requirements for initial release. Each maps to roadmap phases. ### Infrastructure -- [ ] **INFR-01**: Database migrations create service_health_checks and alert_events tables with indexes on created_at +- [x] **INFR-01**: Database migrations create service_health_checks and alert_events tables with indexes on created_at - [ ] **INFR-02**: Admin API routes protected by Firebase Auth with admin email check - [ ] **INFR-03**: 30-day rolling data retention cleanup runs on schedule -- [ ] **INFR-04**: Analytics writes use existing Supabase connection, no new database infrastructure +- [x] **INFR-04**: Analytics writes use existing Supabase connection, no new database infrastructure ## v2 Requirements @@ -75,8 +75,8 @@ Which phases cover which requirements. Updated during roadmap creation. | Requirement | Phase | Status | |-------------|-------|--------| -| INFR-01 | Phase 1 | Pending | -| INFR-04 | Phase 1 | Pending | +| INFR-01 | Phase 1 | Complete | +| INFR-04 | Phase 1 | Complete | | HLTH-02 | Phase 2 | Pending | | HLTH-03 | Phase 2 | Pending | | HLTH-04 | Phase 2 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index fa835fa..f626bfb 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -28,7 +28,7 @@ Decimal phases appear between their surrounding integers in numeric order. 2. All new tables use the existing Supabase client from `config/supabase.ts` — no new database connections added 3. `AlertModel.ts` exists and its CRUD methods can be called in isolation without errors 4. Migration SQL can be run against the live Supabase instance and produces the expected schema -**Plans:** 2 plans +**Plans:** 1/2 plans executed Plans: - [ ] 01-01-PLAN.md — Migration SQL + HealthCheckModel + AlertEventModel @@ -76,7 +76,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Data Foundation | 0/2 | Not started | - | +| 1. Data Foundation | 1/2 | In Progress| | | 2. Backend Services | 0/TBD | Not started | - | | 3. API Layer | 0/TBD | Not started | - | | 4. Frontend | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 1261231..61b9402 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -10,27 +10,27 @@ See: .planning/PROJECT.md (updated 2026-02-24) ## Current Position Phase: 1 of 4 (Data Foundation) -Plan: 0 of TBD in current phase -Status: Ready to plan -Last activity: 2026-02-24 — Roadmap created +Plan: 1 of TBD in current phase +Status: In progress +Last activity: 2026-02-24 — Completed 01-01 (monitoring tables migration + model layer) -Progress: [░░░░░░░░░░] 0% +Progress: [█░░░░░░░░░] 10% ## Performance Metrics **Velocity:** -- Total plans completed: 0 -- Average duration: — -- Total execution time: 0 hours +- Total plans completed: 1 +- Average duration: ~8 min +- Total execution time: ~0.13 hours **By Phase:** | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| -| - | - | - | - | +| 01-data-foundation | 1 | ~8 min | ~8 min | **Recent Trend:** -- Last 5 plans: — +- Last 5 plans: 01-01 (8 min) - Trend: — *Updated after each plan completion* @@ -46,6 +46,10 @@ Recent decisions affecting current work: - Architecture: Health probes decoupled from document processing as separate Cloud Function export - Architecture: Analytics writes are always fire-and-forget (never await on critical path) - Architecture: Alert recipient stored in config, not hardcoded (PITFALL-8 prevention) +- 01-01: TEXT + CHECK constraint used for enum columns (not PostgreSQL ENUM types) +- 01-01: getSupabaseServiceClient() called per-method, never cached at module level +- 01-01: checked_at column separate from created_at on service_health_checks (probe time vs DB write time) +- 01-01: Forward-only migrations only (no rollback scripts) ### Pending Todos @@ -60,5 +64,5 @@ None yet. ## Session Continuity Last session: 2026-02-24 -Stopped at: Roadmap created, STATE.md initialized. Ready to plan Phase 1. +Stopped at: Completed 01-01-PLAN.md — monitoring tables migration + HealthCheckModel + AlertEventModel Resume file: None diff --git a/.planning/phases/01-data-foundation/01-01-SUMMARY.md b/.planning/phases/01-data-foundation/01-01-SUMMARY.md new file mode 100644 index 0000000..8444d50 --- /dev/null +++ b/.planning/phases/01-data-foundation/01-01-SUMMARY.md @@ -0,0 +1,135 @@ +--- +phase: 01-data-foundation +plan: 01 +subsystem: database +tags: [supabase, postgresql, migrations, typescript, monitoring, health-checks, alerts] + +# Dependency graph +requires: [] +provides: + - service_health_checks table with status CHECK constraint, JSONB probe_details, checked_at column + - alert_events table with alert_type/status CHECK constraints, lifecycle timestamps + - HealthCheckModel TypeScript class with CRUD static methods + - AlertEventModel TypeScript class with CRUD static methods + - Barrel exports for both models and their types from models/index.ts +affects: + - 01-02 (Phase 1 Plan 2 — next data foundation plan) + - Phase 2 (health probe services will write to service_health_checks) + - Phase 2 (alert service will write to alert_events and use findRecentByService for deduplication) + - Phase 3 (API endpoints will query both tables) + +# Tech tracking +tech-stack: + added: [] + patterns: + - Static class model pattern (no instantiation — all methods are static async) + - getSupabaseServiceClient() called per-method, never cached at module level + - PGRST116 error code handled as null return (not an exception) + - Input validation in model create() methods before any DB call + - Record for JSONB fields (no any types) + - Named Winston logger import: import { logger } from '../utils/logger' + - IF NOT EXISTS on all DDL (idempotent forward-only migrations) + - TEXT + CHECK constraint pattern for enums (not PostgreSQL ENUM types) + +key-files: + created: + - backend/src/models/migrations/012_create_monitoring_tables.sql + - backend/src/models/HealthCheckModel.ts + - backend/src/models/AlertEventModel.ts + modified: + - backend/src/models/index.ts + +key-decisions: + - "TEXT + CHECK constraint used for status/alert_type columns (not PostgreSQL ENUM types) — consistent with existing project pattern" + - "getSupabaseServiceClient() called per-method (not module-level singleton) — follows Research finding about Cloud Function cold start issues" + - "checked_at column added to service_health_checks separate from created_at — records actual probe run time, not DB insert time" + - "Forward-only migration only (no rollback scripts) — per user decision documented in CONTEXT.md" + - "RLS enabled on both tables with no explicit policies — service role key bypasses RLS; user-facing policies deferred to Phase 3" + +patterns-established: + - "Model static class: all methods static async, getSupabaseServiceClient() per-method, PGRST116 → null" + - "Input validation before Supabase call: non-empty string check, union type allowlist check" + - "Error re-throw with method prefix: 'HealthCheckModel.create: ...' for log traceability" + - "deleteOlderThan(days): compute cutoff in JS then filter with .lt() — Supabase client does not support date arithmetic in filters" + +requirements-completed: [INFR-01, INFR-04] + +# Metrics +duration: 8min +completed: 2026-02-24 +--- + +# Phase 01 Plan 01: Data Foundation Summary + +**SQL migration + TypeScript model layer for monitoring: service_health_checks and alert_events tables with HealthCheckModel and AlertEventModel static classes using getSupabaseServiceClient() per-method** + +## Performance + +- **Duration:** ~8 min +- **Started:** 2026-02-24T16:29:39Z +- **Completed:** 2026-02-24T16:37:45Z +- **Tasks:** 2 +- **Files modified:** 4 + +## Accomplishments + +- Created migration 012 with both monitoring tables, CHECK constraints on all enum columns, JSONB columns for flexible metadata, INFR-01 required created_at indexes, composite indexes for dashboard queries, and RLS on both tables +- Created HealthCheckModel with 4 static methods: create (with input validation), findLatestByService, findAll (with optional filters), deleteOlderThan (30-day retention) +- Created AlertEventModel with 6 static methods: create (with validation), findActive, acknowledge, resolve, findRecentByService (deduplication support for Phase 2), deleteOlderThan +- Updated models/index.ts with barrel exports for both model classes and all 4 types + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Create monitoring tables migration** - `ad6f452` (feat) +2. **Task 2: Create HealthCheckModel and AlertEventModel with barrel exports** - `4a620b4` (feat) + +**Plan metadata:** (docs commit — see below) + +## Files Created/Modified + +- `backend/src/models/migrations/012_create_monitoring_tables.sql` - DDL for service_health_checks and alert_events tables with indexes and RLS +- `backend/src/models/HealthCheckModel.ts` - CRUD model for service_health_checks table, exports ServiceHealthCheck and CreateHealthCheckData types +- `backend/src/models/AlertEventModel.ts` - CRUD model for alert_events table with lifecycle methods (acknowledge/resolve), exports AlertEvent and CreateAlertEventData types +- `backend/src/models/index.ts` - Added barrel exports for both new models and their types + +## Decisions Made + +- TEXT + CHECK constraint used for status/alert_type columns (not PostgreSQL ENUM types) — consistent with existing project pattern established in prior migrations +- getSupabaseServiceClient() called per-method (not cached at module level) — per Research finding about potential Cloud Function cold start issues with module-level Supabase client caching +- checked_at column kept separate from created_at on service_health_checks — probe timestamp vs. DB write timestamp are logically distinct (Research Pitfall 5) +- No rollback scripts in migration — forward-only per user decision documented in CONTEXT.md + +## Deviations from Plan + +None — plan executed exactly as written. + +## Issues Encountered + +- Branch mismatch: Task 1 committed to `gsd/phase-01-data-foundation`, Task 2 accidentally committed to `upgrade/firebase-functions-v7-nodejs22` due to shell context reset between Bash calls. Resolved by cherry-picking the Task 2 commit onto the correct GSD branch. +- Pre-existing TypeScript error in `backend/src/config/env.ts` (Type 'never' has no call signatures) — unrelated to this plan's changes, deferred per scope boundary rules. + +## User Setup Required + +None — no external service configuration required. The migration file must be run against the Supabase database when ready (will be part of the migration runner invocation in a future phase or manually via Supabase dashboard SQL editor). + +## Next Phase Readiness + +- Both tables and both models are ready. Phase 2 health probe services can import HealthCheckModel and AlertEventModel from `backend/src/models` immediately. +- The findRecentByService method on AlertEventModel is ready for Phase 2 alert deduplication. +- The deleteOlderThan method on both models is ready for Phase 2 scheduler retention enforcement. +- Migration 012 needs to be applied to the Supabase database before any runtime writes will succeed. + +--- +*Phase: 01-data-foundation* +*Completed: 2026-02-24* + +## Self-Check: PASSED + +- FOUND: backend/src/models/migrations/012_create_monitoring_tables.sql +- FOUND: backend/src/models/HealthCheckModel.ts +- FOUND: backend/src/models/AlertEventModel.ts +- FOUND: .planning/phases/01-data-foundation/01-01-SUMMARY.md +- FOUND commit: ad6f452 (Task 1) +- FOUND commit: 4a620b4 (Task 2)