PAR-142
/pay routes to a player's personal account instead of the same-named GOVERNMENT account (cache-dependent)
Symptom
/pay <name> <amount> sometimes lands in a player's personal account instead of the GOVERNMENT account of the same name — and it's non-deterministic ("sometimes").
Root cause (confirmed in prod)
DemocracyCraft operates several governments as Minecraft player alt-accounts whose username equals a Treasury GOVERNMENT account's display_name. Two names currently exist as both:
DCGovernmentAV→ GOVERNMENT#49929($3.38M) and player alt PERSONAL#104237DCGovernmentOR→ GOVERNMENT#27107($3.35M) and player alt PERSONAL#104236
PayCommand.doPay (treasury commands/PayCommand.java:72-88) resolves the target player-first:
OfflinePlayer target = Bukkit.getOfflinePlayerIfCached(targetName); // ← only resolves if cached
boolean knownPlayer = target != null && (target.hasPlayedBefore() || target.isOnline());
if (knownPlayer) payPlayer(...); // → PERSONAL account
else getGovernmentAccountByName(targetName) → payGov; // → GOVERNMENT account (fallback)
Because a real player owns DCGovernmentOR, the payment goes to the personal account whenever the username is in the server usercache, and only falls through to the GOVERNMENT account when it isn't — hence the cache-dependent "sometimes". The existing code comment even acknowledges the ambiguity ("use /pay-account to disambiguate").
AccountResolver//pay-account <type> <name> and /gov, tax, and fines lookups all go through the type-filtered findGovernmentAccountByName and are not affected — only the bare-name /pay is.
Evidence
- GOV
#49929received $140k and GOV#27107received payments via/pay(the cache-miss case routed correctly). - Personal
#104237shows admin clean-up of misrouted funds: ledger memos"Admin transfer: Incorrectly given starting balance","Admin transfer: Cleanup"(now $0) — i.e. staff have already had to manually fix mis-paid money here.
Fix
Make /pay deterministic on a player↔GOVERNMENT name collision: if targetName matches both a known player AND a non-archived GOVERNMENT account, refuse and instruct the sender to use /pay-account government <name> or /pay-account player <name>. (Add a cheap AccountService.governmentAccountExists(name) check used alongside the player lookup.) Do not rely on getOfflinePlayerIfCached cache state to decide routing.
Acceptance
/pay DCGovernmentOR 100with the player cached AND uncached both yield the same result (a disambiguation prompt, not a silent personal-account payment).- Non-colliding names are unaffected (player pays player, gov-name pays gov).
- Unit test covering the collision branch.
Part of a set to fix government name-collisions once and for all (see sibling Realty + audit issues).
Resources
- commit9aa5bb1 Guard player↔GOVERNMENT name collisions across remaining resolvers (PAR-144)ParadauxIO/hibernia-economy
Comments
No comments yet.
Activity
- ParadauxIO linked a commit — Commit 9aa5bb1 — Guard player↔GOVERNMENT name collisions across remaining resolvers (PAR-144)
- tesks changed status to Status → Pending Release
- tesks changed status to Status → In Progress
- tesks created the issue