The committed v1.1 worklist. Direction + deferred (v2) work → ROADMAP.md.
Maintenance rules → AGENTS.md. This is the only active worklist —
nothing is tracked elsewhere.
Status: ☐ todo · ◑ in progress · ✅ done. When an item ships it moves to
ROADMAP.md → Done. IDs are stable handles for dispatching work.
- ✅ REC-1 — Chunk-upload CORS regression. The v1.1 size-cap header
(
x-goog-content-length-range) wasn't in the evidence-bucket CORS allowlist, so the browser preflight blocked every screen-recording upload. *Fixed: live bucketbackend/gcs-cors.json. Branchfix/recording-drain-gate(765fb9e).*
- ✅ REC-2 — "Safe to exit" shown with the recording unsaved. The drain gate
could abort on a status flip and falsely report complete; status-driven paths set
"ended" with no buffer check; completion copy was hardcoded. Fixed + 13 tests;
completion can only say "safe to exit" when the buffer is provably empty, else a
loud "recording NOT saved — keep tab open, contact invigilator" + Retry. Branch
fix/recording-drain-gate(6dfa081). - ✅ REC-3 — Escape → instant-lock race. Fixed (
496af04): humane recovery FLOOR on the blocking deadline (max(reentrySeconds, 15s)) so an accidental fullscreen exit always leaves time to re-enter before the lock; default (20s) unchanged, exit-limit ladder unchanged (no enforcement hole, invariant-tested). Note: JS can't veto the native Esc → fix is the recovery window + "paused, not locked" UX, not a keypress veto. (triage B3) Needs a maintainer browser test. - ✅ REC-4 — Admin "chunks uploaded" count is wrong. Fixed (
6741a58): admin session-detail now reports the ground-truth stored count from a paginated GCS prefix listing (countStoredChunks), not the over-counting mint counter; mint counter kept verbatim for the picker filter + hwm. Specdocs/proposed/admin-upload-telemetry.md. Backend 1041/1041; card headline reads stored count. (B6) Card needs the maintainer's morning browser confirm post-deploy. - ✅ REC-5 — Surface pending-upload count in admin session details. Fixed
(
6741a58):pending_upload_count= client-reportedbuffer_pending_chunks(NOT mints − stored, which is retry inflation — caught + regression-guarded), plus raw backlog fields +last_heartbeat_at; warning banner + Pending metric when >0 on an active session. sessionDetail vitest 40/40. (F1) - ✅ TEST-1 — Why tests/health missed REC-1 + a regression guard. Diagnosed
(
docs/proposed/recording-upload-healthcheck.md); addedbackend/test/corsHeaderParity.test.mjs— asserts every header the backend signs on the chunk PUT is ingcs-cors.json's CORS allowlist (fails loudly on drift). Verified: 1007/1007 backend tests pass; regression-proven. (R1; Layer B live preflight probe = optional follow-up.)
- ✅ CAM-1 — Auto-collapse the camera widget when the camera is unavailable.
Fixed (
8feeeb9): edge-triggeredshouldAutoCollapseCameraDock(prev,next)→ collapses to the minimal pill on the transition into "unavailable", respects a manual re-expand, available-state behaviour unchanged. CameraDock 3/3. (B5) (Note: current dock has no blue panel — expanded = self-view tile, collapsed = pill; auto-collapse drops the dead tile to the pill.) - ✅ STUB-1 — PATCHED (live dev bank, 2026-06-24 night-run). All 3 approved
offenders 0626-8, 0626-9, challenge-7 written back via the admin API (HTTP 200,
round-trip-verified all 4 langs;
status/hiddenTestspreserved → no live-edit 409). 0626-8 default now prints0, 0626-9 + challenge-7 printNO; java fixed toclass Main. challenge-7 cpp corrected toreturn "NO"(string), NOT the spec's0(expected output is YES/NO). Same-root-cause but DEFERRED (need the maintainer's OK): challenge-3,4,5,6,8,9 JS-undefinedlegs (1-liner each) + challenge-1/2 fully broken (unadapted OUTPUT_PATH, not in active use) — see night-run log. Original approved scope below. - ☑ STUB-1 — APPROVED 2026-06-23 (night-run). Offenders confirmed
(
docs/proposed/stub-return-none-audit.md): 0626-8, 0626-9, challenge-7 (the Python stub crashes withIndentationError; JSsolve(){}→undefined; JS-undefinedspans challenge-3..9). Problem stubs are not version-controlled — they live only in the live problem bank (datastore, written via the admin API) — so the fix is a write-back to the live dev bank: read each offender, fix the stub bodies, rebuild 0626-8/9 scaffold from their live input signature, verify by replaying vs sample tests. Prevention (committed problem-seed source) → ROADMAP v2. (B4)
- ✅ FLOW-1 — Fixed (
496af04): clean re-share on manual screen-share stop (intentional expected fullscreen-exit viashell.markExpectedExit+ workspace hidden → re-share back through the gate, no reload / no re-prompt for granted camera+mic); recovery overlay "paused, not locked" + auto-focused Enter-fullscreen; best-effort Escape guard (preserves ALERT-1 dispute-note cancel); in-UI "Refresh data" (re-pull config/timer without reload); F5/Ctrl+R + beforeunload kept. Permission-drop audit: no rogue drop (streams persist today). DEFERRED (need the maintainer): record-THROUGH-lock (backend upload-auth policy) — recorder still stops on lock. Needs a maintainer browser test. Original requirements below: - ☑ FLOW-1 — Permission persistence + clean re-share + fullscreen gating
(merges U1 + U6 + U4). One coherent flow:
- Once screen + camera + permissions are granted at the start, never re-ask for the whole session — keep them running.
- Block whole-window refresh (warn on navigate-away: "everything must be redone — don't, unless required"); provide an in-UI data refresh instead; never reload the whole site → never give up screen/camera.
- Audit every place we drop permissions on our own and stop doing it.
- If the candidate manually stops sharing (out of our control): clean flow — drop out of fullscreen, hide the questions, ask to re-share, then back to fullscreen. No confusion at any step.
- Confirm-on-Escape guard so fullscreen isn't exited by mistake.
- Why recording stops on lock is part of this — keep recording while locked where possible; gate questions behind fullscreen.
- ✅ COPY-1 — Consolidated user-facing copy pass. Done (
af4761b): D5 tone (clear/calm/respectful/non-accusatory/action-first). Rules page reworded + red-bar mention; "I have fixed this" → "I have fixed it — continue my test"; removed "No code is needed"; integrity notices de-alarmed to factual "recorded for the proctor to review" (stakes kept); share-stop message aligned to FLOW-1. Copy-only; tests updated; 1071/1071. Final strings logged in the night-run log. Maintainer: sanity- check the softened "violation" tone in the morning if you want more deterrence.
- ✅ ALERT-1 — Candidate alert feedback + per-user alert suppression. Fixed
(
1e63f98): two-button alert response (acknowledge / "Report a problem with this alert" — genuine-fault-only, never unlocks recovery); dispute → server-deriveddispute_raisedflag alert (POST /api/session/dispute-alert, candidate-token auth); admin suppression as a COMMON feature — per-(user,test,type) shared list, guard at the singleupsertProctorAlertchokepoint, contest-scoped, hot-path cached. Backend 1062/1062; 41 frontend tests. (F4) Dispute-button + admin Suppress click need the maintainer's morning browser confirm (no jsdom).- On an alert, a second button: "I understand / won't repeat" vs "This alert is not correct / unfair / a bug." Copy must make the complain option unambiguously for genuine software-mistake / unfair cases only (not for everyone).
- Clicking complain raises a new alert alongside (flagging the dispute) → the admin alerts dashboard.
- Admin can suppress that alert for that user, for that test. Make suppression a common feature (like the existing fullscreen suppression): suppressed alerts go to a shared list and are suppressed thereafter. Also feeds platform-improvement.
- ✅ ALERT-2 — Per-alert screenshot — capture the last frame (incl. when
recording has stopped); jump-to-chunk already exists. Fixed (
247341b): rolling last-good-frame cache in the recorder (works post-ended), distinctscreenshot/GCS prefix (no REC-4 collision, neither chunk counter bumped), cross-session key guard (fail-closed), signedscreenshot_url, admin thumbnail. Backend 1054/1054; frameCapture 13. (F6) Candidate-capture path + admin thumbnail need the maintainer's morning browser confirm (no jsdom to unit-test the recorder wiring). - ✅ EVID-1 — Filter notable paste/keystroke events and surface them as
clickable timeline markers in the recording Evidence tab. Fixed (
11e2965): new adminGET /api/admin/session-editor-events(requireAdmin, text blobs excluded, 8000-cap) + purenotableEditorMarkers.tsclassifier (large_paste/paste/ keystroke_burst, thresholds mirrored from eval REPLAY) → amber marker lane in the Evidence tab, click-to-seek via existing primitive. Backend 1046/1046; markers 19 tests. (F10) Needs the maintainer's morning browser confirm (Evidence tab) post-deploy. - ◑ BANK-1 — Bulk export/import of problems + templates: multi-select, select a
template → all its questions; upload them back; handle dedup + cross-instance
versioning. Spec:
docs/proposed/bulk-problem-template-io.md. (F11 — the originally-dropped request.) Backend done + committed (8b21ea7) — 3 admin endpoints, content-hash dedup, preview/commit, fork-to--2, 23 tests. Admin UI DONE (3838e97) — multi-select export + upload→preview→commit dialog wired to the live contracts, per-row disposition + override, Apply-gated on dangling refs; 26 new tests. Needs the maintainer's morning browser confirm (export/import) post-deploy. - ✅ EVAL-1 — Data-driven eval rule registry: one function per rule so a rule
can be added/removed/retuned without a code change. Done (Wave 2a,
034491d): 12 detectors → registry, thresholds → config data, behaviour-preserving (EVALUATOR_VERSION4); golden-parity 45/45 byte-identical vs pre-refactor + full suite 1035/1035. Phase 2 cross-candidate analysis → v2. Spec:docs/proposed/eval-rule-registry.md. (F14.)
- ✅ PROC-1 — Unified
ROADMAP.md+BACKLOG.md+AGENTS.md/CLAUDE.md+docs/proposed/convention + redundant tracking docs removed. (M1 + M3)
- F13 vs F14: EVAL-1 = the F14 rule-registry refactor → v1.1 (in build).
F13 (automatic AI verification after every test, task #144) → v2 (see
ROADMAP.md). They are disjoint: F14 is the behaviour-preserving eval-math refactor; F13 is a new auto-run + LLM-judgment capability. - M2 — candidate-visible leaderboard: → v2 (
ROADMAP.md).
Filed walking the deployed candidate + admin flows. All v1.1 active. Captured before any build (THE ROADMAP RULE / capture-on-ask) so none can slip again.
- ◑ LT-1 (BUG, code-complete
c3a0939— needs browser test) — Coding screen renders while NOT in fullscreen. After a manual stop-share → re-share, the workspace/questions showed without fullscreen. Hard rule: code must NEVER render unless in fullscreen AND recording. (FLOW-1/T7 — the gate overlay exists but doesn't gate the re-share render path.) - ◑ LT-2 (code-complete
6dd8507+c3a0939— needs browser test) — No-countdown re-entry state. When a candidate returns from an exception (re-share, post-lock, came-from-another-state), show a fullscreen block "enter full screen to continue" with no countdown — don't count them into a surprise lock. Countdown applies only to a genuine mid-exam fullscreen exit. - ◑ LT-3 (BUG, code-complete
6dd8507— needs browser test) — Fullscreen-exit lock timer doesn't RESET on re-entry. First exit recovers fine; re-enter fullscreen; the next exit locks immediately — the deadline isn't reset when fullscreen is regained (enforcement.ts: phase not returning to idle / deadline reused across episodes). - ◑ LT-4 (code-complete
54ea1ef— 15-min bounded record-through-lock; needs browser test) — Don't give up screen-share when LOCKED. Preferred: keep recording AND uploading while locked (we most want to see what they do during a lock); at minimum keep the screen-share stream alive (upload may pause). Today the recorder stops on lock. This was yesterday's T7 ask — investigate why it was missed. (FLOW-1 record-through-lock, was deferred → pull in.) - ◑ LT-5 (code-complete: spec
2abb31a+ B-I/II/III — needs browser test) — Re-express the WHOLE lock/fullscreen/recording flow as a simple, explicit state machine: enumerate the few screen states + the exact transition conditions. Umbrella over LT-1..LT-4. Design-first.
- ◑ LT-6 (code-complete
87457c5— needs browser test) — Optional comments field on the in-session red-bar alert (the dispute "report a problem" panel has one; the red-bar alert doesn't). (ALERT-1.) - ◑ LT-11 (BUG, HIGH, code-complete
a51f71a— needs browser test) — Dispute + suppression broken in the live build: (a) dispute recordswindow_blurwhile the alert shown istab_away— alert_type mismatch; (b) Suppress in the dispute → error "a valid alert type, user name and candidate ID are required"; (c) suppressed alerts still fire (red bar + new alerts keep generating). ALERT-1 passes unit tests but does NOT work end-to-end — REOPEN ALERT-1, fix the live integration, browser-verify. - ◑ LT-12 (code-complete
719bbca— deep-link floor shipped; needs browser test) — Alert → jump to the relevant chunk/playback at that exact timestamp. Ideal: a playback popup that jumps to the chunk with front/back scrubbing; acceptable fallback: deep-link to the evidence screen at that timestamp. Long-standing ask (≈ old #61; ALERT-2 claims "jump-to-chunk exists" — verify, it isn't usable from an alert today).
- ◑ LT-7 (BUG, code-complete
46ad737— needs browser test) — Chunk-count mismatch in LIST views. A session shows 587 chunks in the session list AND the evidence/recordings (find-students) list, but the detail view shows 0. REC-4 fixed the DETAIL view to the GCS ground-truth count; the list views still read the stale mint count — point them at the same source. - ◑ LT-8 (BUG, code-complete
f85b897— needs browser test) — Recording review opens the wrong session. Clicking a student in the left list always loads that student's LATEST session, not the specific row clicked (multiple attempts per student). The session dropdown above the player works; the left-list click must target the clicked session.
- ◑ LT-9 (code-complete
3e8eb17— needs browser eyeball) — Select-all / deselect-all checkbox atop the bulk-select lists for BOTH problems and templates. (BANK-1.) - ◑ LT-10 (code-complete
2452fea— core pages done; ResultsPanel/Settings/AttendancePanel/SystemHealthPanel/BankImportDialog still inline + tracked; needs browser eyeball) — Floating toast for error/success messages. Today they render at the top of the list and scroll out of view (e.g. "Problem referenced" on a blocked delete; long contest page). Replace with a pinned/floating toast that stays in view; rework the notification element platform-wide.
- ◑ LT-13 (BUG, code-complete
759062e— needs deploy+browser) — "Run evaluation" on the result page errorsVITE_API_BASE_URL is not configured.The run-evaluation call's API base URL isn't baked into the deployed frontend build (cf. the build-config bake guard — everyVITE_*the prod build needs must be baked, else the call has no base). Find whichVITE_*var the run-evaluation path reads and make the deploy bake it (extend the bake guard). - ◑ LT-14 (BUG, code-complete
759062e— needs deploy+browser; deploy MUST export EVAL_API_URL=) — The contest Evaluation tab loads the CANDIDATE screen in its iframe instead of the eval UI. Eval was split into a separateproctor-evalservice serving/eval-ui; the tab's iframesrc/ eval-UI base URL is wrong or unset, so it falls back to the candidate origin. Point the iframe atproctor-eval/eval-uiand bake its base URL (likely same root cause as LT-13).
28-agent sweep of the morning triage (T1–T11, S1–S6) vs the consolidated docs +
live code, after T1 (page-merge) was found silently dropped in the e4ca2b9
from-scratch BACKLOG rewrite. Only the gaps are listed; everything else is BUILT or
legitimately DEFERRED in ROADMAP.md (F2/F3/M2/R2/R3).
- ◑ T1 (page-merge half) (code-complete
c3a0939— the originally-dropped item, now BUILT; needs browser test) — Merge browser-check + permission setup into ONE onboarding screen; acquire screen-share/cam/mic ONCE and carry the live streams forward instead of stop-and-re-ask (today BrowserPreflightGate grabs then stops the screen stream, PermissionsGate re-prompts). Copy-half fixed (3f26a58). Overlaps LT-1/LT-2/LT-5. - ◑ T3 (code-complete
87457c5) — Unlock-button still presumes fault ("I have fixed it"). Reword to a no-fault action ("Return to exam"/"Dismiss"); accidental triggers shouldn't admit fault. ~1-line inAnomalyPanel.tsx+ test. - ◑ T7 (code-complete
c3a0939— REVISED: no pre-gate; fullscreen enforced post-unlock via the render-gate, avoiding the BL-3 deadlock; needs browser test) — Gate the unlock-code panel behind being in fullscreen (today it renders on lock with no fullscreen precondition). Record-through-lock = LT-4. - ✅ T10 (
bf8226c) — Admin + invigilator copy never audited (COPY-1 swept candidate-side only). Sweep those strings. - ✅ S5 (
0de7d3f) — The anti-slip auditor is manual-only (AGENTS.md"reconcile at every release cut"). Build the scripted reconcile gate: diff prior IDs vs the docs, fail on any ID with no terminal disposition (BUILT/DEFERRED/DROPPED/FOLDED). This is what would have caught T1. - ✅ STUB-1 — RESOLVED (Phase C; diffs in
local-notes/stub-diffs-2026-06-24.md). All deferred offenders fixed in the live dev bank (challenge-1/2 all langs + challenge-3..9 JS leg), 0 active sessions, status/hiddenTests preserved, re-GET+replay verified. Earlier overclaim: only 3 of 11 known-bad stubs were patched (live bank, repo-unverifiable self-report); challenge-1/2 + JS-undefined legs of challenge-3..9 are DEFERRED. Verify via a live GET of the 3 offenders; decide the 8. - → T9 →
ROADMAP.mdv2 (DSL-driven stub generation — silently dropped, never built).
- ◑ ALERT-1 — dispute/suppress fix landed server-side (
a51f71a, LT-11); needs browser test. - ◑ FLOW-1 / T7 — fullscreen render-gate + recovery + record-through-lock all
built (B-I/II/III, through
54ea1ef); needs browser test (LT-1..LT-5, LT-4). - ◑ REC-4 — list-view chunk counts fixed to GCS ground-truth (
46ad737, LT-7); needs browser test.
A live-test candidate's two foreign_paste flags were re-reviewed; both were FALSE
POSITIVES (his own typed-then-copied code). Root-caused (proven via the real
replaySession on his ~8.4k-event stream): the eval's document reconstruction
garbled on real edit streams, so isForeign couldn't find self/on-page content. Full
design + proof: docs/proposed/paste-detection-and-activity-log.md.
(Real candidate/session/contest IDs: gitignored local-notes/paste-detector-regression-data.md.)
- ◑ EVAL-2 — Foreign-paste detector fixed (code complete + triple-reviewed + tests
green; pending staged deploy + re-eval). Replaced the flat-offset doc model with a
faithful line/column model (
31d8101); threaded the problem statement + sample I/O (raw + markdown-stripped) intoisForeign's on-page sources + gatedforeign_pasteonglitchFree→unverified_pastes(8d52031); DoS-hardened the growth; sanitized real-slice + genuine-external regression fixtures. Re-run eval on affected contests to clear historical FPs (scope = owner decision). Ships with backend deploy. - ◑ LOG-1 — Activity-log info chip built (code complete + tests green; pending
frontend deploy + browser verify). Neutral signals (normal paste marker, focus/blur,
fullscreen, clipboard, cursor bursts) classified as info, hidden by default behind a
"Show info activities" chip defaulting OFF; alerts/foreign-paste/submissions always
shown; raw telemetry unchanged (
fab50e3).
- ✅ uuid GHSA-w5hq-g745-h8pq (moderate) — bumped uuid 8.3.2 / 9.0.1 → 11.1.1 via
override (advisory range is
< 11.1.1; our usage isv4()-only so it was never exploitable, but this clears the alert at source). Override added to both the root (9eff2cc, fixes the repo lockfile Dependabot scans) andbackend/package.json(c0cf055) — the backend image is built standalone frombackend/, so the root override alone never reached the running container.npm audit→ 0; 1108 tests green; deployed + verified on proctor-api (uuid 11 in image, health-check green 9/9) and proctor-eval (uuid 11 in image, Firestore read 200). - esbuild GHSA-gv7w-rqvm-qjhr (high) — withdrawn advisory (GitHub retracted it 2026-06-17) on a build-time-only tool not shipped in the prod nginx image. No code fix; dismiss on the Dependabot UI (or it auto-clears).
- ✅ HARDEN-1 — reproducible backend image build. Committed
backend/package-lock.json(standalone, pins uuid 11.1.1) + both Dockerfiles nowCOPY package.json package-lock.jsonnpm ci --omit=dev(deterministic, fails closed on drift), so the deployed artifact exactly matches an audited tree and carriesbackend/package.json's overrides. Guard test (backendLockfileReproducible.test.mjs) floors uuid + asserts the npm-ci property; CI runs a standalonenpm cito catch lock/manifest drift pre-merge;backend/.dockerignoreadded; regen procedure documented indocs/DEPLOY.md. Reviewed (4-lens adversarial). Deployed + verified on proctor-api + proctor-eval.