70 lines
3.5 KiB
Markdown
70 lines
3.5 KiB
Markdown
# 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 CONFLICT` or 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
|
|
|
|
- [x] Concurrent same-key same-payload requests converge on one report.
|
|
- [x] Same key with different payload is rejected.
|
|
- [x] Different keys racing on one session produce at most one committed submit.
|
|
- [x] A crash after claim has a documented retry/recovery result.
|
|
- [x] Report content remains stable after question mutation.
|
|
- [x] Wrong-question projection is idempotent.
|
|
- [x] Report and history access enforce user and tenant ownership.
|
|
- [x] 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 NOTHING` before session/report writes.
|
|
- The claim uses `PROCESSING` with 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 `PROCESSING` claim 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_id` and the complete immutable response as `COMPLETED`; 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.
|