Roll back import atomically on storage-quota failure (M6, §08)

This commit is contained in:
2026-06-07 20:03:47 +03:00
parent 1bead4d004
commit 30ff7ae357
3 changed files with 211 additions and 6 deletions
+21
View File
@@ -426,6 +426,27 @@ user-fixable (storage full → next step, no diagnostic) from not (blocked stora
explanation **+** a reportable `detail`); and a blocked store at startup **warns and runs
in memory** rather than rejecting into the void.
### Multi-record writes (import): atomicity at the service boundary
`db.ts` exposes only per-store request/transaction helpers — there is **no wrapper for a
single transaction spanning many records across stores**. So a bulk operation like **import**
(`services/transfer.ts`) cannot be truly atomic at the IDB layer; it achieves atomicity at
the **service boundary** instead: write the new records to IndexedDB first, tracking what
succeeded, and only on full success commit to the Zustand stores. On any write failure
(typically `QuotaExceededError`) it **rolls back best-effort** — deletes the records written
so far (`Promise.allSettled`) and removes any datasets already added to the store — so the
spec §08 "no partial import is committed" contract holds and the user gets an actionable
"storage full, delete and retry" message.
> **Rule:** for any operation that persists multiple records, write-then-commit and roll
> back on failure — never mutate the in-memory stores before the writes are known to have
> landed (a half-merged workspace is worse than a failed import).
> **Limit:** rollback is best-effort, not transactional; if the rollback deletes themselves
> fail, orphan records can remain (invisible to the app — never added to a store — and
> cleaned up on the next successful write). True cross-record atomicity would require
> exposing a raw multi-store transaction from `db.ts`; defer that until a second multi-record
> writer needs it.
---
## 7. Checklist for Adding a New Persisted Entity