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---