Skip to content

feat(pam): automatic wallet-based tags with high_risk resweep and race-safe assignment#20

Open
marek-chmielowski-blurify wants to merge 1 commit into
devfrom
feat/automatic-wallet-based-tags
Open

feat(pam): automatic wallet-based tags with high_risk resweep and race-safe assignment#20
marek-chmielowski-blurify wants to merge 1 commit into
devfrom
feat/automatic-wallet-based-tags

Conversation

@marek-chmielowski-blurify

Copy link
Copy Markdown
Collaborator

Summary

Adds two automatic wallet-based player tags (withdrawal_review, dormant_high_roller) and a daily high_risk resweep, and closes gaps found while building them: tag routes were unguarded, concurrent tag assignment could race, and AppError's callers didn't match its constructor signature.

Changes

  • pam/tag: withdrawal_review tag assigned on wallet.withdrawal.requested (the attempt, before funds move); dormant_high_roller assigned by a new daily co-occurrence sweep (inactive + holds high_roller); daily high_risk resweep removes the tag once withdrawal frequency drops below threshold (frequency dimension only, amount dimension is never resweepable - see AGENTS.md for the AML rationale).
  • pam/tag: all tag routes (createTag/deleteTag/listPlayerTags/listAssignableTags/assignPlayerTag/removePlayerTag) now gated by adminGuard on a new tag permission resource (view/create/delete); domain errors mapped via mapErrors instead of leaking as raw 500s.
  • pam/tag schema: partial unique index (player_tag_active_key on (tagId, playerId) WHERE removedAt IS NULL) backing an onConflictDoNothing insert - closes the concurrent-assignment / at-least-once-redelivery race that the app-level pre-check alone couldn't.
  • wallet: WalletReader.getWithdrawalCountsInWindow - batched per-user withdrawal counts for the high_risk resweep (one query for all holders, not N).
  • audit: subscribes to new tag.created/tag.deleted events so tag catalog admin actions are audited.
  • common/errors: fixed ConflictError/NotFoundError/ValidationError/mapDbError to call AppError with the correct (message, code) argument order (was (code, message), silently swapped).
  • New migrations 0002-0004 for the pam/tag module (new tag keys, partial unique index).

Acceptance criteria

  • withdrawal_review tag assigned on withdrawal request when amount crosses the configured threshold, sticky, never auto-removed
  • dormant_high_roller tag assigned/removed by the daily sweep based on inactivity + high_roller co-occurrence
  • high_risk tag auto-removed once withdrawal frequency drops back under threshold
  • Tag routes require admin permission
  • Concurrent/duplicate tag assignment is impossible even under at-least-once event redelivery

Checklist

  • pnpm verify is green (typecheck + lint + boundaries + module-shape + tests)
  • pnpm verify:drift is green (catalog / OpenAPI not stale)
  • 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 - N/A, no new tables (index-only migration)
  • No secrets, real player data, or internal/customer names added
  • Docs / AGENTS.md updated if behavior or extension points changed

…e-safe assignment

Adds withdrawal_review (assigned on wallet.withdrawal.requested) and
dormant_high_roller (daily co-occurrence sweep) tags, plus a daily
high_risk resweep that removes the tag once withdrawal frequency drops
below threshold. Gates all tag routes behind adminGuard, closes the
concurrent-assignment race with a partial unique index, and maps domain
errors to proper HTTP statuses instead of a raw 500. Fixes AppError's
callers to match its actual (message, code) constructor signature.
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.

1 participant