feat(wallet): add runtime auto-withdrawal thresholds - #45
Merged
Conversation
marek-chmielowski-blurify
requested review from
agniev-a-hub,
klaudia-blazyczek-blurify and
zaxovaiko
as code owners
July 30, 2026 12:22
marek-chmielowski-blurify
marked this pull request as draft
July 31, 2026 06:15
klaudia-blazyczek-blurify
previously requested changes
Jul 31, 2026
added 2 commits
August 3, 2026 12:39
…rawal-thresholds # Conflicts: # packages/core/src/wallet/drizzle/migrations/meta/0004_snapshot.json # packages/core/src/wallet/drizzle/migrations/meta/_journal.json # tools/db/seed.ts
- resolveAutoThreshold now reads the global config singleton before accepting a per-player override, so an unseeded install fails closed for every player, not just those without an override. - setAutoWithdrawalConfig runs the before-read, upsert, and audit write in one transaction, so an audit-write failure rolls back the threshold change instead of leaving it un-audited. - bound fiatThreshold/cryptoThreshold to the wallet_auto_withdrawal_config column's decimal(18,8) integer-digit budget so an out-of-range value is a 4xx instead of a DB overflow.
10 tasks
Renamed wallet-auto-withdrawal-config.router.test.ts to .int.test.ts to satisfy dev's new integration-test naming convention.
marek-chmielowski-blurify
marked this pull request as ready for review
August 4, 2026 10:25
zaxovaiko
approved these changes
Aug 5, 2026
zaxovaiko
dismissed
klaudia-blazyczek-blurify’s stale review
August 5, 2026 16:52
Dismissed to unblock stacked PR #49 merge per explicit request; underlying fail-closed/audit-atomicity/precision concerns not addressed in code, tracked for follow-up.
zaxovaiko
pushed a commit
that referenced
this pull request
Aug 5, 2026
## 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) - [x] 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) - [x] 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 - [x] Clearing `excludeRiskFlags` to `[]` genuinely disables all tag-based exclusion - still subject to threshold/KYC/heuristics/caps independently - [x] Auto-approval check includes tag validation before processing - [x] A per-player `autoWithdrawalRule` override does not bypass the tag-exclusion gate - [x] 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 - [x] `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) - [x] `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 - [x] `pnpm verify` (typecheck, lint, format, boundaries, module-shape, deprecations, unit tests, tools, drift check) - green - [x] 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 --------- Co-authored-by: Marek Chmielowski <marek.chmielowski@blurify.com>
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
Moves global fiat and crypto auto-withdrawal thresholds from static platform configuration into a DB-backed wallet singleton. Adds Super Admin read/update routes, audit recording, safe seed defaults, migration coverage, and unit/e2e tests.
Why
Introduced these changes so operators can update auto-withdrawal thresholds at runtime without a redeploy while preserving fail-closed payout behavior and a complete audit trail.
Worth knowing
The new
wallet_auto_withdrawal_configrow is a global singleton for the current single-tenant runtime, so it intentionally has notenantIdor RLS policy. Both seeded thresholds default to zero, preventing an upgrade from silently enabling auto-approval; missing config also fails closed.pnpm verifyis green (typecheck + lint + boundaries + module-shape + tests)pnpm check:driftis green (catalog / OpenAPI not stale) - runpnpm regenif not/schemasubpath (no direct module imports)tenantIdand are RLS-covered (pnpm regenrunsgen:rls)Closes BF-211