feat(platform): pagination, admission control, cohort roles, version endpoint, structured assessment editor

- Real server-side pagination (platform/api/src/pagination.js) on the audit
  log (was a flat LIMIT 200), student attempt history (was LIMIT 100), and
  assessment session history (was fully unbounded - the one genuine growth
  risk found). Frontend updated (AuditView, AssessmentsView, StudentsView).
- Admission control: resource-policy.yaml's new (disabled-by-default)
  admission_control.max_concurrent_labs, checked before touching Docker at
  all, returns a clean 503 CAPACITY_EXCEEDED instead of failing mid-provision.
  Relayed as a clean student-facing message, never a raw 500. New
  GET /capacity endpoint feeds the instructor dashboard.
- cohort_instructors.role_in_cohort (existed since Phase 2B, never read) is
  now enforced: teacher vs viewer, rbac.js's
  requireCohortWriteAccess/requireStudentCohortWriteAccess.
- GET /api/version - app version, deployed Git SHA, latest migration as a
  schema-version stand-in.
- Structured assessment authoring UI (AssessmentsView.jsx's PoolSpecEditor)
  replaces the raw-JSON pool_spec textarea, backed by a new
  GET /api/admin/scenario-catalog endpoint. Owner-only raw-JSON fallback
  remains for anything the structured editor doesn't cover.
- NEEDS_ATTENTION_FAIL_THRESHOLD/STALE_DAYS moved to env-var configuration.

Full regression suite (e2e-phase2a/2b/3a/3b, 67/67) re-run clean after
these changes; frontend production build confirmed clean.
This commit is contained in:
2026-08-24 20:15:39 -05:00
parent addf474b70
commit a8e543234c
13 changed files with 369 additions and 60 deletions
@@ -20,3 +20,12 @@ health_check:
path: /api/health
timeout_ms: 240000 # k3s cold start in a nested container can be slow — matches the
# ~60-attempt wait already in scripts/entrypoint.sh
# Phase 4.1 §26 — admission control. Disabled (no cap) by default, on purpose: don't fabricate
# a concurrency number before it's measured against the real host (docs/CAPACITY_PLANNING.md).
# Set max_concurrent_labs from infra/scripts/capacity-model.js's real hard-admission-control
# output once the production host exists, so a full host rejects new Labs cleanly (503, "Lab
# capacity is temporarily full") instead of failing mid-provision once Docker itself runs out
# of room.
# admission_control:
# max_concurrent_labs: null