Code review of the Ask-7 web equipment-management path surfaced three
correctness issues, all fixed here:
- applyEquipOrder ran the poll-goroutine equip mutations without the
per-user advUserLock that every Matrix-side mutation (!give, !equip,
arena, …) holds, so the lock gave no mutual exclusion against the web
path. A concurrent !give of the item being equipped could duplicate it.
Now takes advUserLock(owner) for the whole apply, matching the DM path.
- applyMasterworkEquip evicted the displaced occupant to the pack BEFORE
the destructive slot write, so a fault left the piece both worn and in
the pack — and the 30s equip poll retry re-evicted it every tick. Now
removes the incoming row, writes the slot, then re-packs the occupant
last as a best-effort step: once the slot no longer references it, the
re-pack cannot duplicate, and a failure is logged not aborted on (the
DM confirm handler's tolerance).
- PlayerDetail.Balance dropped omitempty: a real €0 balance is an
informative fact, not an absent one, and omitting it left the web
confirm dialog with no balance to show.
Mirror of Pete's ask 7. gogobee polls the equip queue and applies the new
actions against the five standard gear slots:
- equip: routes MasterworkGear/ArenaGear to applyMasterworkEquip (evicts
any special occupant back to the pack; downgrade-blocked), else the
existing applyMagicEquip.
- unequip: EquipmentSlot vocabulary -> applyMasterworkUnequip (resets the
slot to its tier-0 default, keeps the row), else applyMagicUnequip.
- upgrade: purchaseEquipmentTier, euro-idempotent (DebitIdem keyed on the
order GUID), downgrade + max-tier guarded.
- repair: repair(), euro-idempotent, recomputes blacksmithRepairCost.
Detail push now carries Slots (EquipSlotView x5) + Balance; itemViews gives
masterwork/arena backpack rows an equip id; the compare decorator is guarded
to magic-only. buildDetailSnapshot is a method so it can read the euro balance.
Retry-safety: no CreditIdem refund on a later save fault (would double-pay a
guid-guarded retry) — we return retry=true and let the next poll re-run, since
the debit is guid-idempotent and the slot write is idempotent. Matches the
casino escrow precedent. Unit tests cover downgrade block, max-tier,
insufficient funds, idempotent replay, eviction, and take-off reset.
Deploy AFTER Pete: Pete's ingest must accept the new verdict strings before
this side emits them.