Merge remote-tracking branch 'origin/main' into feat/router-stage3-three-fixes
This commit is contained in:
@@ -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 <refspec> --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: <yes | no>
|
||||
Lefthook cross-ref-checker (C2): <green | red after N iterations>
|
||||
§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.
|
||||
@@ -1721,3 +1721,4 @@ FNS
|
||||
NTFS
|
||||
маппинге
|
||||
dogfooded
|
||||
пинуются
|
||||
|
||||
@@ -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 <<EOF
|
||||
SELECT * FROM information_schema.tables WHERE table_name IN ('webhook_log', 'webhook_dedup_keys', 'rejected_deals_log');
|
||||
SELECT table_name FROM information_schema.tables WHERE table_name IN ('webhook_log', 'rejected_deals_log');
|
||||
-- ожидается: 0 rows (webhook_dedup_keys НЕ проверяем — она ОСТАЛАСЬ для CSV-канала)
|
||||
SELECT column_name FROM information_schema.columns WHERE table_name='tenants' AND column_name LIKE 'webhook_%';
|
||||
EOF'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user