- Emails now store hash + ciphertext via
lib/security/email.ts.- New Prisma columns:
emailHash,emailEncrypted(seeded + enforced during auth flows). - Account deletion replaces the record with
buildDeletedUserProfile, ensuring PII is destroyed while content stays attributed to a tombstone handle.
- New Prisma columns:
SENSITIVE_DATA_KEYis required at startup; Vitest injects a deterministic value for local runs.- Admins can mark users as
DELETEDin the panel or trigger a full purge.
lib/security/markdown.tssanitizes all UGC (discussions, trails, proposals) and flags spoilers.sanitizePlainInputguards against HTML injection in plain text inputs.- Contest builder enforces Zod constraints on slug, description, and schedule fields.
lib/logger.tsalready redactspassword,token,code,secret. Phase 13 adds email encryption to avoid raw addresses in the DB..env.exampleincludesMETRICS_ACCESS_TOKENso Prometheus scraping can be gated.vitest.setup.tsensuresSENSITIVE_DATA_KEYexists before tests hydrate Prisma.
/api/internal/metricssurfaces Prometheus data (opensolve_trpc_duration_seconds, queue depth, submission counters).- Judge worker + submission router emit counters via
lib/observability/metrics.ts. - CI can gate merges on these signals to ensure regressions are visible.