strings/ is split into domain submodules whose names are re-exported from
strings/__init__.py. Adding a constant to a submodule and forgetting to
re-export it - or shadowing a name across two submodules - would only
surface as a runtime crash in a command. This test asserts every submodule
__all__ entry is reachable as strings.NAME, that no name is defined twice,
and that strings.__all__ matches the union of the submodules. Submodules
are auto-discovered, so a new one is covered without editing the test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
core/economy has long been a package (store/income/gambling/shop/levels/
jail/heist/prestige/fishing/quests/leaderboards/house/admin) rather than a
single core/economy.py, but README.md and docs/DEV_NOTES.md still described
it as one file in ~27 places. Point every reference at the real submodule,
add a "Defined in" column to the constants quick-reference and a Module
column to the strings table, and fix the stale FISH -> FISH_CATALOGUE name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A follow-up reentrancy sweep of the five remaining money-moving views found
one more instance of the same bug class. The other four (quests, prestige,
fish, shop) verified clean - their underlying do_* functions are idempotent.
RequestView / FundModal (commands/economy_support_commands.py):
- on_submit read self._view.remaining, then awaited do_give, then decremented
remaining. Because discord.py dispatches each modal submit as its own task
and do_give is a plain non-idempotent transfer, a funder could open two
modals and submit both before the first resolved: both read the same
pre-decrement remaining, both passed the range check, and both transferred
`amount` - over-funding the request (remaining goes negative) and moving up
to the funder's whole balance.
- Fix: reserve the amount synchronously (decrement remaining BEFORE the do_give
await, with no await in between - atomic under asyncio), and roll the
reservation back if the transfer fails. The second concurrent submit now
sees the reduced remaining and is rejected. Added a cheap _fund guard
(remaining<=0 / is_finished) so a click on a funded request doesn't open a
dead modal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>