T2.1-W01 Atomicity Matrix¶
| Case | Path | Expected HTTP | Expected DB | Evidence |
|---|---|---|---|---|
| TERM-CREATE-VALID | academic-years/save create | success | year + all terms | W01-TERM-CREATE-VALID |
| TERM-START-AFTER-END | create | validation_error | 0 new year; 0 new terms | W01-TERM-START-AFTER-END |
| TERM-BEFORE-YEAR | create | validation_error | 0 new year; 0 new terms | W01-TERM-BEFORE-YEAR |
| TERM-AFTER-YEAR | create | validation_error | 0 new year; 0 new terms | W01-TERM-AFTER-YEAR |
| TERM-MIXED | create (1 valid + 1 invalid) | validation_error | 0 new year; 0 new terms | W01-TERM-MIXED |
| TERM-UPDATE-ATOMIC | update + replace_terms invalid | validation_error | year+terms unchanged | W01-TERM-UPDATE-ATOMIC |
| TERM-CROSS-TENANT | mutate GT-T2 year id | access_denied | foreign year/terms unchanged | W01-TERM-CROSS-TENANT |
| SESSION conflict update | sessions/save | conflict | original session unchanged | W01-SESSION-UPDATE-ATOMIC |
| SESSION concurrent | dual create | exactly one winner | +1 row | W01-SESSION-CONCURRENT |
| LH overlap | lesson-hours/save | validation_error | 0 new row | W01-LH-OVERLAP-FORBIDDEN |
Implementation decision¶
Option A + B combined for academic year:
- Pre-validate all nested term payloads before any year write.
- Wrap year write + term mutations in
cr.savepoint()so any failure rolls back partial state.
Lesson hours: pre-check start<end, company-scoped profile check, savepoint around create/write.
Session concurrent: unique index op_session_faculty_start_uniq as hard backstop when workers=0 reuses a PG connection (advisory locks become re-entrant).