3.5 KiB
EDU-009 — Atomic submit, immutable report, wrong questions, and favorites
- Status: done
- Type: implementation
- Phase: 2
- Blockers: EDU-008 (done)
Student outcome
A student can retry submission safely, receive exactly one immutable report, and see consistent wrong-question and favorite projections.
Core defect to resolve
The current submit path has evidence of check-then-insert idempotency. This ticket must define and implement an atomic initial claim with crash recovery before treating submission as complete.
Scope
- Atomic submit-key reservation using PostgreSQL uniqueness/
ON CONFLICTor the approved project mechanism. - Explicit processing/completed/failed or equivalent recovery semantics.
- Same-key replay and conflicting-payload behavior.
- Single state transition from active session to submitted.
- Immutable scoring/report snapshot.
- Duplicate-safe wrong-question projection.
- Favorite behavior remains independent and tenant/user scoped.
- No external calls inside a long database transaction.
Acceptance criteria
- Concurrent same-key same-payload requests converge on one report.
- Same key with different payload is rejected.
- Different keys racing on one session produce at most one committed submit.
- A crash after claim has a documented retry/recovery result.
- Report content remains stable after question mutation.
- Wrong-question projection is idempotent.
- Report and history access enforce user and tenant ownership.
- Pre-submit responses never expose protected answer data; post-submit response follows the approved report contract.
Recovery contract
- The submit key is serialized with a PostgreSQL transaction-level advisory lock, then reserved with
INSERT ... ON CONFLICT DO NOTHINGbefore session/report writes. - The claim uses
PROCESSINGwith a unique token and lease timestamp; report, report detail, wrong-question projection, session CAS, and token-checked claim completion run in the same transaction. - A normal processing failure rolls the full transaction back, including a newly inserted claim. If a previously
committed/manual
PROCESSINGclaim exists (for example after legacy partial persistence), a retry can take over the matching claim after the 120-second lease expires. A mismatched payload can never take over the claim. - A completed claim stores
report_idand the complete immutable response asCOMPLETED; same-key retries replay only a structurally complete matching response. Malformed or incomplete committed rows fail closed. - A different key that loses the session race is completed against the immutable winner report, so retries of either accepted key remain stable.
No external provider call occurs in the submit transaction: scoring uses the persisted session question snapshot.
V4040 adds the claim token/timestamp columns and normalizes migrated successful SUBMIT_SESSION rows from
legacy ACCEPTED to COMPLETED without changing V4030 release history.
Verification
PostgreSQL concurrency tests are mandatory, along with service/controller/projection tests and required diff/compile gates.
Focused PostgreSQL verification on 2026-07-30:
PracticeSubmitServiceImplTest: 38 passed.- Submit/report controller, projection, wrong-question, and favorite suites: 129 passed total.
- Failures, errors, skipped: 0.
Risk and rollback
- Risk: High state-machine and data-consistency risk.
- Rollback: Disable practice writes or roll back application version; repair through forward migration only.