diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 272b212..54eb016 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -9,7 +9,7 @@ Requirements for initial release. Each maps to roadmap phases. ### Service Health -- [ ] **HLTH-01**: Admin can view live health status (healthy/degraded/down) for Document AI, Claude/OpenAI, Supabase, and Firebase Auth +- [x] **HLTH-01**: Admin can view live health status (healthy/degraded/down) for Document AI, Claude/OpenAI, Supabase, and Firebase Auth - [x] **HLTH-02**: Each health probe makes a real authenticated API call, not just config checks - [x] **HLTH-03**: Health probes run on a scheduled interval, separate from document processing - [x] **HLTH-04**: Health probe results persist to Supabase (survive cold starts) @@ -30,7 +30,7 @@ Requirements for initial release. Each maps to roadmap phases. ### Infrastructure - [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 +- [x] **INFR-02**: Admin API routes protected by Firebase Auth with admin email check - [x] **INFR-03**: 30-day rolling data retention cleanup runs on schedule - [x] **INFR-04**: Analytics writes use existing Supabase connection, no new database infrastructure @@ -86,8 +86,8 @@ Which phases cover which requirements. Updated during roadmap creation. | ANLY-01 | Phase 2 | Complete | | ANLY-03 | Phase 2 | Complete | | INFR-03 | Phase 2 | Complete | -| INFR-02 | Phase 3 | Pending | -| HLTH-01 | Phase 3 | Pending | +| INFR-02 | Phase 3 | Complete | +| HLTH-01 | Phase 3 | Complete | | ANLY-02 | Phase 3 | Complete | | ALRT-03 | Phase 4 | Pending | diff --git a/.planning/STATE.md b/.planning/STATE.md index 5cb28a4..9828bc0 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -84,5 +84,5 @@ None yet. ## Session Continuity Last session: 2026-02-24 -Stopped at: Completed 03-02-PLAN.md — analytics instrumentation in processJob() at upload_started, completed, and failed lifecycle points. +Stopped at: Completed 03-01-PLAN.md — admin API endpoints (GET /health, GET /analytics, GET /alerts, POST /alerts/:id/acknowledge) with requireAdminEmail middleware. Resume file: None diff --git a/.planning/phases/03-api-layer/03-01-SUMMARY.md b/.planning/phases/03-api-layer/03-01-SUMMARY.md new file mode 100644 index 0000000..58136ba --- /dev/null +++ b/.planning/phases/03-api-layer/03-01-SUMMARY.md @@ -0,0 +1,115 @@ +--- +phase: 03-api-layer +plan: 01 +subsystem: api +tags: [express, firebase-auth, typescript, postgres, admin-routes] + +# Dependency graph +requires: + - phase: 02-backend-services + provides: HealthCheckModel.findLatestByService, AlertEventModel.findActive/acknowledge, document_processing_events table + - phase: 01-data-foundation + provides: service_health_checks, alert_events, document_processing_events tables and models +provides: + - requireAdminEmail middleware (404 for non-admin, next() for admin) + - getAnalyticsSummary() aggregate query function with configurable time range + - GET /admin/health — latest health check for all four monitored services + - GET /admin/analytics — processing summary with uploads/success/failure/avg-time + - GET /admin/alerts — active alert events + - POST /admin/alerts/:id/acknowledge — mark alert acknowledged +affects: + - 04-frontend: consumes all four admin endpoints for admin dashboard + +# Tech tracking +tech-stack: + added: [] + patterns: + - Admin routes use router-level middleware chain (addCorrelationId + verifyFirebaseToken + requireAdminEmail) + - requireAdminEmail reads env vars inside function body (Firebase Secrets timing) + - Fail-closed pattern: if no admin email configured, deny all with logged warning + - getPostgresPool() for aggregate SQL (Supabase JS client does not support COUNT/AVG) + - PostgreSQL parameterized interval with $1::interval explicit cast + - Response envelope { success, data, correlationId } on all admin endpoints + +key-files: + created: + - backend/src/middleware/requireAdmin.ts + - backend/src/routes/admin.ts + modified: + - backend/src/services/analyticsService.ts + - backend/src/index.ts + +key-decisions: + - "requireAdminEmail returns 404 (not 403) for non-admin users — does not reveal admin routes exist" + - "Env vars read inside function body, not module level — Firebase Secrets not available at module load time" + - "getPostgresPool() used for aggregate SQL (COUNT/AVG) — Supabase JS client does not support these operations" + - "Service names in health endpoint hardcoded to match healthProbeService: document_ai, llm_api, supabase, firebase_auth" + +patterns-established: + - "Admin auth chain: addCorrelationId → verifyFirebaseToken → requireAdminEmail (router-level, applied once)" + - "Admin 404 pattern: non-admin users and unconfigured admin get 404 to obscure admin surface" + +requirements-completed: [INFR-02, HLTH-01, ANLY-02] + +# Metrics +duration: 8min +completed: 2026-02-24 +--- + +# Phase 3 Plan 01: Admin API Endpoints Summary + +**Four Firebase-auth-protected admin endpoints exposing health status, alert management, and processing analytics via requireAdminEmail middleware returning 404 for non-admin** + +## Performance + +- **Duration:** 8 min +- **Started:** 2026-02-24T05:03:00Z +- **Completed:** 2026-02-24T05:11:00Z +- **Tasks:** 2 +- **Files modified:** 4 + +## Accomplishments +- requireAdminEmail middleware correctly fails closed (404) for non-admin users and unconfigured environments +- getAnalyticsSummary() uses getPostgresPool() for aggregate SQL with parameterized interval cast +- All four admin endpoints mounted at /admin with router-level auth chain +- TypeScript compiles without errors across the entire backend + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Create requireAdmin middleware and getAnalyticsSummary function** - `301d0bf` (feat) +2. **Task 2: Create admin routes and mount in Express app** - `4169a37` (feat) + +**Plan metadata:** (docs commit pending) + +## Files Created/Modified +- `backend/src/middleware/requireAdmin.ts` - Admin email check middleware returning 404 for non-admin +- `backend/src/routes/admin.ts` - Admin router with health, analytics, alerts endpoints (4 handlers) +- `backend/src/services/analyticsService.ts` - Added AnalyticsSummary interface and getAnalyticsSummary() +- `backend/src/index.ts` - Added adminRoutes import and app.use('/admin', adminRoutes) mount + +## Decisions Made +- requireAdminEmail returns 404 (not 403) — per locked decision, do not reveal admin routes exist +- Env vars read inside function body, not at module level — Firebase Secrets timing constraint (matches alertService.ts pattern) +- getPostgresPool() for aggregate SQL because Supabase JS client does not support COUNT/AVG filters +- Service names ['document_ai', 'llm_api', 'supabase', 'firebase_auth'] hardcoded to match healthProbeService output exactly + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +None. + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- All four admin endpoints are ready for Phase 4 (frontend) consumption +- Admin dashboard can call GET /admin/health, GET /admin/analytics, GET /admin/alerts, POST /admin/alerts/:id/acknowledge +- No blockers — TypeScript clean, response envelopes consistent with codebase patterns + +--- +*Phase: 03-api-layer* +*Completed: 2026-02-24*