Conversation
zaxovaiko
added this pull request to stack #179
September 18, 2026 08:41
zaxovaiko
force-pushed
the
feat/promo-admin-grants
branch
from
September 18, 2026 08:48
61e2174 to
a5583ab
Compare
A support manager resolving a dispute needs two things: what bonuses this player has held, and the ability to take one away with a reason on the record. The admin view is the player's view plus the source and its reference - which deposit, which rain drop, which offer produced the bonus. A player has no need for that and does not get it; support cannot answer "why do I have this" without it. The reason and a note of real length are mandatory at the contract layer rather than checked in the handler. A handler check is one refactor away from being skipped, and a forfeiture with no recorded reason is the thing the regulator asks about. Forfeiting a bonus that has already completed, expired or been taken refuses rather than reporting success over a no-op. An admin who forfeits a bonus that converted an hour ago has made a decision on stale information and needs to be told, not reassured.
…osed one The admin forfeit was the only forfeit path that destroyed bonus funds without emitting `promo.bonus.forfeited`. The sweep emits, self-exclusion emits, and a hand-issued forfeit - the one most likely to be disputed later - was silent, so every consumer of that event under-reported exactly the cases an operator most needs to see. Forfeiting a grant id that does not exist answered 409 "no longer active, so there is nothing to forfeit", which asserts the bonus existed and was already closed. A typo'd id read as a completed action, and the false statement went into the dispute trail. It is 404 now, and the read-back's own not-found error is mapped rather than surfacing as a 500 after the forfeit committed. An attempt that takes nothing now leaves an audit row saying so. A money resource an admin can probe without trace is the case where the outcome matters most, and until now only the attempts that succeeded were recorded. `close()` treats `pending` as live alongside `active`. Nothing writes that status today, but this is the change that makes forfeiting admin-visible, and a pending grant that could be neither forfeited nor expired would sit there and pay out later.
zaxovaiko
force-pushed
the
feat/promo-admin-grants
branch
from
September 18, 2026 08:57
a5583ab to
cf2b767
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #193. A support manager can see a player's bonuses and take one away with a reason on the record. BF-595's API half.
Why
The admin view is the player's view plus the source. Which deposit, which rain drop, which offer produced the bonus - a player has no need for that and does not get it, and support cannot answer "why do I have this bonus" without it. The terms snapshot still stays internal on both surfaces; it is the operator's weighting, not an answer to a dispute.
The reason and the note are mandatory at the contract, not in the handler. A handler check is one refactor away from being skipped, and a forfeiture with no recorded reason is exactly what a regulator asks about. The note has a minimum length and is trimmed, so "." does not satisfy it.
Forfeiting a bonus that is already gone refuses rather than reporting success. The claim-then-act update finds nothing to claim and the route returns 409. An admin who forfeits a bonus that converted an hour ago acted on stale information and needs to be told - a silent no-op leaves them believing they took money back that the player already has.
What the review pass changed
promo.bonus.forfeited. It was the only forfeit path that destroyed bonus funds silently - the sweep emits, self-exclusion emits - so every consumer of that event under-reported the hand-issued forfeits, which are the ones most likely to be disputed.pendingcounts as live. Nothing writes that status today, but this PR is what makes forfeiting admin-visible, and a pending grant that could be neither forfeited nor expired would sit there and pay out later.Alternatives considered
Re-using the player grant route with an admin-visible flag. Two audiences, two shapes, and a flag deciding which fields come back is the kind of thing that leaks the wider shape to the narrower audience the first time someone edits it.
Letting the forfeit route return the grant it took as the service saw it. It re-reads instead. The lifecycle service returns what it closed for the caller's own bookkeeping, not a DTO, and shaping that into the admin view inside the service would put a wire concern in the place that moves money.
Risks
Forfeiting is per grant. Taking every bonus a player holds is several calls, and nothing in the API expresses "all of them" - the sweep does that on self-exclusion and account closure, where it is a rule rather than a decision.
The route reads the grant back after the forfeit, so the response is a second query rather than the row the update returned. Consistent within the transaction that just committed, but a grant forfeited and then immediately expired by the sweep would report whichever landed last.