Skip to content

feat(wallet): tag-based exclusions for auto-withdrawal (BF-319) - #49

Merged
zaxovaiko merged 5 commits into
feat/wallet-auto-withdrawal-thresholdsfrom
feat/wallet-auto-withdrawal-tag-exclusions
Aug 5, 2026
Merged

feat(wallet): tag-based exclusions for auto-withdrawal (BF-319)#49
zaxovaiko merged 5 commits into
feat/wallet-auto-withdrawal-thresholdsfrom
feat/wallet-auto-withdrawal-tag-exclusions

Conversation

@marek-chmielowski-blurify

@marek-chmielowski-blurify marek-chmielowski-blurify commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Moves the auto-withdrawal engine's tag-exclusion list (excludeRiskFlags) from static platform config into the DB-backed wallet_auto_withdrawal_config singleton row, so a Super Admin can edit it at runtime via the existing autoWithdrawalConfig.set/.get routes - no deploy, mirroring how fiatThreshold/cryptoThreshold already work (BF-211).
  • The DB value is the entire, sole source of truth for the exclusion set - no tag is hardcoded as permanently excluded. A Super Admin can clear it to [] and every withdrawal becomes eligible for auto-approval by tag (still gated by threshold/KYC/heuristics/caps independently).
  • Migration adds 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.
  • Removes excludeRiskFlags from the static AutoWithdrawalConfigSchema entirely (.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/core bump).

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_abuser always unioned in, so .set could 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." d256184 removes the floor entirely; 7ba2f6b brings the QA e2e suite in line with the corrected behavior; 47b86de is 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, not dev.

Acceptance criteria (BF-319)

  • Exclusion tags configurable by Super Admin from Backoffice - no code change required, including high_risk/bonus_abuser (the AC does not carve out any tag as non-editable)
  • A withdrawal from a player carrying any tag currently in excludeRiskFlags (the DB value, verbatim) is never auto-approved, regardless of amount, when otherwise eligible
  • Clearing excludeRiskFlags to [] genuinely disables all tag-based exclusion - still subject to threshold/KYC/heuristics/caps independently
  • Auto-approval check includes tag validation before processing
  • A per-player autoWithdrawalRule override does not bypass the tag-exclusion gate
  • Configuration changes applied immediately and recorded in audit log (both the config-change audit and the per-decision effectiveExcludeTags audit, 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: clearing excludeRiskFlags to [] 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/testing e2e (qa-bf211-wallet-auto-withdrawal-config, qa-bf319-wallet-auto-withdrawal-exclude-risk-flags) - rewritten in 7ba2f6b to assert the corrected (no-floor) behavior; all green
  • pnpm verify (typecheck, lint, format, boundaries, module-shape, deprecations, unit tests, tools, drift check) - green
  • Reviewed via contract-reviewer, security-reviewer, quality-reviewer subagents; findings addressed (dead-code branch removed, redundant test rewritten to assert the migration default directly)

BF-319

agniev-a-hub
agniev-a-hub previously approved these changes Aug 4, 2026
Marek Chmielowski 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
zaxovaiko force-pushed the feat/wallet-auto-withdrawal-tag-exclusions branch from 72a4650 to cfa44ad Compare August 5, 2026 16:40
@zaxovaiko
zaxovaiko merged commit 047456e into dev Aug 5, 2026
2 checks passed
@zaxovaiko
zaxovaiko deleted the feat/wallet-auto-withdrawal-tag-exclusions branch August 5, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants