[SEC-Q2] AML: streaming-обработка large payouts вместо batch #1152

Closed
opened 2026-07-19 17:05:42 +00:00 by andrei · 4 comments
Owner

Из SEC-Q2 MEDIUM backlog (аудит, срез 2026-07-10).

Крупные выплаты проходят AML-проверку батчем; окно между batch-прогонами — слепая зона. Перевести на streaming/по-событийную проверку крупных payout до исполнения (порог из конфига, precise money math через mulEur, критические секции FOR UPDATE).

Из SEC-Q2 MEDIUM backlog (аудит, срез 2026-07-10). Крупные выплаты проходят AML-проверку батчем; окно между batch-прогонами — слепая зона. Перевести на streaming/по-событийную проверку крупных payout до исполнения (порог из конфига, precise money math через mulEur, критические секции FOR UPDATE).
Author
Owner

Гап подтверждён точной цепочкой (анализ 2026-07-20, master). В отличие от #1151/#1154/#1155 — это РЕАЛЬНЫЙ остаточный гап.

Слепая зона для payout в диапазоне €5,000–€25,000:

  • AML-порог крупного вывода LARGE_PAYOUT_THRESHOLD=€5,000 (api/src/services/aml/thresholds.ts:31).
  • Admin manual review включается только payoutAmount > €25,000 (request-bank-payout.ts:164 → status PENDING_REVIEW).
  • Payout ≤€25K создаётся со status PENDING и немедленно исполняется: request-bank-payout.ts:340-341 (SEPA-AUTO-02) — initiateStripePayout сразу после commit. Крипто-путь аналогично.
  • Единственная AML-проверка крупного вывода — retrospective batch checkLargePayouts (api/src/services/aml/rules/large-payouts.ts), запускается cron aml-monitoring по расписанию 1,16,31,46 * * * * = каждые 15 мин (startCrons.metadata.ts:54). Скан флагит даже уже COMPLETED payout — постфактум.

Итог: вывод €5K–€25K уходит из системы до любой AML/sanctions-проверки; окно слепоты до 15 мин. inline-гейты на пути есть (KYC checkKyc:true, Travel Rule TFR, daily/monthly caps, CTR), но AML sanctions/watchlist-скрининг крупного вывода ДО исполнения отсутствует для этого диапазона.

Блокер на реализацию — compliance/product decision (regulatory implication, не чистая техника):

  1. Что делать с inline-флагнутым payout: держать в PENDING_REVIEW (ручной разбор) / авто-hold с авто-очисткой если чисто / снизить сам review-порог €25K→€5K?
  2. Какие правила гнать inline (sanctions/watchlist — обязательно; structuring/velocity — дороже, оставить в batch?).
  3. Порог inline-скрининга (€5K как AML-порог?).

Технический план после решения: inline-хук в request-bank-payout.ts/request-crypto-payout.ts ПЕРЕД SEPA-AUTO-02/крипто-исполнением — синхронный sanctions/watchlist-скрин; при hit → status HOLD/PENDING_REVIEW вместо авто-payout; идемпотентность; batch остаётся backstop; TDD + i18n новых сообщений.

**Гап подтверждён точной цепочкой (анализ 2026-07-20, master). В отличие от #1151/#1154/#1155 — это РЕАЛЬНЫЙ остаточный гап.** Слепая зона для payout в диапазоне **€5,000–€25,000**: - AML-порог крупного вывода `LARGE_PAYOUT_THRESHOLD=€5,000` (`api/src/services/aml/thresholds.ts:31`). - Admin manual review включается только `payoutAmount > €25,000` (`request-bank-payout.ts:164` → status `PENDING_REVIEW`). - Payout ≤€25K создаётся со status `PENDING` и **немедленно** исполняется: `request-bank-payout.ts:340-341` (SEPA-AUTO-02) — `initiateStripePayout` сразу после commit. Крипто-путь аналогично. - Единственная AML-проверка крупного вывода — retrospective **batch** `checkLargePayouts` (`api/src/services/aml/rules/large-payouts.ts`), запускается cron `aml-monitoring` по расписанию `1,16,31,46 * * * *` = **каждые 15 мин** (`startCrons.metadata.ts:54`). Скан флагит даже уже `COMPLETED` payout — постфактум. Итог: вывод €5K–€25K уходит из системы до любой AML/sanctions-проверки; окно слепоты до 15 мин. inline-гейты на пути есть (KYC `checkKyc:true`, Travel Rule TFR, daily/monthly caps, CTR), но **AML sanctions/watchlist-скрининг крупного вывода ДО исполнения отсутствует** для этого диапазона. **Блокер на реализацию — compliance/product decision (regulatory implication, не чистая техника):** 1. Что делать с inline-флагнутым payout: держать в `PENDING_REVIEW` (ручной разбор) / авто-hold с авто-очисткой если чисто / снизить сам review-порог €25K→€5K? 2. Какие правила гнать inline (sanctions/watchlist — обязательно; structuring/velocity — дороже, оставить в batch?). 3. Порог inline-скрининга (€5K как AML-порог?). Технический план после решения: inline-хук в `request-bank-payout.ts`/`request-crypto-payout.ts` ПЕРЕД SEPA-AUTO-02/крипто-исполнением — синхронный sanctions/watchlist-скрин; при hit → status HOLD/PENDING_REVIEW вместо авто-payout; идемпотентность; batch остаётся backstop; TDD + i18n новых сообщений.
Author
Owner

Policy решена (CEO 2026-07-20): полный inline AML (все правила) перед исполнением payout. Готовый план реализации (дизайн изучен, worktree-разведка выполнена).

Архитектура (аддитивная, обратно совместимая — batch-путь scan.ts не ломается):

  1. Параметризовать 12 rule-функций checkX(since, opts?: { userId?: string }) (api/src/services/aml/rules/*.ts): при opts.userId заменять userId: { not: SYSTEM_USER_ID } на userId: opts.userId (single-user скрин). scan.ts вызывает без opts → без изменений.
  2. Экстракция persist-логики из runAmlScan (scan.ts:98-140: dedup по userId:ruleCode против OPEN/INVESTIGATING + amlAlert.create + risk-score recalc + авто-SAR для CRITICAL/HIGH) в общую persistAmlResults(results) — переиспользуется inline и batch (DRY).
  3. Новый screenUserPayoutInline(userId): Promise<{ flagged: boolean; alerts: RuleResult[] }> — гонит все применимые правила с {userId} за релевантные окна (Promise.all), persist-ит через (2).
  4. Хук в request-bank-payout.ts и request-crypto-payout.ts ПЕРЕД SEPA-AUTO-02 (request-bank-payout.ts:340) / крипто-исполнением: если payoutAmount >= LARGE_PAYOUT_THRESHOLD (€5K) → screenUserPayoutInline; при flagged → создавать payout со статусом PENDING_REVIEW (блок авто-исполнения) вместо PENDING. Batch-скан остаётся backstop.
  5. TDD (failing-first per rule + hook), Rule E mock-sync, Rule C AUDIT-комменты, i18n на 14 языков для нового user-facing сообщения о задержке вывода на проверку.

Почему не реализовано в этой сессии (named blocker, не усталость): это путь ВЫВОДА СРЕДСТВ (самый дорогой money/compliance). Правильная реализация = 12-файловый рефактор + persist-экстракция + 2 money-хука + полное покрытие. По L20 (cost-of-error = real money) вкатывать сырой AML-код в withdrawal-path наспех = риск заблокировать легитимные выводы или пропустить санкционные. Требует свежей выделенной сессии + CEO approval на merge (Rule C financial logic). Дизайн выше — готов к исполнению.

**Policy решена (CEO 2026-07-20): полный inline AML (все правила) перед исполнением payout. Готовый план реализации (дизайн изучен, worktree-разведка выполнена).** Архитектура (аддитивная, обратно совместимая — batch-путь scan.ts не ломается): 1. **Параметризовать 12 rule-функций** `checkX(since, opts?: { userId?: string })` (`api/src/services/aml/rules/*.ts`): при `opts.userId` заменять `userId: { not: SYSTEM_USER_ID }` на `userId: opts.userId` (single-user скрин). scan.ts вызывает без opts → без изменений. 2. **Экстракция persist-логики** из `runAmlScan` (`scan.ts:98-140`: dedup по userId:ruleCode против OPEN/INVESTIGATING + `amlAlert.create` + risk-score recalc + авто-SAR для CRITICAL/HIGH) в общую `persistAmlResults(results)` — переиспользуется inline и batch (DRY). 3. **Новый `screenUserPayoutInline(userId): Promise<{ flagged: boolean; alerts: RuleResult[] }>`** — гонит все применимые правила с `{userId}` за релевантные окна (Promise.all), persist-ит через (2). 4. **Хук в `request-bank-payout.ts` и `request-crypto-payout.ts`** ПЕРЕД SEPA-AUTO-02 (`request-bank-payout.ts:340`) / крипто-исполнением: если `payoutAmount >= LARGE_PAYOUT_THRESHOLD` (€5K) → `screenUserPayoutInline`; при `flagged` → создавать payout со статусом `PENDING_REVIEW` (блок авто-исполнения) вместо `PENDING`. Batch-скан остаётся backstop. 5. TDD (failing-first per rule + hook), Rule E mock-sync, Rule C AUDIT-комменты, i18n на 14 языков для нового user-facing сообщения о задержке вывода на проверку. **Почему не реализовано в этой сессии (named blocker, не усталость):** это путь ВЫВОДА СРЕДСТВ (самый дорогой money/compliance). Правильная реализация = 12-файловый рефактор + persist-экстракция + 2 money-хука + полное покрытие. По L20 (cost-of-error = real money) вкатывать сырой AML-код в withdrawal-path наспех = риск заблокировать легитимные выводы или пропустить санкционные. Требует свежей выделенной сессии + CEO approval на merge (Rule C financial logic). Дизайн выше — готов к исполнению.
Author
Owner

Часть 1/2 реализована и закоммичена (commit 3abce21, ветка feature/claude-sec-q2-1152-aml-inline).

api/src/services/aml/inline-screen.tsscreenUserPayoutInline(userId, now): прогоняет 8 применимых batch AML-правил за 72h-окно (widest rule window) и возвращает флаги только целевого пользователя. Чистая функция детекции, переиспользует правила as-is (ноль изменений rule-логики, scan.ts, money-path). 4 unit-теста green, tsc+eslint clean.

ponytail-ceiling помечен: O(window)-скан на payout (приемлемо — payout редкий, ≤3/user/day, pre-execution gate не hot-path); часть 2 может протолкнуть userId в WHERE правил для O(user).

Часть 2/2 (следующий инкремент, money-critical, требует CEO merge-approval — Rule C):

  1. Экстракция persistAmlResults(results) из scan.ts:82-210 (dedup + amlAlert.create + risk-score + авто-SAR + escalation + TG) в общий модуль; scan.ts и inline-screen переиспользуют (DRY).
  2. Хук в request-bank-payout.ts (перед SEPA-AUTO-02, :340) и request-crypto-payout.ts: если payoutAmount >= LARGE_PAYOUT_THRESHOLD → screenUserPayoutInline; при flagged → persist + статус PENDING_REVIEW (блок авто-исполнения). Batch остаётся backstop.
  3. TDD хука, Rule E mock-sync, i18n×14 нового user-facing сообщения о задержке на проверку.

Часть 2 меняет путь ВЫВОДА СРЕДСТВ — по Rule C требует explicit CEO approval на merge + code-reviewer + 7/7 CI.

**Часть 1/2 реализована и закоммичена** (commit 3abce21, ветка feature/claude-sec-q2-1152-aml-inline). `api/src/services/aml/inline-screen.ts` — `screenUserPayoutInline(userId, now)`: прогоняет 8 применимых batch AML-правил за 72h-окно (widest rule window) и возвращает флаги только целевого пользователя. Чистая функция детекции, переиспользует правила as-is (ноль изменений rule-логики, scan.ts, money-path). 4 unit-теста green, tsc+eslint clean. ponytail-ceiling помечен: O(window)-скан на payout (приемлемо — payout редкий, ≤3/user/day, pre-execution gate не hot-path); часть 2 может протолкнуть userId в WHERE правил для O(user). **Часть 2/2 (следующий инкремент, money-critical, требует CEO merge-approval — Rule C):** 1. Экстракция `persistAmlResults(results)` из `scan.ts:82-210` (dedup + amlAlert.create + risk-score + авто-SAR + escalation + TG) в общий модуль; scan.ts и inline-screen переиспользуют (DRY). 2. Хук в `request-bank-payout.ts` (перед SEPA-AUTO-02, :340) и `request-crypto-payout.ts`: если payoutAmount >= LARGE_PAYOUT_THRESHOLD → `screenUserPayoutInline`; при flagged → persist + статус PENDING_REVIEW (блок авто-исполнения). Batch остаётся backstop. 3. TDD хука, Rule E mock-sync, i18n×14 нового user-facing сообщения о задержке на проверку. Часть 2 меняет путь ВЫВОДА СРЕДСТВ — по Rule C требует explicit CEO approval на merge + code-reviewer + 7/7 CI.
Author
Owner

Реализовано и смержено (PR #1165, CI 8/8 green, 2 раунда code-review).

Слепая зона €5K–€25K закрыта: inline AML pre-screen перед авто-исполнением вывода.

  • aml/inline-screen.ts — screenUserPayoutInline: 12 payout-релевантных правил за окно пользователя, dismissal-suppression (кроме санкций/гео-риска).
  • aml/persist.ts — persistAmlResults вынесен из scan.ts (DRY, batch+inline).
  • request-bank-payout.ts/request-crypto-payout.ts — screen до SEPA-AUTO/крипто; флаг → PENDING_REVIEW; scheduled-канал тоже скринится (gate по Math.max(amount,maxAmount)).

Code-review поймал и закрыл: CRITICAL scheduled-bypass, HIGH недостающие правила, MEDIUM suppression + sanctions-ruleCode. Fail-closed на падение screen. 10957 API + 191 integration tests green.

Реализовано и смержено (PR #1165, CI 8/8 green, 2 раунда code-review). Слепая зона €5K–€25K закрыта: inline AML pre-screen перед авто-исполнением вывода. - `aml/inline-screen.ts` — screenUserPayoutInline: 12 payout-релевантных правил за окно пользователя, dismissal-suppression (кроме санкций/гео-риска). - `aml/persist.ts` — persistAmlResults вынесен из scan.ts (DRY, batch+inline). - `request-bank-payout.ts`/`request-crypto-payout.ts` — screen до SEPA-AUTO/крипто; флаг → PENDING_REVIEW; scheduled-канал тоже скринится (gate по Math.max(amount,maxAmount)). Code-review поймал и закрыл: CRITICAL scheduled-bypass, HIGH недостающие правила, MEDIUM suppression + sanctions-ruleCode. Fail-closed на падение screen. 10957 API + 191 integration tests green.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
europa-tech-srl/europatech#1152
No description provided.