Deny helper returns Denied + reasonCode even without a snapshot bucket, so invalid_quantity/invalid_item are stable before NEO-55 HTTP mapping.
Re-read snapshot under per-player lock; route add/remove through TryMutateSnapshot so read-modify-write is serialized for in-memory and wrapped in one transaction for Postgres.