- Add AlertBanner import and render above nav (admin + active alerts only)
- Add AdminMonitoringDashboard import replacing UploadMonitoringDashboard in monitoring tab
- Add activeAlerts state (AlertEvent[]) with isAdmin-gated useEffect fetch
- Add handleAcknowledge with optimistic update and re-fetch on failure
- Remove UploadMonitoringDashboard import (replaced by AdminMonitoringDashboard)
- AlertBanner filters to active service_down/service_degraded alerts only
- AlertBanner renders red banner with AlertTriangle icon and X Acknowledge button
- AdminMonitoringDashboard fetches health+analytics concurrently via Promise.all
- Health panel shows 1x4 grid of service status cards with colored dots
- Analytics panel shows 1x5 stat cards with range selector and Refresh button
- Both components follow existing Tailwind/lucide-react/cn() patterns
- Create 03-02-SUMMARY.md with task outcomes and decisions
- Update STATE.md: advance to Phase 3 Plan 2 complete
- Update ROADMAP.md: mark plan progress for phase 3
- Mark ANLY-02 requirement complete in REQUIREMENTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- admin.ts: four endpoints (GET /health, GET /analytics, GET /alerts, POST /alerts/:id/acknowledge)
- Auth chain: addCorrelationId + verifyFirebaseToken + requireAdminEmail (router-level)
- Health: queries all four service names matching healthProbeService output
- Analytics: validates range format (/^\d+[hd]$/) then delegates to getAnalyticsSummary()
- Alerts: findActive() returns all active alerts; acknowledge returns 404 on not-found
- Response envelope: { success, data, correlationId } matching codebase pattern
- index.ts: mounts admin router at /admin alongside existing routes
- Add import for recordProcessingEvent from analyticsService
- Emit upload_started after markAsProcessing (job processing start)
- Emit completed with duration_ms after markAsCompleted (job success)
- Emit failed with duration_ms and error_message in catch block (job failure)
- All calls are void/fire-and-forget (no await) per PITFALL-6 constraint
- Null-guard on job in catch block prevents runtime errors
- requireAdmin.ts: returns 404 (not 403) for non-admin users per locked decision
- Reads env vars inside function body to avoid Firebase Secrets timing issue
- Fails closed if neither ADMIN_EMAIL nor EMAIL_WEEKLY_RECIPIENT configured
- analyticsService.ts: adds AnalyticsSummary interface and getAnalyticsSummary()
- Uses getPostgresPool() for aggregate SQL (COUNT/AVG not supported by Supabase JS)
- Parameterized interval with $1::interval cast for PostgreSQL compatibility
- Phase 2 plan 4 complete — two scheduled Cloud Function exports added
- SUMMARY.md created with decisions, deviations, and phase readiness notes
- STATE.md updated: phase 2 complete, plan counter at 4/4
- ROADMAP.md updated: phase 2 all 4 plans complete
- Requirements HLTH-03 and INFR-03 marked complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Created 02-01-SUMMARY.md with full execution documentation
- Updated ROADMAP.md with phase 2 plan progress (2 of 4 plans with summaries)
- Marked requirements ANLY-01 and ANLY-03 complete in REQUIREMENTS.md
- Added 02-01 key decisions to STATE.md
- 9 tests covering all 4 probers and orchestrator
- Verifies all probes return 4 ProbeResults with correct service names
- Verifies results persisted via HealthCheckModel.create 4 times
- Verifies one probe failure does not abort other probes
- Verifies LLM probe 429 returns degraded not down
- Verifies Supabase probe uses getPostgresPool (not PostgREST)
- Verifies Firebase Auth distinguishes expected vs unexpected errors
- Verifies latency_ms is a non-negative number
- Verifies HealthCheckModel.create failure is isolated
- Install nodemailer + @types/nodemailer (needed by Plan 03)
- Create healthProbeService.ts with 4 probers: document_ai, llm_api, supabase, firebase_auth
- Each probe makes a real authenticated API call
- Each probe returns structured ProbeResult with status, latency_ms, error_message
- LLM probe uses cheapest model (claude-haiku-4-5) with max_tokens 5
- Supabase probe uses getPostgresPool().query('SELECT 1') not PostgREST
- Firebase Auth probe distinguishes expected vs unexpected errors
- runAllProbes orchestrator uses Promise.allSettled for fault isolation
- Results persisted via HealthCheckModel.create() after each probe
- Add service_health_checks table with status CHECK constraint, JSONB probe_details, checked_at column
- Add alert_events table with alert_type and status CHECK constraints, lifecycle timestamps
- Add created_at indexes on both tables (INFR-01 requirement)
- Add composite indexes for common query patterns
- Enable RLS on both tables (service role bypasses RLS per Supabase pattern)
Node.js 20 is being decommissioned 2026-10-30. This upgrades the runtime
to Node.js 22 (LTS), bumps firebase-functions from v6 to v7, removes the
deprecated functions.config() fallback, and aligns the TS target to ES2022.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>