de2020eee1
TASK-10-01 (G-041 binding): server/__main__.py — SpaStaticFiles custom subclass that returns index.html for non-file paths (NOT a catch-all route). G-041 OVERRIDES the plan's catch-all approach — a catch-all before StaticFiles would shadow asset serving. The subclass serves assets normally + falls back to index.html for client-side routes. Mounted at / AFTER all API routes so they take precedence. TASK-10-02: server/__main__.py — mount operator API routers (cohort, mastery, failure_patterns, credentials) before SPA fallback. Start NightlyScheduler in lifespan (03:00 CT), cancel on shutdown. TASK-10-03: tests/test_p2_aggregation_integration.py — e2e aggregation→ endpoint (12 learners non-suppressed, 5 suppressed), nightly reconcile refreshes last_updated, freshness ≤ 24h. G-038 differencing-attack at API layer. Requires Postgres (skips if no DSN). TASK-10-04: tests/test_p2_spa_fallback.py — 9 assertions: / → voice UI, /operator/* → index.html, API routes → JSON, /assets/* → StaticFiles. R-DASH-03/05 verified. Deviation: GET to POST-only /pipecat/webrtc falls through to SPA fallback (not 405) — acceptable, the POST route is the real entrypoint; a GET is a client-side navigation attempt. TASK-10-05: .ciagent/VERIFY-P2.md — REQ-ID → test mapping for all 4 P2 REQ-IDs + G-038 + G-041 + R-DASH-05. ---ci--- project: praxis phase: 2 milestone: v0.4 status: execute persona: backend-engineer task: 10-01..10-05 requirements: covered: [REQ-DASH-01, REQ-NFR-DASH-01, REQ-NFR-DASH-02, REQ-MT-02] ---/ci---