Uh oh!
There was an error while loading. Please reload this page.
feat(quiz): server-authoritative grading — per-question answers, quiz_responses, answer-key deprecation (#541) - #549
Conversation
…_responses, answer-key deprecation (#541) Workstream C of the pre-revamp quiz repair batch (epic #537): C1 — POST /api/quiz/attempts/{attempt_id}/answer grades one question server-side: owner check, 409 after completion, 400 QUIZ_QUESTION_INVALID on out-of-range indexes, malformed items never grade correct (#129 rule). Idempotent on (attempt_id, question_index): re-answering returns the FIRST recorded response with recorded:false — no revision, decided and documented for the #537 flow. Returns is_correct/correct_index/ explanation plus the next question stripped of the answer key. C2 — quiz_responses table (migration 20260812214402): plaintext analytics scalars only (indexes, boolean, time_ms, confidence), UNIQUE (attempt_id, question_index) as the idempotency contract, FK cascade with the attempt. Real-DB integration tests pin the UNIQUE arbitration and the cascade. C3 — include_answer_key on generate, default true so the current QuizPanel keeps working; every keyed response logs a deprecation breadcrumb; false strips per-option correct booleans from the response while storage keeps them for grading. Removal tracked in #546; deleting the key is a hard requirement of #537. C4 — submit prefers recorded quiz_responses per question (a contradicting payload answer is ignored — answer-time grades are the source of truth) and falls back to the payload for questions never answered through C1. The atomic completed_at claim (PR #464) and 409 behaviour are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Deploying with |
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs | frontend-staging | c2d316e | Commit Preview URL Branch Preview URL | Aug 13 2026, 02:09 AM |
Warning Review limit reached
Next review available in:57 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…e index/id mismatch, drop the redundant index Review findings (xhigh, 4 confirmed; 5 reports collapsed to one root cause): - submit now persists the RECONCILED answer set (recorded responses winning over payload) instead of the raw request body. A recorded-only submit previously stored a full score beside answers_json=[], and a contradicted payload answer was stored despite losing to the recorded response — the attempt record disagreed with its own score. - The answer endpoint accepts an optional question_id and rejects a mismatch with question_index, plus echoes both in the response: passing the 1-based wire id as the 0-based index used to silently grade the neighbouring question, which idempotency then locked in. - quiz_responses drops the standalone attempt_id index — the UNIQUE's btree already leads with attempt_id, so it only added a write to the per-answer hot path. - correct_index is resolved once per request instead of re-scanning the options on every _graded call (it never depended on the answer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Closes#541. Workstream C of the pre-revamp quiz repair batch (epic #537). Today the answer key ships to the browser and review grades locally; every interesting version of the #537 flow (in-session adaptivity, confidence, timing, honest results) needs the server to be the one that knows. This PR gives the new client its grading API while the current client keeps working unchanged.
What
C1 —
POST /api/quiz/attempts/{attempt_id}/answer.{question_index, selected_index, time_ms?, confidence?}→{is_correct, correct_index, explanation, next_question, recorded}. Owner check; 409QUIZ_ATTEMPT_ALREADY_COMPLETEDafter completion; 400QUIZ_QUESTION_INVALIDon out-of-range indexes; malformed items (no correct option) never grade correct (#129 rule);next_questioncomes back stripped of the answer key. Idempotent on(attempt_id, question_index): re-answering returns the FIRST recorded response withrecorded: false— no revision, decided and documented; a lost insert race re-reads whatever the UNIQUE let win.C2 —
quiz_responses(migration20260812214402):attempt_id(FK cascade),question_index,selected_index,is_correct,time_ms,confidence,answered_at. All plaintext analytics scalars (#521 rationale — no free text; question/option text stays encrypted inquestions_json).UNIQUE (attempt_id, question_index)is the idempotency contract; real-DB integration tests pin the UNIQUE arbitration and the cascade.C3 —
include_answer_keyon generate, defaulttrueso the currentQuizPanelkeeps working. Every keyed response logs a deprecation breadcrumb;falsestrips per-optioncorrectbooleans from the response while storage keeps them for server-side grading. Flip + delete tracked in #546; removal is a hard requirement of #537.C4 — submit reconciles. Responses recorded through C1 are the source of truth per question (a contradicting payload answer is ignored — letting the final POST override answer-time grades would reopen the client-grading hole); questions never answered through C1 fall back to the payload, so the current all-at-the-end client is unaffected. The atomic
completed_atclaim (PR #464) and 409 behaviour are untouched. Mixed-case and recorded-only tests included.Wire-contract notes (current client)
include_answer_keyis additive with a compatible default; response shape unchanged for the old client.Verification
ruff checkclean. Migration replayed from empty by the stack bring-up; applied to staging ahead of merge.🤖 Generated with Claude Code