Дмитрий
5df34a61eb
style+done(p2): pint formatting + P2 plan DONE marker
2026-05-22 18:53:11 +03:00
Дмитрий
57d84c6ea3
feat(audit): Task 7 — log all SupplierWebhookController outcomes to webhook_log
...
- schema v8.29: webhook_log +source/status/lead_id/ip_address/created_at,
tenant_id nullable, +idx_webhook_log_status
- migration 2026_05_22_000002_webhook_log_supplier_columns
- SupplierWebhookController::logSupplierWebhook() private helper (silent/non-throwing)
called at 4 exit points: rejected_secret/rejected_ip/rate_limited/received
- SupplierWebhookLoggingTest: 4 tests 17 assertions GREEN
- Regression SupplierWebhookTest: 13/13 GREEN
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
2026-05-22 18:53:10 +03:00
Дмитрий
451a2944f7
fix(http): timestamp validation ±24h для partition guard (Plan 2.6 #iii)
...
Закрывает CV.11 audit WARN minor #5 (Carbon::createFromTimestamp(time) без
range guard → INSERT CRASH "no partition of relation deals found for row"
для timestamp вне текущего месячного окна deals_2026_MM).
Изменение: SupplierWebhookController::receive — добавлено min/max constraint
на 'time' = [now-24h, now+24h] unix-timestamp. Timestamp вне окна → 422
ValidationException.
±24h: покрывает retry-задержки поставщика (network-сбой) + clock-drift серверов;
шире окно (±48h+) = риск partition-промаха на стыке месяцев (нужен Plan 5
partition cron).
TDD: +3 теста (-2 days → 422; +2 days → 422; -6h → 202).
Regression-fix: existing test 'inserts supplier_lead row' использовал hardcoded
'time' => 1703781939 (Dec 28 2023) — теперь out-of-window. Заменено на time().
phpstan-baseline: postJson() count: 8 → 11 (+3 от Task 3 тестов).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 23:11:26 +03:00
Дмитрий
f78a85595c
fix(http): IP allowlist fail-closed в production env (Plan 2.6 #ii)
...
Закрывает CV.11 audit WARN #5 (пустой supplier_ip_allowlist '[]' = fail-open
на production — любой IP пропускается).
Изменение: SupplierWebhookController::verifyIpAllowlist — пустой allowlist
возвращает true только если env != production. На production пустой allowlist
блокирует (404). На dev/testing fail-open сохраняется (для localhost development).
TDD: +2 теста (production env empty → 404; testing env empty → 202).
Inline-warning header обновлён.
phpstan-baseline: count: 6 → 8 (postJson() Pest TestCall PhpDoc-quirk).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 23:08:16 +03:00
Дмитрий
e41c8f5aef
feat(http): SupplierWebhookController — platform-wide /api/webhook/supplier/{secret}
...
Defense-in-depth: secret (≥32 chars system_setting) + IP allowlist (CIDR).
Несовпадение → 404. UNIQUE vid → 200 OK на дубль (idempotency).
Тесты пока FAIL (route регистрируется в Task 7 — пишем "красные" тесты заранее
для TDD-цикла).
Spec §5.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 19:38:18 +03:00