A high-effort code review of the gifting/vault work turned up three issues:
1. Item duplication (real exploit). The masterwork/arena equip confirmation
captures an item at prompt time and equips it on "yes" without re-checking
ownership. Since MasterworkGear/ArenaGear are now giftable, a player could
!give the item away in the window, then confirm — minting a copy onto
themselves while the recipient kept theirs. Fixed by taking the user lock
(making gift-vs-confirm atomic) and re-loading the inventory to refuse an
item that is no longer ours. Magic items are exempt (not giftable); other
delete paths load-and-remove within one locked invocation.
2. clearAdvInventory read the vault-filtered list but its DELETE wiped every
row including vaulted ones — no live caller today, but it would break the
vault's "safe from !sell all" promise the instant sell-all routed through
it. Delete now matches the read (in_vault = 0).
3. vault store/take took no per-user lock, so two near-simultaneous stores
could both pass the capacity check and overfill the 10-slot vault. Now
serialized on the same user lock the gift path uses.
go test ./... green; golden byte-identical.
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
The Tier-4 "Established" home now unlocks a vault: `!adventure vault
[store|take] <item>` moves items in and out of a 10-slot pool that shelters
them from `!sell all`, crafting, and combat consumption. It is also E2
gifting's prerequisite plumbing.
Implemented as a single `in_vault` flag on adventure_inventory rather than a
parallel table: a stowed item keeps its identity (id, temper, everything) and
loadAdvInventory filters it out, so a vaulted item drops out of every play
surface with one flag change and comes back untouched. DEFAULT 0 = "in play",
correct for every pre-existing row, so no bootstrap backfill.
Golden byte-identical; go test ./... green.
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa