docs(spec,plan): a failed write is compensated, so a retry is safe

The ledger row is written first on purpose, so a crash cannot leave the
audit trail behind the cached total. But when the quantity write then
fails, the ledger sat ahead of the quantity - and the operator was told
to retry, which wrote a second ledger row for the same change and
double-counted it.

A failed quantity write now reverses its own ledger row, so SUM(change_qty)
still reconstructs current_qty and a retry is safe.
This commit is contained in:
Dave 2026-11-05 22:36:37 +02:00
parent 7dd70577a7
commit 62faa390d5
2 changed files with 4 additions and 0 deletions

View file

@ -603,6 +603,8 @@ The PocketBase work: fetch candidates, resolve, apply through the audited servic
### Task 3 amendments (operator decision 2026-11-05, plus implementation findings)
> **Later addition (Task 3 fix round):** the compensation described in the spec — a failed quantity write reverses its own ledger row so `SUM(change_qty)` still reconstructs `current_qty` and a retry is safe — lives in `src/lib/server/stock-quantities-service.ts` (Task 1's file). It was added during Task 3's review because the orphan ledger row was surfaced there. `updateStockItemsQuantities` therefore now writes a compensating row with the opposite `change_qty` and `reason: "adjustment"` when the `stock_items.update` throws after the ledger row landed.
The steps below are the original task text. Four changes were decided after it was first implemented. **They govern.** The original Step 3 code is superseded on these points; the original Step 1 test list is superseded by the list here.
**Amendment 1 — a dry run reports the plan, not a write failure.** The original snippet fell through to the "write did not complete" branch for every matched line on a dry run, because a dry run produces no write results. That contradicted the brief's own dry-run test, which expects `applied === 1` plus the before/after numbers.

View file

@ -137,6 +137,8 @@ Per-row independence: one bad line never stops the others.
A **failed write is its own status**, not an unmatched line. Telling the operator "you typed the SKU wrong" when the cause was a transient database error sends them hunting for a typo instead of retrying — different problems, different responses. `unmatched` keeps its meaning: the line never resolved to an active stock item (nothing matched, or the best match was inactive — the message says which).
**A failed write is compensated, so a retry is safe.** The ledger row is written first, deliberately, so a crash cannot leave the audit trail behind the cached total. When the quantity write then fails, a second ledger row reverses the first, so `SUM(change_qty)` still reconstructs `current_qty`. Without that, the ledger sits ahead of the quantity and a retry — which the operator is told to do — double-counts the change.
### 2. The one service change — negatives
`updateStockItemsQuantities` currently rejects a negative `current_qty`: