π Sprint Results
71% complete (30 of 42 tasks) β remaining 12 blocked on external credentials
30
Tasks Complete
10
Blocked (Stripe + Email key)
2
Pending (depend on blocked)
10/10
Acceptance Criteria Passing
ποΈ Build Status
| Service | Port | Build | Notes |
|---|---|---|---|
| teacher-portal | 3116 | β PASS | Clean |
| user-center | 3121/3126 | β PASS | Clean |
| learner-bot | 3120 | β PASS | Clean |
| reader | 3125 | β PASS | Clean |
| telemetry | 3110 | β οΈ 7 TS errors | Pre-existing β sessions.ts, gdpr.ts, trpc.ts type annotation issues. Not introduced by sprint work. |
β Acceptance Criteria
All 10 acceptance criteria now pass (was 0/10 before sprint).
| # | Description | Status | Evidence |
|---|---|---|---|
| AC-01 | SSO-only teacher auth β no standalone register/login | PASS | 0 standalone auth routes in teacher-portal. TASK-02 commit fdaa870 |
| AC-02 | Student soft-delete (state=deleted, filtered from all lists) | PASS | ne(students.state, 'deleted') on all list queries. TASK-03 commit e4e4015 |
| AC-03 | Class 33-student hard cap | PASS | CLASS_STUDENT_LIMIT=33 in students.ts, returns 400 class_full. TASK-05 |
| AC-04 | GDPR async purge queue (202 + requestId) | PASS | gdpr_requests table, POST returns 202, GET /status/:id. TASK-06 commit 064ee09 |
| AC-05 | TOTP 2FA setup / verify / disable | PASS | 3 endpoints on /api/auth/2fa/*, AES-256-GCM at rest. TASK-25 commit 19dbc5c |
| AC-06 | Admin impersonation with TOTP gate + audit log | PASS | impersonation_sessions table, TOTP-gated, 3600s TTL. TASK-26 commit 7397956 |
| AC-07 | Audit log table β immutable, GDPR-preserved | PASS | audit_log in schema; GDPR deletion nulls PII only, never deletes audit rows. TASK-01 |
| AC-08 | Parent claims table + link endpoints | PASS | parent_claims + students_parents tables; claim/verify/link endpoints. TASK-21/22 |
| AC-09 | PIN reveal tokens (30-day TTL, single reveal) | PASS | pin_reveal_tokens table, POST /api/v1/students/:id/reveal-pin. TASK-19 |
| AC-10 | Email verification gate on teacher registration | PASS | emailVerificationToken column; GET /api/auth/verify-email?token=. TASK-01 |
π¨ Phase 0 β Emergency Security Fixes
5 critical security violations identified and fixed before sprint began.
| Fix | Violation | Resolution | Commit |
|---|---|---|---|
| FIX-1 | Voice audio persisted in fluency DB β COPPA Β§45.3 | audio_key column dropped; in-memory only; raw audio deleted after Whisper | verified |
| FIX-2 | Reader direct DB pool to learner DB β service boundary violation | Replaced with HTTP calls to learner-bot API (port 3120) | 8e05a09 |
| FIX-3 | GDPR deletion deleted audit_log rows β SOC2/ISO27001 violation | Audit logs now immutable; PII nulled only | d05fda1 |
| FIX-4 | bcrypt cost 10 in teacher-portal (spec requires 12) | Changed to cost 12; PIN auth stays at 10 (acceptable for 4-digit) | d010e90 |
| FIX-5 | No email verification gate β teachers immediately active | Added emailVerified boolean + token column + verify endpoint | b2d41b3 |
π οΈ Sprint Tasks Completed
Sprint 1 β Schema & Auth Foundation
| Task | Description | Commit |
|---|---|---|
| TASK-01 | user-center schema migrations: users.state ENUM, email_verification_tokens, password_reset_tokens, audit_log, login lockout (5 failures β 15min + 429) | c9549cd |
| TASK-02 | Remove standalone teacher auth β SSO mandatory via /api/sso/login | fdaa870 |
| TASK-03 | Student soft-delete: state ENUM active|archived|deleted, deleted_at timestamp, WHERE filters | e4e4015 |
| TASK-04 | Students roster schema expanded: uuid, school_id, language, placement_test_completed, failed_attempts, locked, miles, tokens, parent_count | β |
| TASK-05 | Classes schema + 33-student hard cap: state, archived_at, curriculum_territory, school_id; enforced in addStudent + importRoster | β |
| TASK-06 | GDPR async purge queue: gdpr_requests table with state machine; POST returns 202+requestId; GET status endpoint | β |
| TASK-07 | GDPR soft-purge processor cron: runs 10s after startup then hourly; per-request error handling | 064ee09 |
| TASK-08 | Schools + memberships tables + CRUD endpoints | 6d8b3b1 |
| TASK-09 | Tenant isolation: school_id filters on all student/class queries + propagate on create/import | e47ce2c |
Sprint 2 β Identity, Security & Parent Linking
| Task | Description | Commit |
|---|---|---|
| TASK-19 | pin_reveal_tokens table + PIN reveal endpoints (30-day TTL, teacher-only, single reveal) | 5f0543e |
| TASK-20 | Puppeteer login card PDF generator: per-student printable cards with PIN + QR code | 2a8a1c9 |
| TASK-21 | parentClaims + studentsParents tables added to schema with FK references | bddfbcd |
| TASK-22 | Parent linking endpoints: create claim token (72h TTL), verify, link to parent account | 189a54a |
| TASK-24 | addStudent parity with importRoster: username generation, PIN hash (bcrypt 10), Account Center + learner-bot integration, PIN single-reveal | 5d95ca9 |
| TASK-25 | TOTP 2FA: setup/verify/disable endpoints, AES-256-GCM encryption at rest | 19dbc5c |
| TASK-26 | Admin impersonation: TOTP-gated, impersonation_sessions table, 3600s TTL, audit log, end + list endpoints | 7397956 |
| TASK-27 | Audit log viewer: GET /api/admin/audit-logs with cursor pagination, admin-only | 0226c0d |
| TASK-28 | School management admin endpoints: list, detail, update | 9ccaafb |
Sprint 3 β Service Integration & Telemetry
| Task | Description | Commit |
|---|---|---|
| TASK-29 | Reader: remove local users DB lookup; learnerId sourced from Account Center JWT payload | b487ad5 |
| TASK-30 | placement_test_completed flag + bot endpoint (POST /api/v1/bot/:id/placement-complete) with X-Internal-Key auth + audit log | d3905a3 |
| TASK-31 | Miles + tokens surfaced in class roster; POST /api/v1/bot/:learner_id/sync-progress bot sync endpoint | e77d794 |
| TASK-32 | Telemetry β learner-bot session trigger: session_ended events fire async POST to learner-bot | 9e66256 / aea063d |
Sprint 4 β Infrastructure & Standards
| Task | Description | Commit |
|---|---|---|
| TASK-33 | New telemetry tables: device_sessions, telemetry_errors, cron_runs in both telemetry + learner-bot | e06e447 / eb6cb0d |
| TASK-36 | Shared lib/errors.ts error helper across all 5 services β standardises {error, message, details} response shape | 5 commits |
| TASK-37 | Staging infrastructure: docker-compose.staging.yml + .env.staging + GitHub Actions CI (.github/workflows/ci.yml) for all 5 services | 5 commits |
π« Blocked Tasks (12)
All remaining tasks are blocked on two external credentials. No code blockers remain.
Blocker 1: SendGrid / Resend.io API Key
Blocks: TASK-10 (SendGrid setup), TASK-11 (forgot-password email), TASK-23 (teacher notification emails), TASK-35 (weekly parent digest cron)
Needed for: email verification activation, 21 notification types, password reset flow
Blocks: TASK-10 (SendGrid setup), TASK-11 (forgot-password email), TASK-23 (teacher notification emails), TASK-35 (weekly parent digest cron)
Needed for: email verification activation, 21 notification types, password reset flow
Blocker 2: Stripe Test-Mode Credentials
Blocks: TASK-12 (subscriptions table), TASK-13 (checkEntitlement lib), TASK-14 (webhook handler), TASK-15 (billing endpoints), TASK-16 (entitlement gating on all services), TASK-17 (trial logic), TASK-18 (Stripe portal link)
Needed for: entire billing system, entitlement checks, subscription lifecycle
Blocks: TASK-12 (subscriptions table), TASK-13 (checkEntitlement lib), TASK-14 (webhook handler), TASK-15 (billing endpoints), TASK-16 (entitlement gating on all services), TASK-17 (trial logic), TASK-18 (Stripe portal link)
Needed for: entire billing system, entitlement checks, subscription lifecycle
π New Gap Identified
Self-service forgot-password flow β not in original repair plan.
Current state: admin can reset any user's password via
Missing:
Status: BLOCKED β depends on SendGrid/Resend key (same as TASK-11). Will be added to TASK-11 scope when key arrives.
Current state: admin can reset any user's password via
POST /api/admin/users/:uuid/reset-password, but users cannot reset their own password via email link.Missing:
POST /api/auth/forgot-password β generate token β send email | POST /api/auth/reset-password β validate token β update passwordStatus: BLOCKED β depends on SendGrid/Resend key (same as TASK-11). Will be added to TASK-11 scope when key arrives.
π Security Invariants (Non-Negotiable)
| Rule | Status |
|---|---|
| Audit logs immutable β never deleted, even on GDPR request (PII nulled only) | β Enforced |
| Voice audio_key in-memory only β raw audio deleted after Whisper (COPPA Β§45.3) | β Enforced |
| Reader uses HTTP to learner-bot (port 3120) β no direct DB access across service boundaries | β Enforced |
| bcrypt cost 12 for teacher passwords; cost 10 acceptable for 4-digit student PINs only | β Enforced |
| School isolation: school_id filters on all student/class queries | β Enforced |
| TOTP encryption: AES-256-GCM with per-record IV + auth tag (TOTP_ENCRYPTION_KEY env var) | β Enforced |
| Email verification gated by REQUIRE_EMAIL_VERIFICATION env var (activates when SendGrid configured) | β Enforced |
π Next Steps
- To unblock 10 tasks: Provide SendGrid or Resend.io API key + Stripe test-mode secret key + webhook secret to @SigDug
- Fix telemetry build: 7 pre-existing TS errors in
sessions.ts,gdpr.ts,trpc.tsneed type annotations - Git safety: All commits on master, no remote origin β risk of total loss on server failure. Set up a GitHub remote ASAP.