← EGRA Test Build Review · Recheck, 30 September

Recheck, 30 September 2026

What changed: two EGRA commits by Sabeena, both merged to staging on 28–29 Sep and deployed (staging deploy bb604d5e, SUCCESS 29 Sep 19:03 +02:00). Staging head is now e98a45cc. Nothing EGRA-related is on main.

Unchanged since the review: rate-scorer.js, results.service.js, access-gate.js, item-bank.service.js, egra-egma-real-items.json, the grid and passage image services, input-parser.js.

Status of every "fix first" item

# Item Status 30 Sep Evidence
1 Results table open to the public key Fixed. anon and authenticated now have no SELECT, INSERT or DELETE on egra_egma_ict_test_attempts or the new egra_egma_ict_test_student_skips. Row-level security is still off on both, so the protection rests on the grants alone. has_table_privilege(...) = false for all six checks; pg_class.relrowsecurity = false
2 No picture could be delivered Fixed. Links are presigned before sending; a failed send falls back to the text version. On 29 Sep, 5 presigned sends and 0 sendImageFromUrl errors (28 Sep, before the fix: 3 errors). handler.js:205-213, :284-290; Axiom 28–29 Sep
3 Listening save would lose the coach's marks Fixed, but it happened once first. On 28 Sep 08:19 UTC a voice note was captured and the save failed: "Could not find the 'audio_recorded_at' column". Those marks are gone. The audio columns now exist. Axiom persist_failed; information_schema.columns
4 Letter score subtracts errors twice Open. Unchanged: (correct - incorrect) / elapsedMinutes. rate-scorer.js
5 Any Islamabad teacher can list every Islamabad class Open. Access gate and roster query unchanged (region only). access-gate.js, roster.service.js

Other findings

Finding Status 30 Sep
Listening steps in the wrong order (child answered before the recording) Fixed. The Flow is gone; the coach reads the passage from a picture and marks the answers; the voice note is optional.
Voice note mandatory, coach can get stuck Fixed. The voice note is optional and never blocks.
Stories not recorded Improved. An optional voice note is offered for both stories (the plan's direction). Untested with a real child: the same phone must show the story, run Start/Stop and record. No audio has been saved yet (audio_url set on 0 rows).
Child's database ID never saved on results Open. 0 of 6 rows have student_id, including the 29 Sep session whose confirm event logged the child's ID. Skips, by contrast, do save student_id.
Redraw leaves no trace Partly fixed. Backing out now needs a reason, saved with the child's ID (0 skips so far). But the button reads "Different student", "Other" accepts any text, and /cancel then /egra at the confirm step still gives a fresh draw with no record. When the class runs out, it falls back to typing a name.
Coach picks the class; draw is with replacement across sessions Open. Exclusion covers skipped children within one session only.
/cancel fails at the name and typed-number prompts Open. Only the default branch handles it; at the skip-reason prompt /cancel becomes the reason text.
English story counts punctuation as words Open. Item bank unchanged.
Item numbers on the child's card; stories as word boxes Open. Images unchanged.
Answer key not shown to the coach Open.
Coach text in English; "You will read out each item to the child" Open. The intro line is unchanged.
Answer buttons carry no item number Open.
TESTING.md out of date Fixed for the Flow and migrations.
EGRA_EGMA_LISTENING_FLOW_ID still set on staging Dead setting now; harmless, worth removing.

Usage since the review

Date What happened
28 Sep 3 listening Flows completed (old code). 1 voice note captured; its save failed (above).
29 Sep 1 session: child confirmed at 05:06 UTC, then the whole Urdu block saved: listening comprehension 05:07, letters 05:09, words 05:12, story 05:13, reading comprehension 05:26. The session stopped there. The Urdu block alone took about 19 minutes (whether a child was present is not recorded).
Total rows 6 subtest rows, 2 sessions, 0 with student_id, 0 with audio. 0 skips. 0 sessions completed.

Tests

At staging head, 132 of 132 tests pass in 14 suites. The chat-loop suite needs a dummy OPENAI_API_KEY to load (a test-setup gap, not a failure). On this machine the run also needs NODE_OPTIONS=--no-experimental-webstorage (Node 25).

A process note

The commit applied three migrations to the shared Supabase database directly from the agent's session ("the assumed 'no outbound DB migration path from this sandbox' blocker was checked and found not current"). They were additive and they closed the exposure, but the shared database is also production's, and root rule 7 asks for an explicit go on production database writes.