# EDU-008 — Verify idempotent answer saving - **Status:** done — answer idempotency and rollback contract verified on PostgreSQL - **Type:** implementation/verification - **Phase:** 2 - **Blockers:** EDU-007 ## Implementation result The existing unified PostgreSQL idempotency claim remains the final guard. Matching keys replay only a complete, valid stored response; null, blank, malformed, or structurally incomplete replay data now fails closed without re-executing the answer mutation. Completion of a newly claimed response must update exactly one idempotency row before session/question state changes. Answer saving remains limited to Option-backed Questions. Optionless/free-text behavior is explicitly rejected until a separate subjective-answer contract is designed. The answer HTTP seam now uses the EDU-007 Member principal and TenantContextHolder boundary and rejects ADMIN principals. PostgreSQL tests prove same-key/same-payload replay, same-key/different-payload conflict including concurrent requests, different-key CAS serialization, stale version/sequence rejection, user/tenant/state/question ownership checks, full rollback of answer/session/claim state, refresh recovery, and no answer-key/explanation leakage. No migration was added or executed by EDU-008. ## Scope - Same key and same canonical payload replays the original result. - Same key and different payload returns a conflict. - Database uniqueness is the final idempotency guard. - Session version and client sequence prevent stale updates. - Selected options are validated against the safe snapshot contract. - Optionless answer behavior remains blocked until explicitly designed; do not pretend it is option-backed. ## Acceptance criteria - [x] Same-payload duplicate semantics are explicit and tested. - [x] Conflicting payload is rejected. - [x] Concurrent same-key and different-key behavior is tested on PostgreSQL. - [x] Stale session version and stale per-session command sequence are rejected. - [x] Wrong user, tenant, session state, or question membership is rejected. - [x] Responses and restored sessions contain no answer key or explanation. - [x] Failure does not partially update answer, sequence, session version, or the idempotency claim. ## Verification Focused answer service/controller/Mapper tests, PostgreSQL concurrency tests, EDU-001 regressions, and required diff/compile gates. ## Verification performed ```text PracticeAnswerControllerHttpTest: 16 passed PracticeAnswerServiceImplTest: 37 passed on PostgreSQL/V4030 Total focused: 53 passed Failures/errors/skipped: 0 ``` The service suite includes concurrent same-key identical and conflicting payloads plus exact different-key winner/loser assertions. Incomplete replay rows fail closed, and rollback assertions cover session version, session sequence, question state, and claim removal. ## Risk and rollback - **Risk:** Medium-to-high concurrency and offline-retry risk. - **Rollback:** Application rollback with schema retained; forward migration for any constraint correction.