feat(wallet): tag-based exclusions for auto-withdrawal (BF-319) - #49
Merged
zaxovaiko merged 5 commits intoAug 5, 2026
Conversation
marek-chmielowski-blurify
requested review from
agniev-a-hub,
klaudia-blazyczek-blurify and
zaxovaiko
as code owners
August 4, 2026 05:45
agniev-a-hub
previously approved these changes
Aug 4, 2026
added 5 commits
August 5, 2026 18:40
Move the auto-withdrawal exclusion tag list from static platform config into the DB-backed wallet_auto_withdrawal_config singleton row, so a Super Admin can edit it at runtime with no deploy - mirroring fiatThreshold/cryptoThreshold from BF-211. A non-removable compliance floor (withdrawal_review, kyc_rejected, multi_account, high_risk, bonus_abuser) is unioned into the DB-configured set server-side at evaluation time, so a `.set` call can widen exclusions but never narrow them below the floor.
…al exclusions (BF-319) Product owner rejected the hardcoded COMPLIANCE_FLOOR_TAGS union added in c981b27: the ticket AC requires the exclusion list be fully Super-Admin configurable with no floor. excludeRiskFlags is now the DB value verbatim; the 5-tag migration default remains only as a starting value, not an enforced minimum.
…floor The QA e2e suite (qa-bf319-wallet-auto-withdrawal-exclude-risk-flags.e2e.test.ts) still asserted the non-removable COMPLIANCE_FLOOR_TAGS union removed in the prior commit. Rewrite it to match the corrected design: an empty excludeRiskFlags disables all tag-based exclusion, an admin's submitted list is authoritative with no server-side widening, and the auto-approval audit record's effectiveExcludeTags equals the DB value verbatim. Also cleans up stray "compliance floor" wording left in adjacent comments (router test, seed script, sibling e2e fixture).
The evaluateWithdrawalRequested race-avoidance rationale is already documented on evaluateWithdrawalRequested itself (tag-evaluation.service.ts) and in wallet/AGENTS.md; the duplicate inline copy at the call site added no information not already carried by the call order and those docs.
…from sibling suites The singleton wallet_auto_withdrawal_config row is shared across every e2e file in one test:integration run. qa-bf319's suite leaves excludeRiskFlags overwritten (eg to ['large_depositor']), and wallet-ledger-auto-withdrawal's onConflictDoNothing seed silently kept that leftover instead of the migration DEFAULT whenever it ran after qa-bf319 - Vitest's file order isn't guaranteed, so this was order-dependent and flaked CI post-merge. Delete the row before reseeding, same pattern qa-bf319's own first test already uses.
zaxovaiko
force-pushed
the
feat/wallet-auto-withdrawal-tag-exclusions
branch
from
August 5, 2026 16:40
72a4650 to
cfa44ad
Compare
zaxovaiko
approved these changes
Aug 5, 2026
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
excludeRiskFlags) from static platform config into the DB-backedwallet_auto_withdrawal_configsingleton row, so a Super Admin can edit it at runtime via the existingautoWithdrawalConfig.set/.getroutes - no deploy, mirroring howfiatThreshold/cryptoThresholdalready work (BF-211).[]and every withdrawal becomes eligible for auto-approval by tag (still gated by threshold/KYC/heuristics/caps independently).wallet_auto_withdrawal_config.exclude_risk_flags text[] NOT NULL DEFAULT(5 tags:high_risk,bonus_abuser,kyc_rejected,withdrawal_review,multi_account), so an upgraded install with a pre-existing row keeps today's gating behavior before any admin edits it - a starting value only, not an enforced floor.excludeRiskFlagsfrom the staticAutoWithdrawalConfigSchemaentirely (.strict()schema - an intentional breaking change; a consumer's static config still setting this key will fail validation at boot until the key is removed, coordinated with the downstream consumer's@openora/corebump).Revision note: the first version of this PR (
c981b27) shipped with a server-enforced, non-removable "compliance floor" (withdrawal_review,kyc_rejected,multi_account,high_risk,bonus_abuseralways unioned in, so.setcould widen but never narrow the exclusion set). That was my own synthesis of a domain-expert review, not a signed-off requirement. When checked directly against the ticket, the product owner rejected it: the AC says "configurable by Super Admin... no code change required," full stop - not "configurable except for 5 tags we decided are too important to trust an admin with."d256184removes the floor entirely;7ba2f6bbrings the QA e2e suite in line with the corrected behavior;47b86deis a trivial follow-up cleanup. Kept as separate commits rather than squashed/force-pushed so the reasoning stays in the history.Stacked on
feat/wallet-auto-withdrawal-thresholds(open PR #45, BF-211) per request - this PR targets that branch, notdev.Acceptance criteria (BF-319)
high_risk/bonus_abuser(the AC does not carve out any tag as non-editable)excludeRiskFlags(the DB value, verbatim) is never auto-approved, regardless of amount, when otherwise eligibleexcludeRiskFlagsto[]genuinely disables all tag-based exclusion - still subject to threshold/KYC/heuristics/caps independentlyautoWithdrawalRuleoverride does not bypass the tag-exclusion gateeffectiveExcludeTagsaudit, which now records the DB value at decision time verbatim, no floor union)Test plan
pnpm -F @openora/core vitest run src/wallet/__tests__- 172/172 passing (covers: clearingexcludeRiskFlagsto[]lets a previously-excluded tag auto-approve, an admin's submitted list is authoritative with no server-side widening, per-player threshold override doesn't bypass the tag gate, upgraded-install migration default)packages/testinge2e (qa-bf211-wallet-auto-withdrawal-config,qa-bf319-wallet-auto-withdrawal-exclude-risk-flags) - rewritten in7ba2f6bto assert the corrected (no-floor) behavior; all greenpnpm verify(typecheck, lint, format, boundaries, module-shape, deprecations, unit tests, tools, drift check) - greenBF-319