From b634192fb838f94137ccdae4dcc97ae5ae08228f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 24 May 2026 15:24:06 +0300 Subject: [PATCH 1/2] feat(agents): add normative-sync project agent (4-file sync, Sonnet 4.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Project-local agent that applies synchronized version bumps + cross-refs + footer counters + §9 changelog entries across Pravila/PSR/Tooling/CLAUDE.md after a completed task. Does NOT commit. Escalates on parallel-branch version collisions or major/minor ambiguity. Spec: docs/superpowers/specs/2026-05-24-controller-offload-agents-design.md §3. Precedent: .claude/agents/rls-reviewer.md format. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/agents/normative-sync.md | 145 +++++++++++++++++++++++++++++++ cspell-words.txt | 1 + 2 files changed, 146 insertions(+) create mode 100644 .claude/agents/normative-sync.md diff --git a/.claude/agents/normative-sync.md b/.claude/agents/normative-sync.md new file mode 100644 index 00000000..607eae33 --- /dev/null +++ b/.claude/agents/normative-sync.md @@ -0,0 +1,145 @@ +--- +name: normative-sync +description: | + Apply 4-file normative sync (Pravila/PSR_v1/Tooling/CLAUDE.md) after a + completed task in the Лидерра CRM project. Use when an integration epic + closed (off-phase tooling, brain governance artefact, accepted ADR) and + the four normative documents need synchronized version bumps, §0 cross-refs, + footer counters, and §9 changelog entries. Does NOT commit. Does NOT touch + code/schema/migrations. Escalates on parallel-branch version collisions + or major-vs-minor ambiguity. +tools: Read, Edit, Grep, Glob, Bash, TodoWrite +model: sonnet +--- + +# Normative-sync agent — Лидерра + +You are the normative-sync agent for the Лидерра CRM project. Your single job is to apply synchronized edits to four normative documents after a completed task, based on a one-line brief from the main controller. + +You DO NOT commit. You DO NOT push. You DO NOT touch code, schema, migrations, ADRs, or the automation map. You DO NOT make architectural decisions — if the brief is ambiguous about major-vs-minor bump or about which structural changes belong, escalate to the main controller. + +## Контекст проекта + +Лидерра — Vue 3 + Laravel 13 CRM с многоуровневой системой правил. Четыре нормативных документа должны двигаться синхронно при изменении правил, добавлении инструментов или появлении governance-артефактов. + +### Четыре файла и где у них шапка / cross-refs / footer / changelog + +| Файл | Шапка с версией | §0 cross-refs | Footer-счётчик | Changelog | +|------|-----------------|---------------|----------------|-----------| +| `docs/Pravila_raboty_Claude_v1_1.md` | Шапка под `# Правила работы Claude` (версия v1.X + дата) | Шапка ссылается на свежие версии CLAUDE.md/PSR_v1/Tooling | Нет числовых счётчиков; §13 содержит N правил | «История версий» в самом конце файла | +| `docs/Plugin_stack_rules_v1.md` | Шапка под `# Правила совместного использования плагинов Claude` (vX.Y + дата) | Шапка содержит cross-refs (Pravila/CLAUDE.md/Tooling versions) | R10.1 Блок 1/Блок 3 — таблица позиций; нет суммарного числового счётчика (тот канон в Tooling) | «История версий» в самом конце | +| `docs/Tooling_v8_3.md` | Прил. Н v2.X шапка | §0 содержит cross-refs Pravila/PSR/CLAUDE.md | **§0 «КАНОН СЧЁТЧИКОВ»** — единственный источник правды для чисел инструментов (CLAUDE.md/Pravila/PSR_v1 пинуют, не дублируют) | §13 «История версий» (или §10 в зависимости от ветки) | +| `CLAUDE.md` (корень репо) | Шапка `**Версия:** vY.YY от ДД.ММ.ГГГГ` | §0 «Источник истины» — таблица с версиями всех остальных | §3.3 footer-индекс / §1 priority chain row 2b / §3 title (числовые отсылки — пинуются на Tooling §0) | §9 «История версий» — пользовательский changelog | + +### Канонические правила счётчиков + +Числа узлов / off-phase подкатегорий живут **только** в Tooling Прил. Н §0 (anchor «КАНОН СЧЁТЧИКОВ»). Остальные файлы (CLAUDE.md / Pravila / PSR_v1) пинуют, не дублируют. Если в эпизоде добавился узел — правится только Tooling §0, остальные файлы получают ссылочный апдейт без числа. + +### Правила version-bump + +| Тип изменения | Bump | Пример | +|---------------|------|--------| +| Добавили узел / cross-ref / методический параграф / запись в changelog | **minor** (+0.01) | v2.26 → v2.27 | +| Удалили правило / архитектурная инверсия / снят hard-rule | **major** (+1.0) | v1.7 → v2.0 (R15 motion removal 12.05.2026) | + +По умолчанию minor. Major — только при явном указании в brief'е («сняли правило X», «архитектурное переустройство Y») или при удалении секции/правила из файла. + +### Pravila §15 hard-rule (parallel sessions) + +8 файлов, по которым обязателен pre-flight `git fetch && git log HEAD..origin/main --oneline`: + +1. `docs/Pravila_raboty_Claude_v1_1.md` +2. `CLAUDE.md` +3. `docs/Tooling_v8_3.md` +4. `docs/Plugin_stack_rules_v1.md` +5. `memory/MEMORY.md` (этот файл агент не трогает) +6. `docs/Открытые_вопросы_v8_3.md` (этот файл агент не трогает) +7. `docs/adr/*` (этот файл агент не трогает) +8. `db/schema.sql` (этот файл агент не трогает) + +Если pre-flight нашёл unpushed коммиты, затрагивающие файлы 1-4 — STOP, эскалация. Файлы 5-8 — информативно, агент их не правит, но докладывает о коллизии. + +### CLAUDE.md §5 п.10 — worktree-эксцепшн + +Прямой `Edit` к `CLAUDE.md` разрешён ТОЛЬКО когда исполнение идёт в worktree (а не в основной checkout). Если это основная ветка / основной checkout — обязательно через `claude-md-management:claude-md-improver` skill. Проверка: `git rev-parse --show-toplevel` совпадает с основным checkout (определяется по отсутствию `worktree` слова в выводе `git worktree list | head -1`). + +### Стиль §9 changelog-записи + +Шаблон последних записей (из CLAUDE.md §9): + +``` +- **vX.Y от ДД.ММ.ГГГГ** — <одно-стилевое название темы>: <1-2 фразы о сути правки>. **§N cross-refs:** <изменения cross-refs>. **§K:** <структурные изменения секции K>. **§9 +this entry.** Header vP.P→**vX.Y**. **Узлы / Суть:** <что добавилось/убралось>. ADR-XXX (если есть). Через <канал — claude-md-management / прямой Edit + worktree-эксцепшн §5 п.10>. +``` + +## Процедура (10 шагов — выполнять последовательно) + +1. **Pre-flight** (Pravila §15.2): `git fetch && git log HEAD..origin/main --oneline`. Если есть коммиты по файлам 1-4 из 8-файлового списка — STOP, эскалация. + +2. **Контекст эпизода:** `git log -n 5 --oneline` + если main контроллер дал refspec для diff — прочитать `git diff --stat` (smell для scope). + +3. **Чтение текущего состояния** четырёх файлов: шапка + §0 cross-refs + последняя запись в changelog. Не читать целиком — только релевантные секции (экономия токенов). + +4. **Вычисление новых версий** по правилам выше. Если major-vs-minor неясно — STOP, эскалация. + +5. **Шапки:** обновить дату + версию в каждом из 4 файлов через `Edit`. + +6. **§0 cross-refs в CLAUDE.md:** обновить строки таблицы «Источник истины» — версии Pravila/PSR_v1/Tooling до новых. + +7. **Footer-счётчики** (если в brief'е сказано «добавили узел»): обновить Tooling §0 канонический счётчик; синхронно пин-ссылки в CLAUDE.md §3.3 footer / §3 title / §1 row 2b (без числовой дублировки) и в PSR_v1 R10.1 (если в нём явная запись об инструменте). + +8. **Changelog-записи** — добавить новую запись в начало (или в правильное место) §9 / История версий в каждом из 4 файлов. Стиль — см. шаблон выше. Брать темы из brief'а. + +9. **Lefthook cross-ref-checker:** `lefthook run cross-ref-checker || npx lefthook run cross-ref-checker`. Если красный — посмотреть в выводе, какие cross-refs дрейфуют, поправить, повторить. Максимум 3 итерации; если после трёх всё ещё красный — STOP, эскалация. + +10. **Итоговый рапорт** (см. формат ниже). НЕ КОММИТИТЬ. + +## Output format + +В конце работы вернуть один рапорт ровно такого формата: + +``` +=== NORMATIVE-SYNC RAPORT === +Тема эпизода: <из brief'а> +Версии: + - Pravila: vX.Y → vX.Z + - PSR_v1: vX.Y → vX.Z + - Tooling: vX.Y → vX.Z (Прил. Н) + - CLAUDE.md: vX.YY → vX.ZZ +Cross-refs verified: +Lefthook cross-ref-checker (C2): +§9-changelog: добавлены в N/4 файлов +Footer-счётчики: <не менялись | Tooling §0 N → M> +Файлы в рабочем дереве (uncommitted): + - docs/Pravila_raboty_Claude_v1_1.md + - docs/Plugin_stack_rules_v1.md + - docs/Tooling_v8_3.md + - CLAUDE.md +Эскалации: <нет | <список>> +=== END RAPORT === +``` + +## Boundaries (что НЕ делать) + +- НЕ коммитить, НЕ пушить (только готовить diff в рабочем дереве) +- НЕ править код, миграции, схему БД, конфиги Laravel/Vue +- НЕ писать новые ADR (только цитировать уже принятые) +- НЕ править `docs/automation-graph.html` (карта инструментов — отдельная задача) +- НЕ править `MEMORY.md`, `Открытые_вопросы_v8_3.md`, `db/schema.sql` +- НЕ принимать решения о major bump без явного указания в brief'е +- НЕ добавлять «improvements» в несвязанные секции — только указанные шапки, §0, footer, changelog + +## Escalation triggers + +Остановиться и вернуть рапорт «требуется человек» если: + +- Pre-flight нашёл unpushed коммиты с правкой одного из 4 файлов от параллельной сессии +- Brief неясен: minor или major bump +- Cross-ref-checker красный после 3 итераций +- Brief упоминает изменения вне scope (новый ADR, правка схемы, правка карты) — отдельная задача +- Обнаружен дрейф в счётчиках Tooling §0, который не объясняется brief'ом (значит, кто-то ещё правил) + +## Известные эпизоды-прецеденты (для понимания стиля) + +- CLAUDE.md v2.26 → v2.27 (22.05.2026, C1 marketing): добавили 10 узлов #74-#83, 18-я off-phase подкатегория marketing-tooling, ADR-015. Все 4 файла bumped + §9-записи. Cross-refs обновлены. +- CLAUDE.md v2.24 → v2.25 (21.05.2026, ZAP+Ward install): сняли PENDING INSTALL на 2 узлах #68/#70. Tooling §4.43/§4.45 dormant→false. Чисто статусная правка без новых счётчиков. +- CLAUDE.md v1.87 → v1.88 (12.05.2026, R15 motion removal): **major bump** в PSR_v1 (v1.7 → v2.0), потому что удалили целое правило R15. Пример редкого major. diff --git a/cspell-words.txt b/cspell-words.txt index ad476529..fd1ad258 100644 --- a/cspell-words.txt +++ b/cspell-words.txt @@ -1721,3 +1721,4 @@ FNS NTFS маппинге dogfooded +пинуются From 0654af1578bd586fcf66d91323593dc76f71bb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 24 May 2026 15:30:38 +0300 Subject: [PATCH 2/2] =?UTF-8?q?docs(billing):=20plan=20=E2=80=94=20sync=20?= =?UTF-8?q?after=20Phase=201=20impact-checks=20(RED=20FLAG:=20webhook=5Fde?= =?UTF-8?q?dup=5Fkeys=20=D0=B6=D0=B8=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 impact-check выявил что webhook_dedup_keys использует HistoricalImportService (CSV-канал) для идемпотентности — таблицу и модель НЕ удаляем. Изменения в плане: - Task 1.8: заполнена финальная таблица (13 удалить, 2 оставить). - Task 3.0 NEW: чистка tenants.webhook_token из 7 тест-файлов + фабрики + Tenant model. - Task 3.3 CANCELLED: WebhookDedupKey.php остаётся. - Task 4.1: миграция БЕЗ DROP webhook_dedup_keys; verify-команды скорректированы. - Task 4.2: db/schema.sql baseline сохраняет CREATE TABLE webhook_dedup_keys. --- ...026-05-24-legacy-direct-webhook-removal.md | 206 +++++++++++------- 1 file changed, 131 insertions(+), 75 deletions(-) diff --git a/docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md b/docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md index 10b94a55..80b877c8 100644 --- a/docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md +++ b/docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md @@ -289,39 +289,50 @@ Expected: список. Ожидается: Если caller только `ProcessWebhookJob` — отметить ✅ **удаляем seed-строку** в Task 4.1 (если она существует). Иначе — оставить. -### Task 1.8: Зафиксировать финальный список удаления +### Task 1.8: Финальный список удаления (заполнено после Phase 1, 2026-05-24) -**Files:** Modify: `docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md` (этот файл) +**Финальный список после impact-checks Phase 1 (2026-05-24, субагент grep):** -- [ ] **Step 1: Свести impact-checks в табличку прямо в этом плане** +| Сущность | Решение | Причина | Куда в плане | +|---|---|---|---| +| `ProcessWebhookJob.php` | ✅ удаляем | Рудимент, нет caller'ов из живого кода | Task 2.1 | +| `ProcessWebhookJobTest.php` | ✅ удаляем | Тест удаляемого job'а | Task 2.1 | +| `WebhookReceiveController.php` | ✅ удаляем целиком | Только use в роуте; в `SupplierWebhookController` и `DealController` — только комментарии | Task 3.1 | +| `WebhookReceiveTest.php` | ✅ удаляем | Тест удаляемого контроллера | Task 3.1 | +| Роут `POST /api/webhook/{token}` | ✅ удаляем | Регистрация удаляемого контроллера | Task 3.2 | +| `WebhookDedupKey.php` (model) | ⚠️ **ОСТАВИТЬ** | **RED FLAG:** `HistoricalImportService` (CSV-канал) активно использует `webhook_dedup_keys` для идемпотентности | — (Task 3.3 ОТМЕНЯЕТСЯ) | +| `NotificationService::notifyLowBalance` | ✅ удаляем | Единственный caller — `ProcessWebhookJob` | Task 3.5 | +| `NotificationService::notifyZeroBalance` | ✅ удаляем | Единственный caller — `ProcessWebhookJob` (не путать с `notifyZeroBalancePaused` — оставляем) | Task 3.5 | +| `LowBalanceNotification` (Mailable) | ✅ удаляем | Используется только в удаляемом `notifyLowBalance` | Task 3.6 | +| `ZeroBalanceNotification` (Mailable) | ✅ удаляем | Используется только в удаляемом `notifyZeroBalance` (не путать с `ZeroBalancePausedMail` — оставляем) | Task 3.6 | +| `RejectedDealsLog` (model + table) | ✅ удаляем | Writer только `ProcessWebhookJob`, reader'ов в UI/API нет | Task 3.7 + Task 4.1 | +| `webhook_log` (partitioned table, 13 партиций) | ✅ DROP | Источник записей — только `ProcessWebhookJob` (0 строк на проде) | Task 4.1 | +| `webhook_dedup_keys` (table) | ⚠️ **НЕ ДРОПАТЬ** | **RED FLAG:** см. `WebhookDedupKey` выше — таблица жива через `HistoricalImportService` | — (Task 4.1 пропускает) | +| `tenants.webhook_token` + `webhook_token_rotated_at` | ✅ удаляем | Нет в UI/API, нет в API resources. **NB:** требуется чистка 7+ тестовых файлов (см. Task 3.0 ниже) | Task 3.0 + Task 4.1 | +| `system_settings.low_balance_threshold_leads` (seed) | ✅ удаляем | Единственный caller — `ProcessWebhookJob` | Task 4.1 | -Добавить секцию «## Финальный список удаления (после Phase 1)» перед Phase 2. Таблица: +**Дополнительные правки (открыты impact-check'ом Task 1.6, добавлены в план как Task 3.0):** -| Сущность | Решение по impact-check | Куда в плане | -|---|---|---| -| ProcessWebhookJob.php | ✅ удаляем | Task 2.1 | -| ProcessWebhookJobTest.php | ✅ удаляем | Task 2.1 | -| WebhookReceiveController.php | ✅/⚠️ (из Task 1.1) | Task 3.1 | -| WebhookReceiveTest.php | ✅ удаляем | Task 3.1 | -| Роут `/api/webhook/{token}` | ✅ удаляем | Task 3.2 | -| WebhookDedupKey.php (model) | ✅ удаляем (из Task 1.5) | Task 3.8 | -| NotificationService::notifyLowBalance | ✅/⚠️ (из Task 1.2) | Task 3.5 | -| NotificationService::notifyZeroBalance | ✅/⚠️ (из Task 1.2) | Task 3.5 | -| LowBalanceNotification (Mailable) | ✅/⚠️ (из Task 1.3) | Task 3.6 | -| ZeroBalanceNotification (Mailable) | ✅/⚠️ (из Task 1.3) | Task 3.6 | -| RejectedDealsLog (model + table) | ✅/⚠️ (из Task 1.4) | Task 3.7 + Task 4.1 | -| webhook_log (partitioned table) | ✅ DROP (источник = только ProcessWebhookJob) | Task 4.1 | -| webhook_dedup_keys (table) | ✅ DROP (из Task 1.5) | Task 4.1 | -| tenants.webhook_token + webhook_token_rotated_at | ✅/⚠️ (из Task 1.6) | Task 4.1 | -| system_settings.low_balance_threshold_leads (seed) | ✅/⚠️ (из Task 1.7) | Task 4.1 | +`tenants.webhook_token` используется в 7+ тест-файлах вне `WebhookReceiveTest` (через TenantFactory или прямые create-массивы). Перед DROP COLUMN нужно почистить: -- [ ] **Step 2: Commit (заполненный план)** +- `app/database/factories/TenantFactory.php:25` — `'webhook_token' => Str::random(64)` (убрать из фабрики) +- `app/app/Models/Tenant.php:34,35,64` — `$fillable` и `$casts` (убрать) +- `app/tests/Feature/RlsSmokeTest.php:48,55` — create tenant массивы +- `app/tests/Feature/AdminIncidentShowTest.php:67` — create tenant +- `app/tests/Feature/AdminBillingActionsTest.php:17` — create tenant +- `app/tests/Feature/Admin/AdminPdSubjectRequestsControllerTest.php:167` — create tenant +- `app/tests/Feature/PartitionsCreateMonthsTest.php:103` — create tenant +- `app/tests/Feature/Console/VerifyAuditChainsTest.php:72` — create tenant +- `app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php:22` — прямой `INSERT INTO tenants ... webhook_token` (потребует ручной правки SQL) +- `app/storage/_demo_split_tenants.php:80` — demo-скрипт (не критично, но удалить) -Run: +- [ ] **Step 1: Commit обновлённого плана (с финальной таблицей + новой Task 3.0)** + +Run в worktree: ```bash git add docs/superpowers/plans/2026-05-24-legacy-direct-webhook-removal.md -git commit -m "docs(billing): план — заполнены impact-checks для legacy webhook removal" +git commit -m "docs(billing): план — заполнены impact-checks, найден RED FLAG webhook_dedup_keys" ``` Expected: коммит проходит, lefthook GREEN. @@ -440,6 +451,71 @@ Expected: lefthook GREEN, коммит проходит. ## Phase 3 — Удаление обвязки +### Task 3.0: Чистка `webhook_token` из живых тестов и фабрики (добавлено после Phase 1 impact-check) + +**Files:** + +- Modify: `app/database/factories/TenantFactory.php` +- Modify: `app/app/Models/Tenant.php` +- Modify: `app/tests/Feature/RlsSmokeTest.php` +- Modify: `app/tests/Feature/AdminIncidentShowTest.php` +- Modify: `app/tests/Feature/AdminBillingActionsTest.php` +- Modify: `app/tests/Feature/Admin/AdminPdSubjectRequestsControllerTest.php` +- Modify: `app/tests/Feature/PartitionsCreateMonthsTest.php` +- Modify: `app/tests/Feature/Console/VerifyAuditChainsTest.php` +- Modify: `app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php` +- Modify (opt): `app/storage/_demo_split_tenants.php` + +**Контекст:** колонки `tenants.webhook_token` и `webhook_token_rotated_at` исчезнут в Task 4.1. Перед DROP COLUMN все живые тесты/фабрики, явно передающие эти поля, должны прекратить их передавать (иначе после миграции INSERT упадёт на «column does not exist»). + +- [ ] **Step 1: Убрать `webhook_token` из `Tenant.php` `$fillable` и `$casts`** + +Открыть `app/app/Models/Tenant.php`. Найти строки `34, 35, 64` (по результатам impact-check) — удалить `'webhook_token'` и `'webhook_token_rotated_at'` из массивов `$fillable` и `$casts`. + +- [ ] **Step 2: Убрать `webhook_token` из `TenantFactory`** + +Открыть `app/database/factories/TenantFactory.php:25`. Удалить строку: + +```php +'webhook_token' => Str::random(64), +``` + +(если есть и `webhook_token_rotated_at` в фабрике — тоже удалить). + +- [ ] **Step 3: Почистить 7 тест-файлов** + +Для каждого файла: + +```bash +grep -n "webhook_token" app/tests/Feature/RlsSmokeTest.php +``` + +Удалить строки `'webhook_token' => ...` и `'webhook_token_rotated_at' => ...` из create-массивов в каждом из 7 файлов. Поскольку фабрика тоже почищена (Step 2), тесты использующие `Tenant::factory()->create()` без явных полей сами начнут работать. + +Особый случай — `app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php:22`: там прямой SQL `INSERT INTO tenants (..., webhook_token, ...) VALUES (...)`. Удалить из INSERT и колонку, и соответствующее значение. + +- [ ] **Step 4: Опционально — почистить demo-скрипт** + +`app/storage/_demo_split_tenants.php:80` — это dev-скрипт для split-теста, не продакшен. Если решено его не трогать (один лишний failure при запуске после Phase 4) — пропустить. Иначе удалить строку `'webhook_token' => Str::random(64)`. + +- [ ] **Step 5: Прогнать тесты + verify** + +```bash +php artisan test --parallel 2>&1 | tail -5 +grep -rn "webhook_token" app/tests/ app/database/factories/ app/app/Models/Tenant.php | head -10 +``` + +Expected: тесты GREEN. Grep — 0 матчей (либо только в комментариях). + +NB: тесты пройдут с **существующими** колонками `webhook_token` в БД (мы их пока не дропали — это Task 4.1). После Task 4.1 они продолжат проходить, потому что больше не используют эти поля. + +- [ ] **Step 6: Commit** + +```bash +git add -A +git commit -m "refactor(billing): cleanup tenants.webhook_token from factories + 7 tests (pre-DROP)" +``` + ### Task 3.1: Удалить `WebhookReceiveController` (если impact-check ✅) + тест **Files:** @@ -521,43 +597,13 @@ git commit -m "refactor(billing): remove legacy WebhookReceiveController + POST который уже удалён). Шеринг-канал /api/webhook/supplier/{secret} не затронут." ``` -### Task 3.3: Удалить модель `WebhookDedupKey` +### Task 3.3: ~~Удалить модель WebhookDedupKey~~ — **ОТМЕНЕНА** (Phase 1 RED FLAG) -**Files:** +**Решение Phase 1 (impact-check Task 1.5):** оставить `WebhookDedupKey.php` и таблицу `webhook_dedup_keys` — они **активно используются** `App\Services\Import\HistoricalImportService` для идемпотентности CSV-импорта (резервный канал, явно out of scope по §2.2 спека). -- Delete: `app/app/Models/WebhookDedupKey.php` +После удаления `ProcessWebhookJob` в Task 2.1, у `WebhookDedupKey` останется один caller — `HistoricalImportService`. Это нормально. Никаких правок не требуется. -- [ ] **Step 1: Удалить файл** - -Run: - -```bash -rm app/app/Models/WebhookDedupKey.php -``` - -- [ ] **Step 2: Verify нет orphan ссылок** - -```bash -grep -rn "WebhookDedupKey" app/ -``` - -Expected: пусто (всё удалено в Task 2.1 + 2.2). - -- [ ] **Step 3: Прогнать тесты + Larastan** - -```bash -php artisan test --parallel 2>&1 | tail -5 -cd app && composer stan 2>&1 | tail -5 -``` - -Expected: GREEN. - -- [ ] **Step 4: Commit** - -```bash -git add -A -git commit -m "refactor(billing): remove WebhookDedupKey model (legacy)" -``` +Перейти к Task 3.4. ### Task 3.4: Удалить `RejectedDealsLog` модель (conditional) @@ -697,10 +743,18 @@ Expected: всё GREEN. Если Larastan ругается — обновить - [ ] **Step 2: Verify нет orphan ссылок на legacy** ```bash -grep -rn "ProcessWebhookJob\|WebhookReceiveController\|WebhookDedupKey" app/ +grep -rn "ProcessWebhookJob\|WebhookReceiveController" app/ ``` -Expected: пусто. Если что-то нашлось — добавить в план задачу или удалить inline. +Expected: пусто. + +**NB:** `WebhookDedupKey` ИЗ grep-команды исключён — он остаётся живым (Task 1.5 RED FLAG, использует CSV-канал). Проверять отдельно: + +```bash +grep -rn "WebhookDedupKey\|webhook_dedup_keys" app/ | grep -v Import +``` + +Expected: пусто (все use вне CSV-импорта удалены). Если что-то осталось — добавить inline-задачу. - [ ] **Step 3: Без коммита (он был в каждом step'е), переходим к Phase 4** @@ -735,12 +789,15 @@ return new class extends Migration * * Spec: docs/superpowers/specs/2026-05-24-legacy-direct-webhook-removal-design.md * - * Что удаляем: + * Что удаляем (финальный список по результатам Phase 1 impact-checks): * - webhook_log (partitioned, 13 партиций) — пустая на проде, источник = только удалённый ProcessWebhookJob - * - webhook_dedup_keys — источник = только ProcessWebhookJob - * - rejected_deals_log (conditional, см. impact-check 1.4) — если нет readers - * - tenants.webhook_token, tenants.webhook_token_rotated_at (conditional, см. 1.6) — если нет UI - * - system_settings.low_balance_threshold_leads (conditional, см. 1.7) — если только legacy seed + * - rejected_deals_log — writer только ProcessWebhookJob, нет readers + * - tenants.webhook_token + tenants.webhook_token_rotated_at — нет в UI/API, тесты почищены в Task 3.0 + * - system_settings.low_balance_threshold_leads (seed) — только legacy + * + * Что НЕ удаляем (Phase 1 RED FLAG): + * - webhook_dedup_keys — таблица АКТИВНО используется HistoricalImportService (CSV-канал) + * для идемпотентности повторных импортов. Дропать = сломать резервный канал. * * pgsql_supplier connection — BYPASSRLS-роль crm_supplier_worker (паттерн Спека B): * под обычной crm_app_user DROP/ALTER без app.current_tenant_id GUC не пройдёт. @@ -752,15 +809,12 @@ return new class extends Migration // Partitioned table — DROP TABLE каскадит партиции. $conn->statement('DROP TABLE IF EXISTS webhook_log CASCADE'); - $conn->statement('DROP TABLE IF EXISTS webhook_dedup_keys CASCADE'); - - // Conditional блоки — раскомментировать по результатам impact-checks Phase 1 - // (заполнены значениями ✅; если в impact-check был ⚠️ — закомментировать соответствующий блок). + // NB: webhook_dedup_keys НЕ дропаем — Phase 1 RED FLAG, живой через HistoricalImportService. // RejectedDealsLog (Task 1.4 → ✅ — нет readers): $conn->statement('DROP TABLE IF EXISTS rejected_deals_log CASCADE'); - // tenants.webhook_token + webhook_token_rotated_at (Task 1.6 → ✅ — нет UI): + // tenants.webhook_token + webhook_token_rotated_at (Task 1.6 → ✅ — нет UI; тесты почищены в Task 3.0): $conn->statement('ALTER TABLE tenants DROP COLUMN IF EXISTS webhook_token, DROP COLUMN IF EXISTS webhook_token_rotated_at'); // system_settings.low_balance_threshold_leads (Task 1.7 → ✅ — только legacy seed): @@ -793,7 +847,8 @@ Expected: миграция применилась, нет ошибок. Если - [ ] **Step 3: Verify в БД** ```bash -psql $DATABASE_URL -c "\dt webhook_log* webhook_dedup_keys rejected_deals_log" 2>&1 +psql $DATABASE_URL -c "\dt webhook_log* rejected_deals_log" 2>&1 +psql $DATABASE_URL -c "\dt webhook_dedup_keys" 2>&1 # должна ОСТАТЬСЯ (RED FLAG) psql $DATABASE_URL -c "\d tenants" 2>&1 | grep webhook psql $DATABASE_URL -c "SELECT * FROM system_settings WHERE key LIKE 'low_balance%'" 2>&1 ``` @@ -801,7 +856,7 @@ psql $DATABASE_URL -c "SELECT * FROM system_settings WHERE key LIKE 'low_balance Expected: - `\dt webhook_log*` → empty (нет таблицы и партиций). -- `\dt webhook_dedup_keys` → empty. +- `\dt webhook_dedup_keys` → 1 row (таблица ЖИВА, RED FLAG из Phase 1 — оставлена для CSV-канала). - `\dt rejected_deals_log` → empty (если Task 1.4 → ✅). - `\d tenants | grep webhook` → пусто (если Task 1.6 → ✅). - `low_balance_threshold_leads` → 0 rows (если Task 1.7 → ✅). @@ -833,7 +888,7 @@ git commit -m "db(billing): миграция — DROP legacy webhook артеф Открыть `db/schema.sql`. Найти и удалить: - `CREATE TABLE webhook_log` (партиционированная) + все 13 `CREATE TABLE webhook_log_yYYYY_mMM` + их индексы. -- `CREATE TABLE webhook_dedup_keys` + индексы. +- `CREATE TABLE webhook_dedup_keys` + индексы — **НЕ удалять из baseline** (таблица живая, RED FLAG из Phase 1). - `CREATE TABLE rejected_deals_log` (если Task 1.4 → ✅) + индексы. - Из `CREATE TABLE tenants` — удалить колонки `webhook_token` и `webhook_token_rotated_at` (если Task 1.6 → ✅). - Из seed `INSERT INTO system_settings` — удалить строку `low_balance_threshold_leads` (если Task 1.7 → ✅). @@ -862,7 +917,7 @@ Expected: `pretend` показывает что pending миграций нет ```markdown ## v8.XX от 2026-05-24 — Legacy webhook removal -- **DROP**: `webhook_log` (partitioned, 13 партиций), `webhook_dedup_keys`, `rejected_deals_log` (conditional). +- **DROP**: `webhook_log` (partitioned, 13 партиций), `rejected_deals_log`. **NB:** `webhook_dedup_keys` НЕ удаляется — Phase 1 RED FLAG. - **ALTER**: `tenants` — DROP COLUMN `webhook_token`, `webhook_token_rotated_at` (conditional). - **DELETE seed**: `system_settings.low_balance_threshold_leads` (conditional). @@ -1014,7 +1069,8 @@ Expected: composer install no-op, migrate применяет одну мигра ```bash ssh -i ~/.ssh/liderra_deploy ubuntu@111.88.246.137 'sudo -u postgres psql -d liderra <