[CL-1176] E2E money-путей: 3 латентных ledger-бага (auto-match NUMERIC, regulatoryRegime default, amount-constraint) #1186

Merged
andrei merged 1 commit from fix/cl-1176-e2e-latent-ledger-bugs into master 2026-07-22 15:45:05 +00:00
Owner

Что сделано

Третий адверсариальный проход: синтетический ПОЛНЫЙ P2P-цикл (SELL → BUY → auto-match → escrow REFUNDED → unwind) на эфемерной копии prod-схемы (все 250 миграций), без моков money-путей. Первое реальное исполнение ledger-кода поймало 3 латентных бага:

  1. auto-match totalPrice=0 — pg driver adapter отдаёт NUMERIC из $queryRaw строками; counter.pricePerShare шёл в mulEur() без toNum(), isFinite-guard превращал результат в 0. Каждый матч записал бы trade с totalPrice=0: продавец получает 0, покупатель уже дебетован полной суммой. Фикс: toNum() в auto-match.ts, honest-тип CounterOrderRow (number | string), red-green тест auto-match.raw-numeric.test.ts (НЕ мокает decimal.js — замоканный mulEur прятал баг).
  2. transactions.regulatoryRegime NOT NULL без default — ~60 мест transaction.create вне travel-rule путей не пишут колонку: любая ledger-запись падала null constraint violation. Миграция 20260722150000_unblock_ledger_writes: SET DEFAULT '{}' + @default([]) в схеме.
  3. check_transaction_amount_positive (amount>0) несовместим с ledger-семантикой: ≥13 мест пишут дебеты отрицательными (payouts, buyback, exchange, refund clawback, escrow unwind). Миграция: constraint → amount <> 0 (знак несёт направление).

Opt-in E2E-тест закреплён: revert.e2e.int.test.ts (гейт E2E_DATABASE_URL, снимает глобальные prisma/config-моки через vi.unmock; в юнит-прогонах и CI — skip). Vault: api-p2p.md.

Зачем

Все три бага латентны ТОЛЬКО потому, что prod pre-launch (transactions пуста, P2P-активности 0). Первый реальный пользовательский поток — покупка, payout, P2P-матч — упал бы или (хуже) тихо записал трейд на 0 с потерей денег покупателя. Юнит-тесты слепы к этому классу: prismaMock не знает DB constraints, замоканный mulEur коэрцит строки.

План тестирования

  • E2E: 3 зелёных прогона (после ручных ALTER, повторный на той же БД — перезапускаемость, после prisma migrate deploy чистой миграции). Точная сверка каждой ноги: продавец 1000→1980→1000, покупатель 2000→1000→2000, система 0→20→0, basis 700 восстановлен, W15-B1.5 zero-basis, 3 reversing-строки ledger, идемпотентность already-applied, ордер не реанимирован (#1176).
  • Red-green: auto-match.raw-numeric.test.ts красный до фикса (expected +0 to be 500), зелёный после.
  • Полный сьют: 10996 passed / 0 failed; tsc чист; eslint --max-warnings 0 чист; prisma validate ок; migrate deploy на эфемерной БД чист.

Где могу ошибаться

  • Rule 118: схема Prisma затронута (@default([])) — требует твоего sign-off, сам НЕ мержу.
  • Выбор amount <> 0 vs «переписать 13+ мест на положительные суммы + направление типом»: взял меньший дифф, совместимый с существующим кодом. Если хочешь строгий положительный ledger — это отдельный большой рефакторинг.
  • regulatoryRegime default [] означает «регуляторный режим не проставлен» для всех обычных транзакций — если compliance-логика где-то будет считать пустой массив «нарушением», надо будет заполнять на write-путях.
  • E2E использует PLATFORM_FEE_RATE=0.02 внутри теста; если прод-ставка иная, числа теста не зависят от прод-env (тест самодостаточен).
## Что сделано Третий адверсариальный проход: синтетический ПОЛНЫЙ P2P-цикл (SELL → BUY → auto-match → escrow REFUNDED → unwind) на эфемерной копии prod-схемы (все 250 миграций), без моков money-путей. Первое реальное исполнение ledger-кода поймало 3 латентных бага: 1. **auto-match totalPrice=0** — pg driver adapter отдаёт NUMERIC из $queryRaw строками; `counter.pricePerShare` шёл в `mulEur()` без `toNum()`, isFinite-guard превращал результат в 0. Каждый матч записал бы trade с totalPrice=0: продавец получает 0, покупатель уже дебетован полной суммой. Фикс: `toNum()` в `auto-match.ts`, honest-тип `CounterOrderRow` (`number | string`), red-green тест `auto-match.raw-numeric.test.ts` (НЕ мокает decimal.js — замоканный mulEur прятал баг). 2. **transactions.regulatoryRegime NOT NULL без default** — ~60 мест `transaction.create` вне travel-rule путей не пишут колонку: любая ledger-запись падала null constraint violation. Миграция `20260722150000_unblock_ledger_writes`: `SET DEFAULT '{}'` + `@default([])` в схеме. 3. **check_transaction_amount_positive (amount>0)** несовместим с ledger-семантикой: ≥13 мест пишут дебеты отрицательными (payouts, buyback, exchange, refund clawback, escrow unwind). Миграция: constraint → `amount <> 0` (знак несёт направление). Opt-in E2E-тест закреплён: `revert.e2e.int.test.ts` (гейт `E2E_DATABASE_URL`, снимает глобальные prisma/config-моки через `vi.unmock`; в юнит-прогонах и CI — skip). Vault: `api-p2p.md`. ## Зачем Все три бага латентны ТОЛЬКО потому, что prod pre-launch (transactions пуста, P2P-активности 0). Первый реальный пользовательский поток — покупка, payout, P2P-матч — упал бы или (хуже) тихо записал трейд на 0 с потерей денег покупателя. Юнит-тесты слепы к этому классу: prismaMock не знает DB constraints, замоканный mulEur коэрцит строки. ## План тестирования - E2E: 3 зелёных прогона (после ручных ALTER, повторный на той же БД — перезапускаемость, после `prisma migrate deploy` чистой миграции). Точная сверка каждой ноги: продавец 1000→1980→1000, покупатель 2000→1000→2000, система 0→20→0, basis 700 восстановлен, W15-B1.5 zero-basis, 3 reversing-строки ledger, идемпотентность already-applied, ордер не реанимирован (#1176). - Red-green: `auto-match.raw-numeric.test.ts` красный до фикса (`expected +0 to be 500`), зелёный после. - Полный сьют: 10996 passed / 0 failed; tsc чист; eslint --max-warnings 0 чист; prisma validate ок; migrate deploy на эфемерной БД чист. ## Где могу ошибаться - **Rule 118**: схема Prisma затронута (`@default([])`) — требует твоего sign-off, сам НЕ мержу. - Выбор `amount <> 0` vs «переписать 13+ мест на положительные суммы + направление типом»: взял меньший дифф, совместимый с существующим кодом. Если хочешь строгий положительный ledger — это отдельный большой рефакторинг. - regulatoryRegime default `[]` означает «регуляторный режим не проставлен» для всех обычных транзакций — если compliance-логика где-то будет считать пустой массив «нарушением», надо будет заполнять на write-путях. - E2E использует PLATFORM_FEE_RATE=0.02 внутри теста; если прод-ставка иная, числа теста не зависят от прод-env (тест самодостаточен).
[CL-1176] E2E-проход money-путей: 3 латентных ledger-бага (auto-match NUMERIC-строка, regulatoryRegime default, amount-constraint)
All checks were successful
CI / Telegram Mini App (pull_request) Successful in 2m2s
CI / Python SDK (pull_request) Successful in 27s
CI / Contracts (pull_request) Successful in 2m51s
CI / Secrets Scan (pull_request) Successful in 20s
CI / Prisma Migrate Gate (pull_request) Successful in 1m48s
CI / API (pull_request) Successful in 18m3s
CI / App (pull_request) Successful in 21m29s
7ba69bd966
Синтетический полный P2P-цикл (SELL→BUY→auto-match→REFUNDED→unwind) на
копии prod-схемы впервые исполнил money-путь без моков и поймал:
1) auto-match: pg driver adapter отдаёт NUMERIC из $queryRaw строками;
   counter.pricePerShare без toNum() валил mulEur() в 0 (isFinite-guard) —
   каждый матч писал бы trade с totalPrice=0 (продавцу 0, покупатель уже
   дебетован). Фикс + honest тип CounterOrderRow + red-green тест.
2) transactions.regulatoryRegime NOT NULL без default — ~60 мест
   transaction.create вне travel-rule не пишут колонку: любая ledger-запись
   падала бы. Миграция: SET DEFAULT '{}'.
3) check_transaction_amount_positive (amount>0) против >=13 мест кода,
   пишущих дебеты отрицательными (payouts/buyback/exchange/unwind).
   Миграция: constraint -> amount<>0.
Всё латентно только потому, что prod pre-launch: transactions пуста.
Opt-in E2E-тест закреплён (гейт E2E_DATABASE_URL, в CI skip).
andrei merged commit 66bd8137ee into master 2026-07-22 15:45:05 +00:00
andrei deleted branch fix/cl-1176-e2e-latent-ledger-bugs 2026-07-22 15:45:06 +00:00
Sign in to join this conversation.
No reviewers
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!1186
No description provided.