[CL-1153] SEC-Q2: убрать non-atomic webhook dedup путь — только атомарный fail-closed #1157

Merged
andrei merged 2 commits from feature/claude-sec-q2-1153-dedup-nonatomic into master 2026-07-19 18:54:26 +00:00
Owner

Что сделано

  • api/src/controllers/webhook/dedup.ts: удалены legacy non-atomic пары isEventProcessed/markEventProcessed + NP/OB варианты и write-only in-memory fallback Map (fallbackProcessedIds, evictOldestEntries, cleanup-interval). Общая логика сведена в markKeyProcessedIfNew (Redis SET NX, fail closed). Остались только mark*EventProcessedIfNew + release*EventProcessed.
  • Тесты dedup.test.ts переведены на атомарный контракт (SET NX claim/duplicate/fail-closed/release+re-claim).
  • Моки dedup-модуля синхронизированы в 6 тест-файлах (Rule E).
  • Стейл-комменты в premium.routes.ts обновлены (isEventProcessed -> markEventProcessedIfNew).
  • Vault: docs/vault/modules/api-payments.md +1 строка (Rule G).

Refs #1153

Зачем

Non-atomic путь допускал TOCTOU-гонку двойного финансового исполнения между check и mark, а in-memory fallback тихо деградировал multi-instance dedup при падении Redis (PM2 cluster). После ухода всех хэндлеров на атомарный путь legacy-код остался мёртвым грузом с опасным контрактом: fallback Map стал write-only (никто не читал), т.е. защиты не давал вообще. GitNexus impact: LOW, 0 прод-callsites.

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

  • pnpm exec tsc --noEmit — clean (после prisma generate).
  • pnpm exec eslint src --max-warnings 0 — clean.
  • Полный pnpm exec vitest run в api: 867 files / 10940 tests passed, 0 failed.
  • Таргетные webhook-тесты: 7 файлов / 114 passed.

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

  • Redis-restart-с-потерей-данных + мгновенный replay провайдера теоретически даёт дубль (SET NX на пустом Redis вернёт OK). Это pre-existing поведение: fallback Map этот сценарий и раньше не закрывала (её никто не читал в атомарном пути). Митигация — Redis persistence на проде.
  • darwin-специфики нет, модуль платформо-независимый.
## Что сделано - `api/src/controllers/webhook/dedup.ts`: удалены legacy non-atomic пары `isEventProcessed`/`markEventProcessed` + NP/OB варианты и write-only in-memory fallback Map (`fallbackProcessedIds`, `evictOldestEntries`, cleanup-interval). Общая логика сведена в `markKeyProcessedIfNew` (Redis SET NX, fail closed). Остались только `mark*EventProcessedIfNew` + `release*EventProcessed`. - Тесты `dedup.test.ts` переведены на атомарный контракт (SET NX claim/duplicate/fail-closed/release+re-claim). - Моки dedup-модуля синхронизированы в 6 тест-файлах (Rule E). - Стейл-комменты в `premium.routes.ts` обновлены (`isEventProcessed` -> `markEventProcessedIfNew`). - Vault: `docs/vault/modules/api-payments.md` +1 строка (Rule G). Refs #1153 ## Зачем Non-atomic путь допускал TOCTOU-гонку двойного финансового исполнения между check и mark, а in-memory fallback тихо деградировал multi-instance dedup при падении Redis (PM2 cluster). После ухода всех хэндлеров на атомарный путь legacy-код остался мёртвым грузом с опасным контрактом: fallback Map стал write-only (никто не читал), т.е. защиты не давал вообще. GitNexus impact: LOW, 0 прод-callsites. ## План тестирования - `pnpm exec tsc --noEmit` — clean (после `prisma generate`). - `pnpm exec eslint src --max-warnings 0` — clean. - Полный `pnpm exec vitest run` в api: **867 files / 10940 tests passed, 0 failed**. - Таргетные webhook-тесты: 7 файлов / 114 passed. ## Где могу ошибаться - Redis-restart-с-потерей-данных + мгновенный replay провайдера теоретически даёт дубль (SET NX на пустом Redis вернёт OK). Это pre-existing поведение: fallback Map этот сценарий и раньше не закрывала (её никто не читал в атомарном пути). Митигация — Redis persistence на проде. - darwin-специфики нет, модуль платформо-независимый.
[CL-1153] SEC-Q2: удалить non-atomic webhook dedup путь, оставить только атомарный fail-closed
Some checks failed
CI / Contracts (pull_request) Has been cancelled
CI / Telegram Mini App (pull_request) Has been cancelled
CI / Python SDK (pull_request) Has been cancelled
CI / Secrets Scan (pull_request) Has been cancelled
CI / Prisma Migrate Gate (pull_request) Has been cancelled
CI / App (pull_request) Has been cancelled
React Doctor / React Doctor / App (pull_request) Has been cancelled
CI / API (pull_request) Has been cancelled
6a0cbe6dc8
Legacy пары isEventProcessed/markEventProcessed (+NP/OB варианты) допускали
TOCTOU-гонку двойного финансового исполнения и in-memory fallback, тихо
деградировавший multi-instance dedup при падении Redis. Прод-callsites: 0
(GitNexus impact LOW). Общая логика сведена в markKeyProcessedIfNew (SET NX,
fail closed, 503 + Retry-After у хэндлеров). Write-only fallback Map удалён.
Тесты переведены на атомарный контракт, моки синхронизированы (Rule E),
vault api-payments.md обновлён (Rule G).

Refs #1153
[CL-1153] ревью-фиксы: сохранить дословные Sentry-тексты по провайдерам, добить тест-гэпы
All checks were successful
CI / Contracts (pull_request) Successful in 2m54s
CI / Telegram Mini App (pull_request) Successful in 1m39s
CI / Secrets Scan (pull_request) Successful in 13s
CI / Python SDK (pull_request) Successful in 48s
CI / Prisma Migrate Gate (pull_request) Successful in 1m41s
React Doctor / React Doctor / App (pull_request) Successful in 2m22s
CI / API (pull_request) Successful in 17m43s
CI / App (pull_request) Successful in 19m9s
dee5960e57
MEDIUM из ревью: консолидация сменила текст Sentry/log-сообщений для NP/OB —
text-matching alert-правила могли молча отвалиться. Возвращены дословные
pre-1153 строки через DedupProvider {tag, short, long}; теги не менялись.
LOW: добавлены тесты already-claimed для NP/OB и ветка 'Redis client not
configured' (vi.resetModules + fresh import, assert captureMessage x3).

Refs #1153
Author
Owner

Merge evidence (Rule 103.1 + Rule C):

  • local-review: Agent(code-reviewer) — 0 CRITICAL/HIGH; 1 MEDIUM (Sentry message-text drift) + 2 LOW (тест-гэпы) закрыты коммитом dee5960e (дословные pre-1153 тексты через DedupProvider, +3 теста).
  • ci-status: 8/8 green на dee5960e (API, App, Contracts, Python SDK, Secrets Scan, Telegram Mini App, Prisma Migrate Gate, React Doctor).
  • Полный api vitest: 10940 passed / 0 failed; tsc + eslint clean.
  • GitNexus impact: LOW, 0 прод-callsites удалённых экспортов.
  • CEO approve на merge: явный выбор «Мержи по зелёному CI» в сессии 2026-07-19.
**Merge evidence (Rule 103.1 + Rule C):** - local-review: Agent(code-reviewer) — 0 CRITICAL/HIGH; 1 MEDIUM (Sentry message-text drift) + 2 LOW (тест-гэпы) закрыты коммитом dee5960e (дословные pre-1153 тексты через DedupProvider, +3 теста). - ci-status: 8/8 green на dee5960e (API, App, Contracts, Python SDK, Secrets Scan, Telegram Mini App, Prisma Migrate Gate, React Doctor). - Полный api vitest: 10940 passed / 0 failed; tsc + eslint clean. - GitNexus impact: LOW, 0 прод-callsites удалённых экспортов. - CEO approve на merge: явный выбор «Мержи по зелёному CI» в сессии 2026-07-19.
andrei merged commit 541befd8f6 into master 2026-07-19 18:54:26 +00:00
andrei deleted branch feature/claude-sec-q2-1153-dedup-nonatomic 2026-07-19 18:54:27 +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!1157
No description provided.