Skip to content

feat(wallet): add runtime auto-withdrawal thresholds - #45

Merged
zaxovaiko merged 5 commits into
devfrom
feat/wallet-auto-withdrawal-thresholds
Aug 5, 2026
Merged

feat(wallet): add runtime auto-withdrawal thresholds#45
zaxovaiko merged 5 commits into
devfrom
feat/wallet-auto-withdrawal-thresholds

Conversation

@marek-chmielowski-blurify

Copy link
Copy Markdown
Collaborator

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_config row is a global singleton for the current single-tenant runtime, so it intentionally has no tenantId or RLS policy. Both seeded thresholds default to zero, preventing an upgrade from silently enabling auto-approval; missing config also fails closed.

  • pnpm verify is green (typecheck + lint + boundaries + module-shape + tests)
  • pnpm check:drift is green (catalog / OpenAPI not stale) - run pnpm regen if not
  • New cross-module talk goes through events / command ports / contracts / the /schema subpath (no direct module imports)
  • New data tables carry tenantId and are RLS-covered (pnpm regen runs gen:rls)
  • No secrets, real player data, or internal/customer names added

Closes BF-211

Comment thread packages/core/src/wallet/service/wallet.service.ts
Comment thread packages/core/src/wallet/router/index.ts Outdated
Comment thread packages/core/src/wallet/contract/index.ts Outdated
Marek Chmielowski 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.
Renamed wallet-auto-withdrawal-config.router.test.ts to .int.test.ts
to satisfy dev's new integration-test naming convention.
@marek-chmielowski-blurify
marek-chmielowski-blurify marked this pull request as ready for review August 4, 2026 10:25
@zaxovaiko
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
zaxovaiko merged commit 8395431 into dev Aug 5, 2026
2 checks passed
@zaxovaiko
zaxovaiko deleted the feat/wallet-auto-withdrawal-thresholds branch August 5, 2026 16:52
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>
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