Commit Graph

2630 Commits

Author SHA1 Message Date
Дмитрий 5be29671c9 feat(auth): фронт-виджет Yandex SmartCaptcha на регистрации с fallback на стаб без ключа (M-2) 2026-06-21 09:34:31 +03:00
Дмитрий ce8bddc7c0 fix(captcha): актуальный validate-URL Yandex SmartCaptcha smartcaptcha.cloud.yandex.ru (сверено по докам) 2026-06-21 09:15:20 +03:00
Дмитрий 90a202be4e feat(security): реальный Yandex SmartCaptcha драйвер самозаписи по CAPTCHA_DRIVER (M-2) 2026-06-21 09:02:31 +03:00
Дмитрий 03ff03a714 docs(приёмка): хэндофф сессии 3 — apiv1-rate/M-1/Раздел B + точка входа следующей 2026-06-21 08:42:20 +03:00
Дмитрий 44a536344c test(coverage): расширение охвата Раздел B №2-5 — границы reminder/final, терминальные пути оркестратора, G6 API edges 2026-06-21 08:41:03 +03:00
Дмитрий 5cdb2b488c test(supplier): regression-guard неизменности received_at при merge CSV-recovered сделки 2026-06-21 08:23:13 +03:00
Дмитрий cdfeb92b77 fix(security): fail-closed app-гейт админ-зоны по REMOTE_USER и allowlist — M-1 2026-06-21 08:11:02 +03:00
Дмитрий 39c6863826 feat(api): rate-limit публичного /api/v1/deals 120 в минуту на источник перед apikey 2026-06-21 08:10:22 +03:00
Дмитрий 33ea78bf7f docs(приёмка): хэндофф раунда 2 — что закрыто + точка входа следующей сессии
Раунд 2 закрыт (7 коммитов, всё локально-исполнимое по TDD). Хэндофф-док:
карта сделанного + оставшиеся owner-решения (M-1, M-2, apiv1-rate, tenants-RLS,
created_at TZ, #21 schema, Раздел B, прод-прогон) + контекст исполнителю
(db/ через терминал, observer-расстейдж, параллельные сессии, не пушено).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:07:15 +03:00
Дмитрий 32b5902a3f fix(digest): идемпотентность дайджеста новых сделок по сделке (N-4)
N-4: SendNewLeadsDigestJob выбирал сделки received_at>now()-30min без маркера
отправки → ручной/повторный прогон (R3b велит дёргать вручную) дублировал
письмо-дайджест. Окно «непересекается» только при ровно-30-мин прогонах.

Фикс без схемы: идемпотентность по сделке через Redis SETNX
(Cache::add 'digest_sent:<id>', TTL 1 сутки) — паттерн как rate-limit
ZeroBalancePausedMail. Уже отправленная сделка в дайджест повторно не входит.

TDD: тест «повторный прогон НЕ дублирует» (RED 2 письма → GREEN 1), сюит 5/5.
R3b DIGEST-ON + owner-decisions + NEW-статус обновлены (N-4 → закрыто).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:01:10 +03:00
Дмитрий 7aa5fb7505 docs(приёмка): шаг вечерней заморозки §20.2 в план №9 (cover-freeze)
Ревью раунда 1 (полнота): инвариант §20.2 не имел шага ни в одном плане.
Добавлен Шаг 9-4: вечерний BalancePreflightSweepJob @18:00 смотрит на ЗАВТРА
(тенант мёрзнет с «+» балансом), разморозка снимает только наши паузы
(paused_at >= frozen_at_was), ручные паузы клиента сохраняются. Код-факты
сверены по BalancePreflightSweepJob.php:62/70-85/97-108 + routes/console.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:55:26 +03:00
Дмитрий f62ccfd4ea docs(приёмка): статусы раунда-2 + вынос на решение владельца
- coverage-expansion-NEW.md: статус-баннер (N-1..N-8 закрыто/вынесено), закоммичен
  (был untracked).
- owner-decisions.md (новый): M-1 админ fail-open, M-2 капча Null, N-4 дайджест-дубль,
  tenants-RLS, apiv1-rate, created_at TZ, #21 schema header, Раздел B — то, что
  нельзя править молча (намеренные состояния / смена поведения / объём), с анализом
  и вариантами.
- реестр: раздел «Раунд 2» — F-CSV ДОзакрыт (форматтеры), карта закрытых находок.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:45:32 +03:00
Дмитрий 983e9a63f8 docs(приёмка): починка runbook-SQL по находкам N-1/N-2/N-3/N-5/N-7/N-8
R5 teardown/сверки (сверено по db/schema.sql):
- N-1 (CRITICAL): TD3 падал — supplier_lead_costs БЕЗ tenant_id (→ через deal_id,
  до deals) + DELETE FROM reminders по дропнутой таблице (убрано). Прежде вся
  транзакция откатывалась → тест-данные оставались на боевом.
- N-2: + DELETE pd_processing_log/tenant_operations_log (по tenant_id) + нота про
  orphan-таблицы инъекции без tenant_id (чистить по манифесту R2).
- N-3: нота — НЕ удалять глобальные auth_log/saas_admin_audit_log (разрыв
  hash-цепочки → F3 красный); per-tenant цепочки рвутся безопасно.
- N-8: F4 маржа — supplier_lead_costs.cost_kopecks→cost_rub + JOIN deals;
  lead_charges.price_kopecks→price_per_lead_kopecks (ревьюер ошибся, сверено).

R2: N-5 — предупреждение про snapshot:rebuild (DELETE без фильтра тенанта/транзакции,
снесёт слепок дня вкл. lkomega); по умолчанию backfill с явной --date.

R1: N-7 — предусловие PR2: гейт реквизитов (422 requisites_required) + preflight
баланса (409) ДО создания проектов через портал.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:42:40 +03:00
Дмитрий dc14afdedd docs(приёмка): поправки планов по находкам ревью (Татарстан/капча/lpimp/воронка/№23/append-only)
Закрыты doc-находки двух ревью-сессий (точность планов перед прод-прогоном):
- GAP-1: Татарстан 16→19 (16=Мордовия, конституц. порядок ст.65, НЕ ГИБДД) —
  свод, план №3 (C-2), PR2 (сноска: P5 [16]=Мордовия, не Татарстан). Сверено
  по RussianRegions.php.
- M-2/GAP-3: капча = NullCaptchaVerifier БЕЗУСЛОВНО (не только local) — план №18.
- lpimp-status: lpimp_ → 401 на биллинг/api-keys (не 403), 403 только на admin,
  /api/billing/charges читается — план №25.
- N-6: воронка статусов НЕ форсится (любой валидный slug, нет state-machine) — план №13.
- №23-tx: импорт пишет 1 нулевую historical_import строку; сверять баланс/lead_charges,
  не число balance_transactions — план №23.
- append-only: lead_charges защищён GRANT'ом (prod/crm_app_user), не триггером →
  на dev-superuser правка пройдёт (ложный GREEN); гонять на проде — план №12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:38:37 +03:00
Дмитрий a7286f6230 chore(frontend): убрать «Напоминания» из устаревшего комментария AppLayout
F-REMIND хвост (ревью раунда 2): комментарий currentPageTitle приводил
«Напоминания» как пример страницы вне sidebar — фича снята (G3). Оставлен
живой пример «Импорт данных».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:26:16 +03:00
Дмитрий 631d620ee6 fix(reports): защита от CSV/formula-инъекции в писателях-форматтерах (F-CSV-wide)
Ревью раунда 2: F-CSV шире — формула может уйти не только через DealExport, но
и через центральные писатели ОТЧЁТОВ (Managers/Sources/Billing/Deals × csv/xlsx).
Прежний фикс нейтрализовал в DealsExportProvider — неверный слой: он кормит и
JSON-формат, где апостроф портил данные (ReportJobControllerTest).

Перенос защиты в писатели:
  - CsvFormatter — CsvFormulaGuard::neutralizeCell (апостроф, числа не трогаются)
  - XlsxFormatter — опасные строки через setCellValueExplicit TYPE_STRING
    (Excel НЕ вычисляет формулу; XLSX опаснее — считает без предупреждения)
  - DealsExportProvider — откат к сырым данным (JSON больше не портится)
CsvFormulaGuard: + isDangerous()/neutralizeCell() — numeric-aware (ведущий «-»
числа не экранируется).

TDD: unit-тесты CsvFormatter + XlsxFormatter (load xlsx → datatype 's', не 'f').
DealExportController (OpenSpout, отдельный путь Сделок) — без изменений.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:23:42 +03:00
Дмитрий 48fccbf109 docs(приёмка): раунд 2 — новые находки + расширение охвата теста
CRITICAL N-1: скрипт teardown R5 не сработает на боевом
supplier_lead_costs без tenant_id + reminders дропнута падают транзакцию.
N-2 orphan-таблицы инъекции; N-4 дубль дайджеста; N-5 snapshot rebuild.
Раздел B — непокрытые код-пути CsvReconcile/заморозка/отчёты/impersonation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:21:22 +03:00
Дмитрий 3ba703d9c9 docs(приёмка): корпус приёмочного теста + поправка №15 + статусы реестра
F-CORPUS: ключевые документы приёмки liderra.ru лежали untracked — мастер-
хэндофф ссылался на отсутствующие в git файлы (битые ссылки в новом клоне).
Закоммичены: R0–R5 + stepbystep ранбуки, хартия, prod-logic-map, эфир-хэндофф,
imitation-checks-table, live-demo/ (эфир-плеер) + смежные specs/планы серий
f1-card/phase1/televizor/g1/g2 (решение владельца — «корпус + смежные»).

F-DELPROJ: пункт №15 checks-table → «удаление проекта со сделками запрещено
(422), сделки целы» (было неточно «сделки сохранены», сверено по
ProjectService::delete).

Реестр находок: статусы F-DEPTRAC/F-CSV/F-REMIND/F-DELPROJ/F-CORPUS → закрыто.
.gitleaks.toml: ранбуки приёмки добавлены в allowlist (синтетические тест-
телефоны, та же категория что plans/specs/audits).
live-demo HTML: stylelint --fix (#fff→#ffffff).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:48:50 +03:00
Дмитрий b2b84e0fe0 chore(frontend): убрать мёртвый тип 'reminder' и устаревший докблок
F-REMIND: фича «Напоминания» снята (G3, drop_reminders_table), но во фронте
остались следы. Убран осиротевший член типа NotificationEventKey 'reminder'
(api/auth.ts) — он нигде не рендерился (в EVENTS NotificationsTab его нет,
мёртвого переключателя не было) — и устаревшее упоминание «напоминания» в
докблоке DealDetailBody.vue. type-check + eslint чисты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:46:08 +03:00
Дмитрий 2ed4e30760 fix(export): защита выгрузок сделок от CSV/formula-инъекции
F-CSV: ячейки экспорта писались «как есть» — значение вроде =HYPERLINK(...)
или @SUM(...) в комментарии/контакте/городе при открытии файла в Excel/
LibreOffice исполнялось как формула (OWASP Formula Injection).

Новый App\Support\CsvFormulaGuard::neutralize() префиксует апострофом ячейку,
начинающуюся с = + - @ TAB CR. Применён к свободному тексту в:
  - DealExportController (телефон/источник/город/статус/комментарий)
  - DealsExportProvider  (телефон/контакт/статус/проект/менеджер)
Числовые колонки (id/суммы/даты) не трогаются — ведущий «-» там легитимен.
TenantChargesController НЕ уязвим: колонки enum(CHECK)/число/дата, свободного
текста нет.

TDD: 13 unit-тестов хелпера + feature-тест экспорта сделок + provider-тест.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:45:30 +03:00
Дмитрий 5e9ae82454 docs(notifications): актуализировать докблок дефолтов уведомлений
G2-B (19.06) флипнул new_lead.email false→true — дайджест по умолчанию ВКЛ.
Докблок NotificationService ссылался на устаревший дефолт (email:false) и
старую строку schema.sql:699. Поправлено на канон-схему (email:true).
Только комментарий, без изменения логики.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:44:01 +03:00
Дмитрий 5bb3816e23 refactor(impersonation): отправку письма о завершении сессии вынести в сервис
F-DEPTRAC: middleware ImpersonationContext зависел от слоя Mail
(ImpersonationEndedMail) — нарушение deptrac ruleset, блокировало ВСЕ
php-коммиты. Отправка письма + завершение сессии вынесены в новый
ImpersonationExpiryService (Service-слой, которому Mail разрешён,
идемпотентно). Middleware теперь зависит только от Service.

deptrac: 0 нарушений. TDD: 2 теста сервиса (письмо в очереди + идемпотентность).
phpstan-baseline.neon перегенерён под Pest $this-false-positives новых тестов
этой серии правок приёмки (level 5, composer stan = 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:43:16 +03:00
Дмитрий c20d27f8ab docs(приёмка): независимый ревью подготовки прод-прогона — 5 осей + новые находки
Read-only разбор: код-факты денег/изоляции/распределения сверены построчно.
Блокеры до старта: Татарстан=16 неверно (19), корпус untracked, капча-Null.
Новые HIGH вне реестра: админ fail-open (=F-T2), капча отключена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:25:14 +03:00
Дмитрий 5437ea6f6b docs(приёмка): F-DEPTRAC+F-CORPUS в реестр + 2 промта для свежих сессий
Реестр: F-DEPTRAC (Middleware→Mail блокирует php-коммиты, от G7-B),
F-CORPUS (корпус приёмки R0-R5/хартия/map/live-demo untracked).
Промт #1 — закрыть находки (F-DEPTRAC/F-CSV/F-REMIND/F-DELPROJ/F-CORPUS + комментарий).
Промт #2 — независимый критразбор подготовки приёмки перед прод-прогоном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:28:51 +03:00
Дмитрий e217103d3d docs(приёмка): мастер-хэндофф для прод-прогона + планы 24/25/26 + поправки
Мастер-документ точки входа для свежей сессии: тест на боевом liderra.ru,
порядок R1-R5, карта 26 планов, безопасность, эфир, вердикт GO/NO-GO.
Планы 24 помощь, 25 импер­сонизация, 26 публичный API.

Поправка: F-DIGEST снята — была ошибкой чтения устаревшего комментария,
не отставанием локалки (она байт-в-байт с боевым a119aba8). Канон-схема
db/schema.sql:796 new_lead.email:true — дайджест вкл по умолчанию. План 20
и реестр обновлены. Тесты — только на проде.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:47:29 +03:00
Дмитрий ca47f68be4 docs(приёмка): реестр находок + планы 18 онбординг и 21 напоминания убраны
Реестр находок (сверка свода с кодом под планы):
F-CSV экспорт без защиты от CSV-инъекции (безопасность, под фикс),
F-DELPROJ удаление проекта со сделками запрещено (свод неточен),
F-DIGEST дефолт email-дайджеста по схеме выключен,
F-PDF штатность PDF-формата не подтверждена,
F-REMIND фронт-хвосты снятой фичи напоминаний,
F1-F5 прежние прогонные (F3 закрыта).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:54:03 +03:00
Дмитрий 83f0b88d3e docs(приёмка): ещё 11 планов-отчётов — движок добит + UI-блок
Движок Фазы 3: 02 второй круг, 04 лимит/пауза под локом, 06 каналы/парсинг,
12 денежный аудит, 11 лента денег/калькуляторы, 15 сделки переживают удаление,
16 изоляция. UI-блок R3b: 19 гейт реквизитов+ИНН, 20 колокольчик+дайджест,
22 отчёты, 23 импорт CSV.

Расхождения свода с кодом помечены как находки под проверку:
15 удаление проекта со сделками запрещено а не сохраняется,
20 дефолт email-дайджеста по схеме выключен,
22 PDF-формат проверить на штатность.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:36:30 +03:00
Дмитрий b6109e1485 docs(приёмка): 11 планов-отчётов по пунктам приёмки liderra.ru в формате PR1
Пошаговые планы было→ожидали→стало с код-фактами file:line для пунктов:
PR2 проекты, PR3 балансы, 01 распределение CAP3, 03 каскад региона,
05 идемпотентность, 07 тройная запись, 08 тариф-ступень,
09 нехватка→откат, 10 два проекта одно списание,
13 сделки лента/карточка/статусы, 14 ручная/массовые/экспорт.

NB 14: защита экспорта от CSV-инъекции в коде не найдена — помечено как находка под проверку на прогоне.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:11:27 +03:00
Дмитрий 7c19b5aa9a docs(ПИЛОТ): снимок 19.06 — G1-G7 выкачены на боевой liderra.ru, метод/грабли/бэкапы 2026-06-19 19:29:33 +03:00
Дмитрий a119aba822 fix(G7-B): stray не-lpimp Bearer на sanctum-роуте → чистый 401 вместо 500 (нет таблицы PAT) 2026-06-19 17:35:53 +03:00
Дмитрий 602eaacd4a chore(G7-B): baseline Pest TestCall-ложноположительных для impersonation-тестов (0 продуктовых подавлений) 2026-06-19 17:25:28 +03:00
Дмитрий 21f8c1ff8d feat(G7-B): клиентская плашка impersonation + редирект/ключ в диалоге + leave 2026-06-19 17:09:59 +03:00
Дмитрий 61267dae92 feat(G7-B): leave из кабинета + impersonation-контекст в /api/auth/me + end-письмо 2026-06-19 17:04:48 +03:00
Дмитрий 8f1ded9b0d feat(G7-B): изоляция админ-зоны при impersonation + авто-истечение сессии 1 ч 2026-06-19 16:51:21 +03:00
Дмитрий aaeb396b1f feat(G7-B): guard impersonation + envelope машинного ключа на рабочих группах кабинета
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 16:44:42 +03:00
Дмитрий 0375c2a35a feat(G7-B): verify — session-takeover целевого юзера + выдача машинного ключа 2026-06-19 16:35:03 +03:00
Дмитрий 8118f852d5 feat(G7-B): письма impersonation код-согласие + завершение, wire в init 2026-06-19 16:25:49 +03:00
Дмитрий c3fd9e05b7 feat(G7-B): колонка session_token_hash под машинный ключ impersonation, schema v8.49 2026-06-19 16:21:32 +03:00
Дмитрий 4a077db3a5 docs(G7-B): план реализации двери impersonation + машинный ключ 2026-06-19 16:17:56 +03:00
Дмитрий f3892cfa93 docs(G7-B): спека двери impersonation + машинный ключ под ИИ + карта каналов 2026-06-19 16:09:07 +03:00
Дмитрий 1c0b432d8a docs(G7-B): хэндофф достройки impersonation — состояние брейншторма + промпт след. сессии
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:20:24 +03:00
Дмитрий eddad70d63 chore(G7-A): baseline Pest-ложноположительных в SupportRequestControllerTest
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:08:30 +03:00
Дмитрий 7ebe25385b feat(G7-A): экран «Помощь» (форма-заявка) + пункт меню + роут
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:04:17 +03:00
Дмитрий dd365b0a0e feat(G7-A): meta support-email + условный JivoSite в shell-blade
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:02:14 +03:00
Дмитрий b02ab1cadf feat(G7-A): POST /api/support-requests + тест (store+mail+валидация)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:00:12 +03:00
Дмитрий 0469994948 feat(G7-A): SupportRequestMail + шаблон письма
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:55:21 +03:00
Дмитрий 448633e59c feat(G7-A): модель SupportRequest
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:54:25 +03:00
Дмитрий 0826cb24dd feat(G7-A): таблица support_requests (schema + миграция, RLS)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:53:57 +03:00
Дмитрий 35e4f020b9 feat(G7-A): конфиг support.email + jivosite.widget_id
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:50:39 +03:00
Дмитрий 229e8e2459 docs(G7-A): план реализации клиентской «Помощь»
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:48:18 +03:00