c396ded395
TASK-07-01: server/cohort/aggregator.py — aggregate_session with k-anon write-time suppression (D-034, K_ANON_THRESHOLD=10), idempotent upsert, 7-day rolling window, multiple metrics (sessions_count, active_learners, gate_open_rate, median_mastery_score, rubric_criterion_means, failure_mode_frequency, branch distribution). No PII in aggregates (D-031). TASK-07-02: server/cohort/hook.py — on_session_end fire-and-forget (D-054), no-op when no Postgres, failures log + nightly reconciles. TASK-07-03: server/cohort/nightly.py — NightlyScheduler in-process asyncio loop, 03:00 CT (America/Winnipeg approx), reconcile from mastery_gate_events, R-DASH-04 failure handling. TASK-07-04: session_recorder.py — chain aggregation hook after mastery flow via asyncio.create_task (parallel, off voice path, D-054). TASK-07-05: tests/test_cohort_aggregation.py — k-anon threshold (9/10/11), idempotent, 7-day window, metrics, no PII. TASK-07-06: tests/test_cohort_nightly.py — scheduler timing, reconciliation, hook-failure+nightly recovery, R-DASH-04. G-038 (binding): differencing-attack test — 10 learners window A, 9 in B, verify dropped learner cannot be isolated (B suppressed, value=NULL). ---ci--- project: praxis phase: 2 milestone: v0.4 status: execute persona: backend-engineer task: 07-01..07-06 requirements: covered: [REQ-MT-02, REQ-NFR-DASH-02, REQ-NFR-DASH-01] ---/ci---