Commit Graph

117 Commits

Author SHA1 Message Date
Дмитрий fe778858ce docs(plans): close Plan 2/5 supplier webhook routing — status header
Все Tasks 1–9 + CV.1–CV.14 + Task 11 выполнены в main (fb55bfd..c1ae195, 16 commits).
Plan 2.5 hotfix (1ba1df8 fix #3 idempotency + c1ae195 fix #2 concurrency) закрыл
2 из 3 BLOCKER findings CV.11 audit.

В Plan 3 backlog:
- BLOCKER #6 (RLS на failed_webhook_jobs INSERT NULL tenant) — первая задача.
- WARN #4 (placeholder secret '__SET_ON_DEPLOY__') + WARN #5 (пустой IP allowlist)
  — operational deploy gates.
- 8 minor WARN + 5 NIT — Plan 3 NOTES.

Pest 549/547 (+2 от Plan 2 baseline 547/545), Larastan + Pint clean.

Чекбоксы Tasks/CV/Task11 ниже намеренно не tick'нуты — детали статуса
в memory/project_supplier_integration.md и memory/project_state.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:31:59 +03:00
Дмитрий 67cb2cc946 docs(superpowers): economy-mode hook bypass closure spec + plan
6-component architecture (permissions block + 5 hooks) closing 8 critical/high
bypass paths: settings disable (H1), hook script edit (H2), prompt injection
(H4), state file delete (H6), subagent inheritance (H7), PostCompact loss
(H8), retry exhaustion (H9), verifier endpoint failure (H10), tool output
spoofing (H12), no-claim bypass (H13).

End-of-prompt parsing for "экономия N%". Shared state file in $TEMP.
Sonnet 4.6 verifier on Stop with decision:"block" + max 3 retry → escalate.

Spec: 964 lines, 12 sections.
Plan: 7 stages with TDD per task.
Runtime cost: ~\$7-14/month.

Stage 0 ratchet verified: auto-mode classifier blocks subagent Write on hook
scripts AND Bash heredoc bypass on settings.json.

Also adds 4 cspell vocabulary terms (парсингом/промпт/Mojibake/sed) used
in the new spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:39:23 +03:00
Дмитрий 00aaa9ea89 docs(plan): Plan 2/5 — Supplier Webhook + Sharing Routing
11 задач + 14-пунктовый Comprehensive Verification Gate.
Spec §5-§6: platform-wide webhook + N deal-копий через LeadRouter.
Legacy /api/webhook/{token} остаётся параллельным каналом.

+ allowlist generic pattern 7\d{3}1234567 для PhonePrefixService docs/tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:55:13 +03:00
Дмитрий 7ab7cf51cb docs(plans): supplier integration plan 1/5 — Foundation (schema + models + validators)
12 tasks + 3 verification gates covering:
- 5 migrations: extend projects, supplier_projects, pricing_tiers, lead_charges, supplier_sync_log
- 4 new Eloquent models + factories + Pest unit tests
- Project model extension with signal_type/sms_*/supplier_b1/b2/b3 relations
- 3 signal validators (Domain, Phone, SmsSender) with edge-case datasets
- SupplierProjectResolver service with B1+SMS guard
- Comprehensive verification gate: Larastan + squawk + pgFormatter + cspell + markdownlint + cycle-check + code-review subagent

Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §2, §7

cspell-words: +vashinvestor
.gitleaks.toml allowlist: +test phones for validator datasets

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:10:04 +03:00
Дмитрий 3f463f48ea docs(specs): close all 7 open questions in supplier integration design
Q1 SMS validation: B2 = (sender, keyword), B3 = sender only; B1 not available
Q2 Supplier pricing: фиксированная цена за лид
Q3 Liderra billing: 7-tier volume pricing, monthly reset (config in admin)
Q4 Geo filter: MVP, union at supplier + filter on our side
Q5 Webhook auth: defense-in-depth (IP allowlist + secret token in URL)
Q6 Cookie/CSRF refresh: hourly headless Playwright cron + reactive on 401/403
Q7 supplier_project TTL: immediate disable, delete after 180 days no activity

cspell-words.txt: +геофильтр, логинится, encrypter, PHPSESSID

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:39:29 +03:00
Дмитрий ac09603181 docs(specs): supplier integration design (Лидерра ↔ crm.bp-gr.ru)
Captures brainstorming output for two-way integration:
- Sharing model: один supplier-лид → много deal-копий клиентам Лидерры
- 3 supplier-проекта на источник (B1/B2/B3), shared между клиентами
- Dynamic limit adjustment через 20:30 МСК cron + AJAX rt-project-update
- Quota distribution Total/3 с приоритетом B1→B2 на остатке
- Instant FIFO routing по дате создания проекта клиента
- Резервирование обоих направлений: pending-changes queue + CSV reconciliation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:02:19 +03:00
Дмитрий 528fe16166 docs(plans): Sprint 6 Phase A — Reports backend implementation plan
6 tasks: 3 providers (managers/sources/billing) + registry extension +
S3 disk abstraction + file download endpoint. TDD with Pest 4.
Corrects schema column names vs spec (manager_id, type, balance_rub_after)
and uses May 2026 test dates (partitions start 2026-05-01).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:38:43 +03:00
Дмитрий 2a6f0289db docs(spec): Sprint 6 Post-MVP backend design (Reports + Notifications + AdminTenantDetail) 2026-05-10 09:13:45 +03:00
Дмитрий 3c13cc5d64 docs(rules): третий аудит нормативки — закрытие 13 находок (CLAUDE.md v1.86 + PSR_v1 v1.7 + Tooling v1.15)
P0 (4) — арифметические конфликты в CLAUDE.md, прошли мимо второго аудита:
- §3 header «Карта 28 инструментов» → «33» (header застрял с pre-FD эпохи)
- §3.4 header «(+5, итого 28)» → «итого 29» (после #30 в фазу 2)
- §3.3 footer «из 30 номеров минус #1 = 29» → «33 номеров: 29 phase-active + 3 off-phase + 1 historic»
- §6 «Активно: 19 инструментов из 29» (vs раскладка 9+8+7=24) → «24» в обоих местах

P1 (5) — sync stale `+`-refs после второго аудита:
- PSR_v1 шапка: «CLAUDE.md v1.84+/Pravila v1.9+» → «v1.86+/v1.10+»
- Tooling шапка: «Pravila v1.9+/PSR_v1 v1.5+/CLAUDE.md v1.84+» → «v1.10+/v1.7+/v1.86+»
- CLAUDE.md §5 п.5 «PSR_v1 v1.5+» → «v1.7+»

P2 (2) — внутренние несогласованности:
- PSR_v1 line 4/856 «slot уровня 2.5» → «уровня 2b» (описка внутри changelog'а v1.6)
- CLAUDE.md §3.3 #33 «вне Pravila §13» → «вне UI-пула §13» (Pravila §13.2 v1.10 включает claude-md-management как infrastructure subsection)

Sync-правки в connected files:
- README.md + README_АРХИВ_v8_5.md «карта 28 инструментов» → «33»
- Tooling §11.5/§12 «не входят в 28 инструментов» → «33 формализованные позиции»

Через `/claude-md-management:claude-md-improver` с cross-impact-анализом перед каждой группой правок. Pravila v1.10 без изменений (cross-refs к нему уже актуальны).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 06:32:17 +03:00
Дмитрий 07ed3f1be6 docs(rules): второй аудит нормативки — закрытие 15 находок (CLAUDE.md v1.85 + Pravila v1.10 + PSR_v1 v1.6 + Tooling v1.14)
Закрыто 15 находок второго аудита правил использования плагинов и скилов
(4 P0 реальных противоречий + 7 P1 stale-refs + 4 P2 структурных).
Через `/claude-md-management:claude-md-improver`.

P0 — реальные противоречия:
- Tooling §10.3 шаг 2: «3 skills» → «14, все запреты сняты Pravila §11»
  (закрыта внутри-документная контрадикция с §4.1 того же файла)
- CLAUDE.md §6: арифметика «33» исправлена (+1 заменённый PG MCP
  исторически; раньше 19/29 + 3 = 32, не 33)
- Pravila §13.2: «v1.4 (15 правил)» → «v1.6 (16 правил R0–R15)»
- Tooling §13: добавлены v1.13 + v1.14 entries (раньше история
  обрывалась на v1.12, хотя шапка описывала v1.13)

P1 — массовый stale-refs дрейф v1.4→v1.6 + v1.12→v1.14:
- CLAUDE.md §0 cross-refs (Pravila/PSR_v1/Tooling rows), §3.3 #31, #32, §5 п.12
- Pravila §11.5 («10 правил»→«16»), §13.9, §13.10
- Tooling §4.4 («10 правил»→«16»), §4.5, §4.6, §4.7

P2 — структурные:
- Tooling Прил. Н добавлен explicit-слотом уровня 2b в priority chain
  (CLAUDE.md §1, PSR_v1 R0.1 таблица, Tooling §7) — раньше формальная
  дыра «PSR_v1 R0.1 говорит stack ниже Tooling, но Tooling нет ни
  в одной priority chain»
- PSR_v1 R0.4.A свёрнут до cross-ref на Pravila §12.3 SoT — устранён
  риск дрейфа формулировок (раньше параллелил список разной формулировкой)
- Pravila §0 +note про §11 локальное override-исключение над §2.2/§4.5/§8.4
  (раньше §11 формально стоял ниже §9 в цепочке вопреки фактическому
  override §2.2/§4.5/§8.4 при skill-инвокации)
- PSR_v1 R0.6 hard-стопы пронумерованы 1–11 для надёжности cross-refs
  «пункт 9/10/11» (раньше буллет-list, ссылки молча ломались)

Все 4 файла нормативной документации внутренне непротиворечивы.
История версий синхронизирована.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 05:56:43 +03:00
Дмитрий ffe34e1b17 docs(registry): Sprint 4 «Audit tail» closure record (v1.76→v1.77)
Sprint 4 закрыл 3 оставшихся audit O-* пункта (5 коммитов f77c91d..b912724):
- O-perf-04: keyset pagination в DealController::index + 3 Pest-теста
- O-refactor-04 хвост: 8 Vue-компонентов >300 строк разделены (AppLayout
  R0.6 hard-стоп снят явным запросом заказчика)
- O-refactor-06: rollup-plugin-visualizer + knip + cleanup ~165 строк
  dead-code в composables/

Acceptance закрыт за исключением 2 known DealsView/DealDetailDrawer
(intentional Sprint 3 defineExpose-контракт).

Регрессия: Pest 421 / Vitest 416 / Larastan 0 / vue-tsc 0 / ESLint 0 / build OK.

Sprint 0 push выполнен (4f36bd3..8c6374d, 21 коммит). PAT Workflows R&W
подтверждена через API-диагностику.

CLAUDE.md/Pravila/Tooling/Plugin_stack_rules — параллельно обновляются
другой сессией (PSR_v1 v1.4 формализация UPM/21st/R15 motion в локальных
коммитах 833e3e6 + cf1aabb), не трогаю в этом коммите.

cspell-words: +oxc (oxc-parser Windows quirk knip), +консты (Russian slang).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 05:06:47 +03:00
Дмитрий 27dec3d459 docs(rules): аудит нормативки — закрытие 14 находок (CLAUDE.md v1.84 + Pravila v1.9 + PSR_v1 v1.5 + Tooling v1.13)
Audit конфликтов и запутанностей между CLAUDE.md / Pravila / PSR_v1 / Tooling
выявил 14 находок (3 🔴 high, 6 🟡 medium, 5 🟢 low). Все правки — через
paired stack: writing-plans → executing-plans → claude-md-improver (для
CLAUDE.md по §5 п.10) → verification-before-completion с grep-evidence.

Ключевые правки:

1. claude-md-management формализован #33 в Tooling §4.7 — пятый
   включённый плагин (категория «инфраструктурная», вне UI-пула).
   Tooling §0 счётчик 31 → 33 (3 off-phase tools).

2. Tooling §7 + PSR_v1 уровнем 3 — иерархия source of truth расширена
   с 5 до 7 уровней, sync с CLAUDE.md §1.

3. Tooling §6 +5 конфликтов v1.4 — UPM↔FD, 21st↔Vuetify, 21st↔App*,
   framer↔motion-v, UPM↔21st (с 5 до 10 строк).

4. Pravila §12.3 объявлен Single Source of Truth для exclusions §12;
   PSR_v1 R0.4.A + CLAUDE.md §5 п.11 — cross-ref сюда.

5. Pravila §13.6 +tier-таблица hard-rule (explicit / transitive /
   standard) — снимает скрытую иерархию между §12 и §13.9/§13.10.

6. PSR_v1 R10.1 разбит на 3 блока: enabledPlugins / built-in skills
   Claude Code / MCP-серверы — раньше всё было одним списком.

7. PSR_v1 R8 +тай-брейкер FD↔21st (последовательно, FD ведущий).

8. PSR_v1 R10.4 + R14.7 — tier-метки transitive hard-rule с явным
   указанием, что Pravila §9 «Отступления» к ним не применяется.

9. Scope-метки приоритетных цепочек — Pravila §0 (внутрипараграфный),
   CLAUDE.md §1 (межфайловый), PSR_v1 R0.1 (scope головенства stack'а).
   Снимает путаницу 4-х представлений.

10. CLAUDE.md §5 п.5 свёрнут до 2 строк со ссылкой на PSR_v1 R14
    (был копией PSR_v1 на 12 строк).

11. Tooling §4.6 — settings.json → ~/.claude.json (где реально лежит
    API-ключ 21st).

cspell-words.txt: +внутрипараграфный, внутрипараграфные, скилов
(новые термины из scope-меток и plan-файла).

Намеренно оставлено: R0.6 пункт 11 ⊂ пункт 6 (motion-специальный flow);
Pravila §13.10 формально избыточен (явная запись лучше транзитивного).

Plan: docs/superpowers/plans/2026-05-10-rules-audit-fixes-plan.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 05:06:16 +03:00
Дмитрий 833e3e6083 docs(rules): PSR_v1 v1.4 — формализация UPM + 21st Magic MCP + R15 motion-системы
Триггер: пользователь спросил «хочу добавить плагины 21st, framer motion,
UI UX max — проанализируй конфликты». Проверка показала: UPM (skill) и 21st
Magic MCP (`magic` сервер) уже фактически включены в ~/.claude/settings.json
и ~/.claude.json, но в правилах не описаны. Framer Motion — React-only
runtime npm-библиотека, не Claude-плагин, физически не работает в Vue.

Через цикл brainstorming → 3 варианта → итерации согласовано: формализовать
UPM + 21st; для motion — двухуровневая R15 (framer-motion hard-запрет
навсегда + motion-v узкое окно по 4 условиям).

PSR_v1 v1.3 → v1.4 (главный артефакт):
- R6 → R6.0 универсальная таблица фильтра для FD/UPM/21st
- R6.1 hard-override Forest расширен на все три плагина
- R10.1 +21st row, ослабление UPM (FD молчит ИЛИ R12 третий вариант)
- R11.5 (новое) активация UPM в R12 архитектурном
- R11.6 (новое) иерархия 7 motion-источников
- R0.6 +3 hard-стопа (App* через 21st, Vuetify-эквивалент, motion-v)
- R13 +9 строк matrix'а
- R14 (новое, 7 подразделов) pipeline UPM + 21st
- R15 (новое, 7 подразделов) motion-системы
- R8 +7 тай-брейкеров

Pravila v1.7 → v1.8: §13 расширен на расширенный пул, §13.10 hard-link на R14.

Tooling Прил. Н v1.11 → v1.12: #31 UPM + #32 21st (off-phase tools), §9.2
motion-runtime denylist (framer-motion + react-spring R15.1; motion-v + gsap
+ anime + lottie + popmotion R15.2/R15.7). 31 формализованных позиций
(19/29 активных по фазам + 2 off-phase).

CLAUDE.md v1.82 → v1.83: §0 cross-refs, §2 +Animation default stack, §3.3
+#31 UPM +#32 21st, §5 п.5 расширен, §5 п.12 motion-runtime новый,
§6 обновлён, §9 +entry.

cspell-words.txt: +UPM +gsap +LLM (валидные термины проекта).

Через /claude-md-management:claude-md-improver. 6 файлов, 0 изменений
в коде проекта (resources/js/, app/, db/ нетронуты), 0 npm install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:33:42 +03:00
Дмитрий 8c6374d278 docs(spec-roadmap): fix broken link to memory/project_state.md (outside repo)
lychee pre-push hook поймал invalid markdown link на memory/project_state.md —
файл живёт в C:\Users\Administrator\.claude\projects\... (Claude auto-memory),
не в репозитории. Заменено на plain-text упоминание.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:12:58 +03:00
Дмитрий 823daf4f9f docs(plan-sprint4): implementation plan «Audit tail» + revised spec scope
Sprint 4 — закрытие 3 оставшихся audit O-* пунктов:
- Phase A: O-perf-04 keyset pagination в DealController::index
- Phase B (1+2+3): O-refactor-04 хвост, split 8 Vue-компонентов >300 строк
- Phase C: O-refactor-06 dead-code detection через rollup-plugin-visualizer + knip

Spec ревизия: Sprint 4 уменьшен с 8 до 3 пунктов после факт-чека —
O-perf-07/O-refactor-05/O-refactor-07/O-stack-02/O-stack-03 уже закрыты
в Sprint 1–2 (Sprint 1 ESLint vuetify rule, Sprint 2 Phase A: Pest browser
scaffold, infection mutation, lazy-loading, Larastan cache; CLAUDE.md
header фактически 30 строк после 5b13c95 changelog extraction).

Финальная регрессия: Pest 419+ / Vitest 430+ / Larastan + ESLint + vue-tsc 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 03:57:46 +03:00
Дмитрий 77b018dff8 docs(spec-roadmap): дизайн roadmap'а до production — 3-track структура (A/B/C)
Покрывает категории A (audit-хвосты), B (Phase 3 tooling), C (Post-MVP backend),
D (Б-1-зависимые фичи), E (production deploy в YC). Категория F (юр. тексты) —
external; G (push 18 коммитов) — Sprint 0 precondition.

Двухдорожечная структура:
- Track A (Sprint 4–6): не зависит от Б-1, стартует сразу после push
- Track B (Sprint 7–9): после получения реквизитов ООО — YC infra + CI/CD + SSO + лендинг + hardening + soft-launch

Definition of done «production launch» в §1, per-sprint acceptance в §5,
9 рисков с mitigations в §6, 5 open questions для будущей детализации в §7.

Базовый HEAD: 6c2f0ce (после Sprint 3 Phase C).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 03:51:05 +03:00
Дмитрий 42b3f3f9e4 docs(spec-sprint2): дизайн Спринта 2 «Modernization» — 12 O-* находок
Scope: 8 O-stack + 2 O-perf + 2 O-refactor (low/medium risk, без архитектурных
рефакторингов). Default-выборы для design-решений зафиксированы в spec'е.

4 фазы: A.Backend (Pest 4 browser + mutation + Laravel 13 lazy + Larastan cache)
→ B.Frontend (Vue 3.5 + Vuetify 3.12 + lazy-imports + ESLint check)
→ C.Docs (Google Fonts + CLAUDE.md §0 reorg + FD plugin checklist)
→ D.Hygiene (dead-code report).

Не входит — 6 Sprint 3 пунктов (high risk, R0.6 hard-стопы).

Бюджет: 5-6 часов агентов.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:51:55 +03:00
Дмитрий 97bdb784d6 docs(registry): новые открытые вопросы по аудиту P1-10/11/09
Phase F Спринта 1 «Hygiene». Закрытие аудита 2026-05-09 (b6ae8dd) для registry-блока:
- P1-10: новый CTO-18 в §3 — auth+tenant middleware на /api/deals на pre-prod.
  Trigger: prod-миграция (после Б-1). MVP-flow с tenant_id query-param остаётся.
- P1-11: cross-link к Б-1 в §10 — /api/admin/* без auth = часть SSO-блока.
- P1-09: новый OPEN-FE-1 в §8.5 (новый раздел Frontend tooling) — Histoire 1.0-beta.1
  ↔ Vite 8 совместимость. Trigger: релиз Histoire с peerDep vite ^8.

Все 3 пункта аудита P1, не правки в коде. После Sprint 1 эти пункты остаются
открытыми в реестре с явными triggers закрытия.

lychee: 0 errors. cspell: 0 issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:45:48 +03:00
Дмитрий e2de8bf8a2 docs(narrative): sync versions + Histoire 21/43 + cross-refs (audit P1-01/03/06/08 + P2-03 + post-A)
Phase D Спринта 1 «Hygiene». Закрытие аудита 2026-05-09 (b6ae8dd):
- P1-01: README.md обновлён (CLAUDE.md ссылка с 28→29 инструментов и 5→6 уровней;
  Tooling v1.0→v1.10; Pravila v1.2→v1.6; PSR_v1 v1.3 добавлен; schema v8.5→v8.11
  (56 базовых + 12 партиций / 97 индексов / 38 RLS / 5 функций / 13 триггеров)).
- P1-03: CLAUDE.md Histoire 21/28 → 21/43 (заменено все вхождения, включая §0 и §3.3).
- P1-06: Pravila §13.9 cross-ref на Plugin_stack_rules_v1 теперь с (v1.3).
- P1-08: Tooling §2.1 п.7 Stylelint раздел дополнен версией stylelint-config-standard ^40.0.0.
- P2-03: CLAUDE.md F-K cross-link на Plugin_stack_rules_v1.md#история-версий.
- post-A: метрики schema в CLAUDE.md синхронизированы до v8.11 (97 / 38).

Также фикс битой ссылки в spec'е Sprint 1 (lychee 1 error → 0): относительный путь
до Plugin_stack_rules_v1.md из docs/superpowers/specs/ исправлен на ../../

Bump версий:
- CLAUDE.md v1.81 → v1.82
- Pravila v1.6 → v1.7
- Tooling Прил. Н v1.10 → v1.11

Все правки CLAUDE.md/Pravila/Tooling — через claude-md-management:claude-md-improver
(CLAUDE.md §5 п.10). README.md и spec — обычные правки.

lychee: 0 errors. markdownlint: 0 errors. cspell на web/v8/* — out-of-scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:36:36 +03:00
Дмитрий 93b1af40d0 docs(plan-sprint1): implementation plan для Спринта 1 «Hygiene»
По spec'у 2026-05-09-sprint1-hygiene-design.md (08bbe4d). 41 атомарный task в 6 фазах:

- Phase A. DB (A1-A7, ~20 мин): RLS impersonation_tokens + 2 FK indices + CHANGELOG
  schema v8.10→v8.11 + verify squawk/grep (38 RLS, 97 indexes).
- Phase B. Backend (B1-B7, ~40 мин): apply 'tenant' middleware к 3 auth:sanctum-группам
  (alias уже в bootstrap/app.php) + HasPasswordRules trait + test password ≥8 +
  verify Pest 416/416 + Larastan 0.
- Phase C. Configs (C1-C8, ~40 мин): format:sql:check Windows fix + .lychee.toml exclude
  + pa11y.config paths + composer audit-offline + ESLint anti-vuetify + npm-outdated CI.
- Phase D. Docs (narrative, D1-D5, ~45 мин): README versions + CLAUDE.md/Pravila/Tooling
  через claude-md-management:claude-md-improver skill (CLAUDE.md §5 п.10).
- Phase E. Docs (handoff, E1-E4, ~45 мин): BRANDBOOK status-slug mapping table +
  DEVELOPER_HANDOFF axe-claim doc + font-display strategy.
- Phase F. Registry (F1-F3, ~15 мин): Открытые_вопросы — новый CTO для P1-10 +
  Б-1 cross-link для P1-11 + новый OPEN для P1-09 (Histoire-Vite).

Зависимости: B и D зависят от A (CHANGELOG ссылки + метрики schema).
Бюджет: 2.5-3.5 часа агентов.

Cspell-словарь дополнен: Митигация, закоммитить.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:12:26 +03:00
Дмитрий 08bbe4d9c8 docs(spec-sprint1): дизайн Спринта 1 «Hygiene» исправления аудита 2026-05-09
Spec через superpowers:brainstorming. Scope: 22 правки + 3 записи в реестр:
- 3 P0 (RLS impersonation_tokens, SetTenantContext middleware, format:sql:check Windows)
- 10 P1 (P1-10/P1-11 → реестр)
- 3 P2 (test password, орфо, F-K расшифровка)
- 6 low-risk O-* (2 FK indices, password rules trait, ESLint anti-vuetify, npm CI, font-display docs)

6 фаз / 6 коммитов: A.DB → B.Backend → C.Configs → D.Docs(narrative) → E.Docs(handoff) → F.Registry.
Зависимости: B/D зависят от A (CHANGELOG schema метрики).
Бюджет: 2.5-3.5 часа агентов в субагентном режиме.

CLAUDE.md/Pravila/Tooling правки в Фазе D — обязательно через
claude-md-management:claude-md-improver (CLAUDE.md §5 п.10).

Не входит: Спринт 2 (modernization) и Спринт 3 (big refactors) — отдельные spec'и.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:51:16 +03:00
Дмитрий b6ae8dd641 docs(audit): сводный отчёт аудита проекта на 2026-05-09 — P-дефекты + O-возможности
По spec v1.1 (b034301) и plan'у 2026-05-09-project-audit-plan.md (fb7334a):
- 4 этапа: 15 CLI-тулов parallel + 6 субагентов parallel + консолидация + self-review.
- Покрытие: docs (16 .md + Pravila/Plugin_stack_rules_v1/Tooling/CLAUDE.md/README) + db (4 файла) + handoff + Laravel + Vue + 10 конфигов корня.
- Категории: P0/P1/P2 + O-perf/O-refactor/O-stack + Отложенное (открытые вопросы + workaround-ы).
- R-CHECKS под Plugin_stack_rules_v1 v1.3 включены в чеклист и DoD.

Сводка: P0=3, P1=12, P2=3, O-perf=7, O-refactor=7, O-stack=10, Отложенное=4.

Главные находки P0:
- SetTenantContext middleware не зарегистрирован на tenant-маршрутах (RLS-bypass risk).
- impersonation_tokens без RLS+POLICY (есть tenant_id, gap 2 vs ENABLE RLS).
- format:sql:check сломан на Windows (Unix /tmp/ путь).

Self-review (verification-before-completion): file:line ссылки сверены, метрики schema перепроверены grep'ом (68/12/95/37/5/13 ✓), 0 placeholders, исправлен один номер строки D2 (510 не 639).
Independent code review: VERDICT: PASS (.tmp/audit/stage4_code_review.md).

Сам отчёт — диагностический. Реализация правок — отдельный flow по решению заказчика.
Stage 1-4 артефакты в .tmp/audit/ НЕ коммитятся (.gitignore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:40:25 +03:00
Дмитрий fb7334af05 docs(plan-audit): implementation plan для аудита проекта на 2026-05-09
По spec v1.1 (b034301). 12 атомарных Tasks по 4 фазам:
- Phase 0 — setup (Task 0): mkdir .tmp/audit, проверка .gitignore, snapshot HEAD/status.
- Phase 1 — sweep (Task 1): 15 CLI-тулов в 5 параллельных группах A/B/C/D/E,
  raw output в .tmp/audit/stage1_*.txt + stage1_summary.md.
- Phase 2 — субагенты (Tasks 2-4):
  Task 2 готовит r_rules_excerpt.md (CLAUDE.md §1 + R0.1/R6/R6.1/R10/R11/R13);
  Task 3 диспатчит 6 субагентов параллельно (D1-D6, единый шаблон промпта);
  Task 4 — early-stop предохранитель (>10 P0 на одном субагенте → AskUserQuestion).
- Phase 3 — консолидация (Tasks 5-8): aggregate, dedup по (file,line,category),
  переклассификация по spec §3, генерация docs/audit_2026-05-09.md по §9 структуре.
- Phase 4 — review + commit (Tasks 9-11): verification-before-completion (file:line +
  метрики + 0 placeholders + 0 R-нарушений), независимый code review через
  requesting-code-review (PASS/FAIL), коммит + update memory project_state.

Бюджет: 1.5-2 часа wall-clock. .tmp/audit/ НЕ коммитится (.gitignore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:15:02 +03:00
Дмитрий b0343014db docs(spec-audit): пересмотр под Plugin_stack_rules_v1 v1.3 — spec v1.0 → v1.1
§12 «Плагины и MCP» переписана с полным inventory и R-обоснованиями:
- 12.1 Superpowers-skills (6 применяемых + 8 из карты §12.2 не релевантны)
- 12.2 Встроенные плагины: FD plugin НЕ призывается (R10+R13: аудит ≠ дизайн); claude-md-management вне DoD аудита
- 12.3 Внешние плагины (R10): simplify/security-review/review/init/ui-ux-pro-max — только по явному /команде, не призываются
- 12.4 MCP: laravel-boost (D2/D4), playwright опционально (D3), github не используется
- 12.5 CLI-тулы — не плагины, регулируются Tooling Прил. Н

В §3 расширен P0 stack-нарушениями (Pravila §13.9 hard-link на R10).
В §6 D1 включает Pravila/Plugin_stack_rules_v1/Tooling в зону аудита;
D3 проверяет R11 иерархию источников истины UI/UX;
D5 проверяет R6 стек-фильтр Vue+Vuetify и R6.1 Forest.
В §7 добавлен общий R-CHECKS блок поверх P/O чеклиста.
В §10 DoD self-review проверяет «0 R-нарушений в самом отчёте»;
code review мета-проверяет соблюдение Plugin_stack_rules_v1 v1.3.
В §13 добавлены риски misuse FD plugin / priority chain / правок Pravila.
Новая §15 — карта соответствия Plugin_stack_rules_v1 v1.3 → spec
(13 R-правил + Pravila §12/§13.9 + CLAUDE.md §1/§5 п.10/п.11 + Tooling Прил. Н).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:01:36 +03:00
Дмитрий 4f36bd3601 docs(plugin-stack-rules): CLAUDE.md v1.77→v1.81 + Pravila v1.4→v1.6 + Прил. Н v1.9→v1.10 + Plugin_stack_rules_v1 v1.3 (новый) — снятие запрета Frontend Design plugin + paired stack со Superpowers + 23 закрытых трения
Frontend Design plugin (anthropics/frontend-design) подключён через ~/.claude/settings.json paired stack'ом со Superpowers v5.1.0. Координация — через новый docs/Plugin_stack_rules_v1.md (10 правил R0-R9 + R10-R13 + 6 патчей F-K, всего 23 закрытых конфликта).

v1.78: снят запрет CLAUDE.md §5 п.5 на Frontend Design plugin. Создан Plugin_stack_rules_v1.md (10 правил, 8 первичных конфликтов закрыты). Pravila +§13 «paired stack». Tooling +#30 в фазе 2.

v1.79: PSR v1.0→v1.1 — 5 патчей по реальным трениям A-E (R6.1 hard-override Forest, R1 дезамбигуация компонент=UI-фича, R7 deployable+Pa11y, R0.6 hard-стоп список из 8 триггеров, runtime-заметка о skill list = constant per conversation).

v1.80: PSR v1.1→v1.2 — принцип-аксиома «stack — головной» + R10 (внешние плагины как tools, реестр 11 плагинов с ролями: ui-ux-pro-max=резерв, claude-md-management=инструмент, review/security-review/init/simplify=только по /имя), R11 (иерархия 6 источников истины UI: Brandbook→ТЗ+schema→FD→Boost→UPM→Vue/Vuetify docs), R12 (три паттерна дизайн-решений), R13 (decision matrix Auto+§12+R0.6 на 14 типов задач).

v1.81: PSR v1.2→v1.3 + Pravila v1.5→v1.6 — 6 трений второго порядка F-K. F: R12 архитектурное override §4.5 через явный brainstorming. G: R12 тактическое split на «с альтернативами» (A/B/C под user-стиль «а/б») и «без» (одна BOLD от FD). H: R13 строка про новую UI-фичу разделена — «вне ТЗ И не в Открытые_вопросы» = hard-стоп (Pravila §7). I: R11.4 fallback при технической недоступности уровней источников. J: R10.4 смягчение + Pravila §13.9 hard-link (нарушение R10 = нарушение §13). K: R0.1 точный scope «головенства» через таблицу priority chain.

Skill list = constant per conversation — для активации FD требуется новый чат.

cspell-words.txt +5 (инвокация, инвокирован, инвокируемые, инвокируются, головенство).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:46:31 +03:00
Дмитрий 3d936d0da8 docs(spec-audit): добавлен дизайн подробного аудита проекта на 2026-05-09
Spec через superpowers:brainstorming. Гибридный подход (тулы + субагенты):
4 этапа (sweep тулами → 6 семантических субагентов → консолидация → self-review),
P0/P1/P2 для дефектов и O-perf/O-refactor/O-stack для возможностей улучшения.
Реализация — отдельным implementation plan через superpowers:writing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:00:02 +03:00
Дмитрий 4cac61d748 docs(superpowers-hard-rule): CLAUDE.md v1.76→v1.77 + Pravila v1.3→v1.4 + Прил. Н v1.8→v1.9 — Superpowers как hard rule §12
Заказчик 09.05.2026 ввёл правило явной командой: «Создай правило, что
ты всегда в первую очередь пользуешься superpowers. При этом ты не
можешь игнорировать и обходить это правило». Pravila §12 — единственное
hard-правило документа: §9 «Отступления» к нему не применяется.

Pravila v1.4:
- Добавлен §12 «Superpowers — приоритет первого выбора (hard rule)»,
  8 подсекций.
- §12.1 Принцип: skill инвокируется ПЕРВЫМ перед любой содержательной
  задачей; обычный flow только если skill отсутствует.
- §12.2 Карта 14 задач → 14 skills (TDD/debug/plan/parallel/review/
  verify/brainstorm/worktree/finishing PR/subagent/writing-skills/
  using-superpowers).
- §12.3 Когда правило НЕ применяется: чтение/grep/glob, тривиальные
  правки, справочные ответы, документация §4, открытые вопросы (§7).
- §12.4 Hard-rule статус: §9 не применяется; единственная отмена —
  явный запрос «не используй superpowers сейчас» только на текущее
  действие; рационализация — нарушение уровня §5 ПДн.
- §12.5 §12 имеет приоритет над §11.
- §12.6 §5/§7/§3.6 не override-ятся даже §12.
- §12.7 Нарушения: фиксация в feedback memory.
- §12.8 Ревизия: откат только явным запросом.
- §0 priority расширен — §12 встаёт перед §1.

CLAUDE.md v1.77 (через /claude-md-management:claude-md-improver):
- §1 priority chain +уровень 0: «Pravila §12 — Superpowers hard rule».
- §0 — Pravila v1.3 → v1.4.
- §3.3 строка №19 дополнена пометкой «§12 hard rule».
- §5 +п.11 «Не пропускать инвокацию Superpowers skill'а».

Прил. Н v1.9: шапка + §4.1 callout про hard rule.

cspell-words.txt: +5 слов второй порцией (инвокацию/инвокируется/
инвокирует/инвокировать/банами/банов).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:20:58 +03:00
Дмитрий 4993790fd5 docs(superpowers-unblock): CLAUDE.md v1.75→v1.76 + Pravila v1.2→v1.3 + Прил. Н v1.7→v1.8 — снятие запрета на Superpowers
Заказчик 09.05.2026 принял вариант "A. Полное снятие, включая Pravila".
Плагин obra/superpowers v5.1.0 (14 skills) подключён через ~/.claude/settings.json
(extraKnownMarketplaces.superpowers-dev + enabledPlugins.superpowers@superpowers-dev).
Декларативная установка — Claude Code сам подтянул кэш на рестарте.

Pravila v1.3:
- Добавлен §11 «Superpowers plugin — снят запрет, override §2.2/§4.5/§8.4».
- §11.1: brainstorming/writing-plans/executing-plans/dispatching-parallel-agents
  могут перевешивать §4.5/§8.4/§2.2 при явном вызове skill'а.
- §11.2: §1/§3.6/§5/§7/§9 не override-ятся.
- §11.3: using-git-worktrees физически нестабилен на кириллическом пути —
  это факт среды, не правил.
- §11.4: ревизия — заказчик может откатить §11 одной правкой.
- Приоритет в §0 расширен до §11. Таблица версий +v1.3.

CLAUDE.md v1.76 (через /claude-md-management:claude-md-improver по §5 п.11):
- §5 п.4 удалён (запрет на 5 Superpowers skills).
- Перенумерация 5→4..11→10. Перекрёстные ссылки переподписаны
  (п.11→п.10 в шапке п.7; пп.8→пп.7 внутри п.10).
- §3.3 строка №19: «3 skills» → «v5.1.0, все 14 skills, override
  §2.2/§4.5/§8.4 разрешён, см. Pravila §11».
- §0 — Pravila v1.2+ → v1.3 от 09.05.2026.

Прил. Н v1.8 (Tooling_v8_3.md):
- §4.1 «Поведенческий слой — Superpowers» полностью переписан:
  3 skills → 14 skills, установка декларативная (settings.json), а не /plugin install.
- Снятые ранее запреты сохранены как историческая запись.
- §11.2 «Git worktrees»: запрет снят, но Windows + кириллица остаются
  враждебной средой, skill сам обрабатывает ошибки.

cspell-words.txt: +5 слов (Лидерре, obra, override, ятся, ится).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:13:17 +03:00
Дмитрий f596e26153 docs(admin-tenant-detail-epic): фиксация Post-MVP AdminTenantDetailView закрытия
- Реестр v1.75→v1.76: запись о закрытии эпика, метрики Pest 416/416 + Vitest 416/416.
- CLAUDE.md в этот раз не правлю: §5 п.11 — синхронизация при следующем
  /claude-md-management:claude-md-improver run'е.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:39:08 +03:00
Дмитрий 5b13c95180 docs(claude-md): refactor v1.73→v1.74 — вынос changelog + правило single-entry plugin
(1) Структурный refactor — 250+ строк changelog v1.1→v1.73 вынесены из шапки CLAUDE.md в новый docs/CHANGELOG_claude_md.md (CLAUDE.md 380→234 строк, −39%; conciseness score 5/15→14/15 по quality-criteria плагина claude-md-management).

(2) Введено правило §5 п.11 — все правки CLAUDE.md только через плагин claude-md-management из anthropics/claude-plugins-official: /claude-md-improver (audit + targeted updates) и /revise-claude-md (capture session-learnings). Прямые Edit/Write — нарушение. §5 п.8 дополнен оговоркой о применимости внутри flow п.11. В шапке добавлена строка про владельца.

(3) cspell-words.txt +23 слова — техжаргон из перенесённого changelog'а (anthropics/CWD/маппится/бампается/etc); часть из них опечатки в исторических записях, не правлю исторический контент.

Содержательно правила/состав 28 инструментов/метрики не тронуты — синхронизация Pravila/Tooling не требуется.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:12:36 +03:00
Дмитрий ab6a74052c docs(reports-epic): фиксация Post-MVP Reports backend закрытия
- Реестр v1.74→v1.75: запись о закрытии эпика, метрики Pest 403/403 + Vitest 393/393.
- CLAUDE.md v1.72→v1.73: подробные изменения по 4 этапам.
- cspell-words.txt: +«реструктура».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:52:25 +03:00
Дмитрий 07ff7c8bb2 docs(mvp-closed): фиксация финала фазы 2 — Claude-зона MVP закрыта
- Реестр v1.73→v1.74: запись о закрытии MVP, остаточные blocked-only.
- CLAUDE.md v1.71→v1.72: финал-метрики Pest 359/359 + Vitest 369/369.
- cspell-words.txt: +«бэкенда» (творительный падеж).
- Дальнейшая работа возобновляется по триггерам (Б-1, юр., фаза 3, deploy).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:14:14 +03:00
Дмитрий 830a652588 phase2(trash-bin): GET /api/deals?only_deleted + «Корзина» в DealsView
Расширяет stages 5/6 (soft-delete + 8-сек undo) до постоянного доступа
к удалённым сделкам через отдельный view-mode.

Backend (DealController::index):
- Новый query-param only_deleted=true.
- withTrashed() + whereNotNull('deleted_at') — обход global scope
  SoftDeletes + явный фильтр для NO-OP idempotency.
- Все остальные фильтры применимы и в trash-mode.

Pest +3 (DealIndexTest):
- only_deleted=true → только soft-deleted (alive скрыты).
- Без only_deleted → soft-deleted скрыты (default behavior).
- RLS+app-фильтр изолирует чужие удалённые.

Frontend:
- ListDealsParams.onlyDeleted?: boolean + axios mapping.
- DealsView: trashMode ref + toggleTrashMode (clear selected + reload) +
  applyBulkRestoreFromTrash (optimistic remove + bulkRestoreDeals + toast).
- UI changes в trash-mode:
  - Заголовок «Сделки» → «Корзина».
  - Toggle-btn 'mdi-arrow-left К сделкам' (warning-flat) вместо
    'mdi-trash-can-outline Корзина' (outlined).
  - Скрыты Экспорт + Новая сделка.
  - Скрыт chiprow filter-bar.
  - Info-alert «Корзина: показаны удалённые сделки».
  - Bulk-bar: только Восстановить (mdi-restore success-tonal) + clear;
    status/export/delete скрыты.

Vitest +2 (DealsListIntegration):
- toggleTrashMode → trashMode=true + listDeals с onlyDeleted=true.
- applyBulkRestoreFromTrash → bulkRestoreDeals + remove from state +
  toast «Восстановлено 2».

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 321/321 за 19.60 сек (+2 от 319).
- Vite build 1.04 сек.
- Pint + PHPStan passed.
- Pest 269/269 за 29.12 сек (+3 от 266, 1009 assertions).

Реестр v1.72→v1.73 / CLAUDE.md v1.63→v1.64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:27:11 +03:00
Дмитрий 01c20e7b6c phase2(polling): usePolling composable 30 сек + Page Visibility pause
Закрывает последний unblocked production-TODO «Polling/SSE для real-time».
Manual reload-btn остаётся как fast-path; polling — фоновый автообновитель.

Composable (composables/usePolling.ts):
- usePolling(loader, {intervalMs=30_000, enabled=true}).
- Page Visibility API: при document.hidden=true interval останавливается;
  при visibilitychange с возвратом hidden=false — restart + немедленный
  loader() (не ждать следующего interval'а).
- Cleanup на onBeforeUnmount: clearInterval + removeEventListener.
- enabled=false — composable не стартует (feature-flag).

Integration:
- DealsView + KanbanView → loadDeals.
- AdminTenantsView → loadTenants.
- AdminBillingView → loadBilling.
- AdminIncidentsView → loadIncidents.

Vitest +6 (usePolling.spec.ts) с vi.useFakeTimers:
- Вызов каждые intervalMs / default 30 сек / skip при document.hidden /
  cleanup на unmount / enabled=false → no-op / visibilitychange
  pause+resume с немедленным loader.

Регресс:
- Lint+type-check+format passed.
- Vitest 319/319 за 18.67 сек (+6 от 313).
- Vite build 899 ms.
- Pint + PHPStan passed.
- Pest 266/266 за 28.62 сек (backend не тронут).

Реестр v1.71→v1.72 / CLAUDE.md v1.62→v1.63.
ВСЕ unblocked production-TODO закрыты.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:17:51 +03:00
Дмитрий fa11c7b223 phase2(admin-tenants-mrr): mrr_rub в /api/admin/tenants (этап 7)
Закрывает gap из v1.66 — mock-форма имеет mrrRub, но API возвращал null.
Теперь AdminTenantsView показывает реальную колонку MRR.

Backend (AdminTenantsController::index):
- Добавлено tariff_plans.price_monthly as tariff_price_monthly в select.
- mrr_rub в response: price_monthly (string) если не-trial; иначе null.
- Aggregate-формат как у /admin/billing — string чтобы decimal не терял
  точность при передаче через JSON.

Pest +3 (AdminTenantsIndexTest):
- mrr_rub='990.00' для активного тарифа не-trial.
- mrr_rub=null для trial (даже если тариф есть).
- mrr_rub=null если current_tariff_id отсутствует.

Frontend:
- ApiAdminTenant.mrr_rub: string | null в типе.
- mapApiAdminTenant: parseFloat(api.mrr_rub) или null (вместо hardcoded
  null из v1.66).
- AdminTenantsView: formatRub(item.mrrRub) для консистентности с другими
  ₽-полями.

Vitest +2:
- mrr_rub строка → number.
- mrr_rub=null → mrrRub null.

PHPStan baseline регенерирован. cspell-glossary +консистентности.

Регресс:
- Lint+type-check+format passed.
- Vitest 313/313 за 18.83 сек (+2 от 311).
- Vite build 947 ms.
- Pint + PHPStan passed.
- Pest 266/266 за 28.39 сек (+3 от 263, 1001 assertion).

Реестр v1.70→v1.71 / CLAUDE.md v1.61→v1.62.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:08:12 +03:00
Дмитрий c34d4009d1 phase2(restore-flow): POST /api/deals/restore + undo-snackbar (этап 6 — completion of stage 5)
Soft-delete был half-done: пользователь не мог отменить случайное удаление.
Теперь после bulk-delete показывается snackbar «Удалено N · Восстановить»
на 8 секунд.

Backend (DealController::restore):
- POST /api/deals/restore {tenant_id, ids: [1..1000 ints]}.
- withTrashed() обходит global scope SoftDeletes + явный
  whereNotNull('deleted_at') для NO-OP idempotency на живых.
- RLS + defense-in-depth where(tenant_id).
- ActivityLog event=deal.restored, context.source='bulk' для каждой
  ВОССТАНОВЛЕННОЙ. Константа EVENT_DEAL_RESTORED добавлена в модель.

Pest +7 (DealRestoreTest):
- 422/404 базовые / soft-delete + restore + audit / NO-OP на живых
  не пишет audit / defense-in-depth (свой restored, чужой остался) /
  после restore видна в GET /api/deals / 422 пустой массив.

Frontend:
- dealsApi.bulkRestoreDeals — POST-helper.
- DealsView::applyBulkDelete: snapshot удалённых сделок (deep-clone
  manager.*) сохраняется в lastDeletedSnapshot ref.
- undoBulkDelete() async: optimistic re-insert + bulkRestoreDeals если
  auth.user; success → toast «Восстановлено N»; fail → warning.
- v-snackbar bulk-delete: 3→8 сек timeout + #actions слот с кнопкой
  «Восстановить» (показ только при snapshot.length > 0). После undo
  snapshot очищается → кнопка пропадает.

Vitest +3 (DealsListIntegration):
- bulk-delete + undo восстанавливает обе + bulkRestoreDeals + cleanup
  snapshot.
- Undo без tenant_id — НЕ вызывает API + только локально.
- Undo reject → warning toast + локальное восстановление остаётся.

PHPStan baseline регенерирован. cspell-glossary +unshift +партиальный.

Регресс:
- Lint+type-check+format passed.
- Vitest 311/311 за 18.71 сек (+3 от 308).
- Vite build 877 ms.
- Pint + PHPStan passed.
- Pest 263/263 за 27.68 сек (+7 от 256, 998 assertions).

Реестр v1.69→v1.70 / CLAUDE.md v1.60→v1.61.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:01:35 +03:00
Дмитрий f0dce283a8 phase2(soft-delete): schema v8.9 + DELETE /api/deals (этап 5/5 — авто-план закрыт)
Bulk soft-delete для UI applyBulkDelete. Hard-delete отбракован из-за
CASCADE-FK от webhook_dedup_keys: hard уничтожил бы dedup-ключи и
нарушил идемпотентность webhook §5.5.

Schema v8.8 → v8.9:
- deals.deleted_at TIMESTAMPTZ (NULL = живая).
- Partial index (tenant_id, status) WHERE deleted_at IS NULL —
  самый частый UI-фильтр.
- ALTER TABLE на партиционированной deals distributes во все 6
  партиций автоматически (PG 14+).
- CHANGELOG +§U с обоснованием soft vs hard.

Backend (DealController::destroy):
- DELETE /api/deals {tenant_id, ids: [1..1000 ints]}.
- Bulk-update deleted_at=NOW() через RLS + defense-in-depth where(tenant_id).
- ActivityLog event=deal.deleted (source='bulk') для каждой ИЗМЕНЁННОЙ.
- NO-OP (уже удалена) не пишет audit.
- Deal model: SoftDeletes trait + deleted_at в fillable/casts. Global
  scope автоматически добавляет whereNull('deleted_at') ко всем существующим
  query (index/show/transition/update/export).

Pest +8 (DealDestroyTest):
- 422/404 базовые / soft-delete + audit / defense-in-depth (свой
  удалён, чужой жив) / NO-OP idempotency / GET скрывает soft-deleted
  (list+show 404) / 422 пустой массив.
- Quirk: migrate:fresh --env=testing без .env.testing использует liderra
  вместо liderra_testing → решение DB_DATABASE=liderra_testing migrate:fresh.

Frontend:
- dealsApi.bulkDeleteDeals — DELETE-helper с config.data (axios особенность).
- DealsView::applyBulkDelete async: optimistic local-removal +
  bulkDeleteDeals если auth.user; success → toast «Удалено N из M.»;
  fail → warning toast + локальный update НЕ откатывается.

Vitest +3 (DealsListIntegration):
- bulkDeleteDeals с tenant_id + optimistic + toast.
- Без tenant_id — НЕ вызывается.
- Reject → warning toast + локальный update остаётся.

PHPStan baseline регенерирован.

АВТО-ПЛАН (5 этапов) ЗАКРЫТ ПОЛНОСТЬЮ.

Регресс:
- Lint+type-check+format passed.
- Vitest 308/308 за 20.12 сек (+3 от 305).
- Vite build 973 ms.
- Pint + PHPStan passed.
- Pest 256/256 за 27.75 сек (+8 от 248, 977 assertions).

Реестр v1.68→v1.69 / CLAUDE.md v1.59→v1.60.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:51:47 +03:00
Дмитрий 14dc317e2b phase2(admin-incidents): GET /api/admin/incidents + AdminIncidentsView API (этап 4/5)
Чтение incidents_log с фильтрами type/severity/unresolved_only + summary
(open/investigating/rkn_pending/total_unresolved).

Backend (AdminIncidentsController::index):
- ORDER BY started_at DESC. Filters: type, severity, unresolved_only=true.
- Derived: incident_id (INC-YYYY-MMDD-NNNN), status (resolved_at!=null →
  resolved; detected_at!=null → investigating; иначе open),
  affected_tenants_count из BIGINT[] (parsePgArray для '{1,2,3}'),
  rkn_deadline_at = detected_at+24h для data_breach без notification.
- summary: open/investigating/rkn_pending/total_unresolved.

Pest +11 (AdminIncidentsIndexTest):
- пустой / incident_id формат / derive status / filter type+severity /
  unresolved_only / ORDER BY started_at DESC / rkn_deadline +24h для
  data_breach / non-data_breach без deadline / summary.rkn_pending /
  limit+offset.
- Quirk: saas_admin_users.full_name (не first/last) + нет updated_at.

Frontend:
- api/admin.ts::listAdminIncidents — типизированный helper.
- AdminIncidentsView: унифицированный IncidentRow (mock-category ↔
  API-type, mock-title ↔ API-summary). Reactive rowsState+stats default
  = MOCK; loadIncidents() async на onMounted; fetchError + warning
  alert + MOCK fallback; reload-btn. РКН pending chip учитывает оба
  pdn_breach/data_breach.

Vitest +5:
- listAdminIncidents на mount / replace state+stats + rkn_deadline /
  reject → fetchError+alert+fallback / reload-btn x2 / РКН pending chip
  виден для data_breach без notification.

PHPStan baseline регенерирован. cspell-glossary +MMDD.

Регресс:
- Lint+type-check+format passed.
- Vitest 305/305 за 20.59 сек (+5 от 300).
- Vite build 1.05 сек.
- Pint + PHPStan passed.
- Pest 248/248 за 28.02 сек (+11 от 237, 951 assertion).

Реестр v1.67→v1.68 / CLAUDE.md v1.58→v1.59.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:38:34 +03:00
Дмитрий 4532b95d64 phase2(admin-billing): GET /api/admin/billing + AdminBillingView API (этап 3/5)
Aggregates пополнений/списаний за текущий месяц по balance_transactions
+ summary с MRR/revenue/overdue/refunds_30d.

Backend (AdminBillingController::index):
- GET /api/admin/billing?search=. Per-tenant SUM с CASE WHEN type IN
  ('topup','lead_charge') GROUP BY tenant_id; ABS для charges.
- Row: id/subdomain/organization_name/contact_email/status/balance_rub/
  tariff_id/tariff_name/mrr_rub (=tariff.price_monthly если не-trial)/
  monthly_topups_rub/monthly_charges_rub/last_payment_at/
  chargeback_unrecovered_rub.
- summary: total_mrr_rub (SUM не-trial), monthly_revenue_rub (SUM topup),
  overdue_count (balance<0 || chargeback>0), refunds_count_30d.
- Quirk: schema-колонка tariff_plans.price_monthly (НЕ price_rub_monthly)
  — обнаружено первым прогоном Pest, исправлено сразу.

Pest +9 (AdminBillingIndexTest):
- пустой / поля+tariff JOIN / aggregates за месяц / прошлый месяц не
  попадает / overdue / refunds_30d (старые исключены) / total_mrr_rub
  (trial исключаются) / search ILIKE / soft-deleted скрыт.

Frontend:
- api/admin.ts::listAdminBilling — типизированный helper.
- AdminBillingView: reactive rowsState+summary default = MOCK,
  loadBilling() async на onMounted парсит API-строки → numbers + derive
  status (suspended/balance<0||chargeback>0→overdue/active). На fail —
  fetchError + warning alert + MOCK fallback. Reload-btn.
- tariffLabel/statusInfo обобщены с fallback'ами на новые slug'и.

Vitest +4:
- listAdminBilling на mount / replace rowsState+summary + string→number
  + status derive / reject → fetchError+alert+fallback / reload-btn x2.

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 300/300 за 18.41 сек (+4 от 296).
- Vite build 925 ms.
- Pint + PHPStan passed.
- Pest 237/237 за 27.69 сек (+9 от 228, 926 assertions).

Реестр v1.66→v1.67 / CLAUDE.md v1.57→v1.58.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:28:49 +03:00
Дмитрий 6ef9961f5f phase2(admin-tenants): GET /api/admin/tenants + AdminTenantsView API (этап 2/5)
AdminTenantsView переходит с mock-данных на live backend.

Backend (AdminTenantsController::index):
- GET /api/admin/tenants?status=&search=&limit=&offset=.
- LEFT JOIN tariff_plans для tariff_name. ORDER BY last_activity_at DESC.
- ILIKE search по organization_name + subdomain + contact_email.
- stats {total, active, trial, overdue} — overdue считает balance<0
  ИЛИ chargeback_unrecovered_rub > 0.
- На MVP без auth (saas-admin SSO ⏸ Б-1).

Pest +8 (AdminTenantsIndexTest):
- 200 + пустой / все поля / status filter / search ILIKE /
  ORDER BY last_activity_at DESC / stats / soft-deleted скрыт /
  limit+offset.

Frontend:
- api/admin.ts::listAdminTenants — типизированный helper.
- composables/adminTenantsMapper.ts::mapApiAdminTenant — converter
  API → UI: status derive (is_trial→trial, chargeback>0||balance<0
  →overdue), inn='', code=subdomain, tariff clamp на known TenantTariff,
  todayActual/mrrRub отсутствуют в API → 0/null, activitySince через
  formatRelative.
- AdminTenantsView: reactive tenantsState+stats default = MOCK,
  loadTenants() на onMounted → splice replace; на fail — fetchError +
  warning alert + MOCK fallback. Reload-btn.

Vitest +13:
- View-integration (4): listAdminTenants на mount / replace state+stats /
  reject → fetchError + alert + fallback / reload-btn x2.
- Mapper (9): name/code/inn/status-derives (trial/overdue/suspended) /
  balance_rub→number / activitySince + null fallback.

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 296/296 за 18.91 сек (+13 от 283).
- Vite build 1.02 сек.
- Pint + PHPStan passed.
- Pest 228/228 за 25.22 сек (+8 от 220, 906 assertions).

Реестр v1.65→v1.66 / CLAUDE.md v1.56→v1.57.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:19:53 +03:00
Дмитрий 7e1bf8b42d phase2(deal-patch): PATCH /api/deals/{id} + comment-editor в DealDetailDrawer (этап 1/5)
Drawer из read-only становится editable. ActivityLog event пишется на
каждое изменение поля.

Backend (DealController::update):
- PATCH /api/deals/{id} {tenant_id, comment?, manager_id?, status?}.
- Каждое изменённое поле → ActivityLog:
  comment → deal.commented (context.text);
  manager_id → deal.assigned (context.from/to + assigned_at=NOW);
  status → deal.status_changed (context.from/to/source='manual').
- NO-OP не пишется в audit. Manager FK guard + status slug validation.
- RLS + defense-in-depth where(tenant_id) → 404 для чужой сделки.

Pest +10 (DealUpdateTest):
- 422/404 базовые / 404 чужая сделка / comment+audit / manager+audit+
  assigned_at / status+audit / 422 неизвестный slug / 422 чужой manager /
  NO-OP не пишет / комбинированно → 2 audit записи.

Frontend:
- api/deals.ts::updateDeal — PATCH helper c ensureCsrfCookie.
- DealDetailDrawer: новая секция «Комментарий» (только при tenantId).
  v-textarea auto-grow + counter=5000 + Save-btn → updateDeal →
  toast success + reload events (новый deal.commented в timeline).
  На fail → warning toast.

Vitest +3 (DealDetailDrawerApi):
- saveComment вызывает updateDeal + toast + reload events (getDeal x2).
- saveComment reject → commentSaveError + warning toast.
- comment-section не рендерится без tenantId.

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 283/283 за 18.13 сек (+3 от 280).
- Vite build 1.12 сек.
- Pint + PHPStan passed.
- Pest 220/220 за 25.64 сек (+10 от 210, 871 assertion).

Реестр v1.64→v1.65 / CLAUDE.md v1.55→v1.56.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:10:58 +03:00
Дмитрий e31ea5354a phase2(lead-statuses): GET /api/lead-statuses + Pinia-store с snapshot fallback
Заменяет static-снапшот LEAD_STATUSES в коде на live-данные из БД.
Custom slug'и (добавленные после deployment'а) теперь видны UI без rebuild'а.

Backend:
- LeadStatus model (PK=slug string, incrementing=false, timestamps=null).
- LeadStatusController::index — GET /api/lead-statuses, ORDER BY sort_order,
  slug. Таблица глобальная (не tenant-aware), auth не требуется на MVP.

Pest +5 (LeadStatusesIndexTest):
- 200 + не пустой / все 14 системных slug'ов из seed / все нужные поля /
  sort_order ASC / кастомный slug после INSERT появляется в endpoint'е.

Frontend:
- api/leadStatuses.ts::listLeadStatuses — GET helper.
- stores/leadStatuses.ts::useLeadStatusesStore — Pinia setup-store:
  statuses default = LEAD_STATUSES snapshot (UI работает без fetch'а),
  load(force=false) идемпотентен, bySlug computed Map, findBySlug helper.
  На fail — snapshot остаётся, fetchError=true.
- DealsView/KanbanView/DealDetailDrawer переехали со static-импорта
  LEAD_STATUSES на store. KanbanView использует safe-access
  dealsByStatus[slug] || [] (защита от custom slug'а из API без seeded
  column). load() в onMounted у обоих view'ов.

Vitest +7 (leadStatusesStore.spec.ts):
- initial snapshot / findBySlug existing & null / load success replace +
  loaded / load reject — fetchError + snapshot fallback / load идемпотентен /
  load(force=true) refetch.
- 2 spec'а DealDetailDrawer получили setActivePinia(createPinia()) в
  beforeEach (без этого Pinia store-injection в jsdom падает).

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 280/280 за 19.44 сек (+7 от 273).
- Vite build 1.17 сек.
- Pint + PHPStan passed.
- Pest 210/210 за 24.59 сек (+5 от 205, 840 assertions).

Реестр v1.63→v1.64 / CLAUDE.md v1.54→v1.55.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:59:17 +03:00
Дмитрий cba76c5d18 phase2(deal-show): GET /api/deals/{id} + DealDetailDrawer на реальный ActivityLog
Закрывает gap «timeline в drawer'е показывает hard-coded MOCK_EVENTS» —
теперь drawer fetch'ит реальные activity-events на open из tenant-filtered
activity_log. Без tenant_id — fallback на MOCK_EVENTS как раньше.

Backend (DealController::show):
- GET /api/deals/{id}?tenant_id={id} — возвращает {deal, events}.
- Deal extended (project_name, manager_name/initials, comment, assigned_at).
- Events — последние 50 записей activity_log по (tenant_id, deal_id)
  ORDER BY created_at DESC, с актором (user через belongsTo).
- RLS-обёртка + defense-in-depth where(tenant_id) — 404 если чужая.

Pest +8 (DealShowTest):
- 422/404 базовые / 404 чужая сделка / deal-relations / events ORDER BY +
  actor + actor=null для system-event / RLS+app-фильтр изоляция событий /
  лимит 50 событий.

Frontend:
- api/deals.ts::getDeal — типизированный helper c ApiDealEvent/Detail/Response.
- composables/dealsApiMapper.ts::mapApiDealEvent — converter ApiDealEvent →
  DealEvent: clamp event-slug на known types с fallback на 'deal.viewed';
  detail зависит от type (status_changed: 'from → to'; created: source;
  остальные: JSON-сводка context).
- DealDetailDrawer: optional tenantId prop, watch([open, deal.id, tenantId])
  с immediate=true → loadEvents() на open. Reject → eventsFetchError +
  v-alert warning + MOCK_EVENTS fallback.
- DealsView/KanbanView передают :tenant-id="auth.user?.tenant_id".

Vitest +4 (DealDetailDrawerApi.spec.ts):
- Без tenantId — getDeal не вызывается + MOCK_EVENTS видны.
- С tenantId — getDeal + events замещены + 'new → paid' виден.
- reject → fetchError + alert + MOCK_EVENTS fallback.
- open=false → getDeal не вызывается.

PHPStan baseline регенерирован.

Регресс:
- Lint+type-check+format passed.
- Vitest 273/273 за 20.76 сек (+4 от 269).
- Vite build 1.12 сек.
- Pint + PHPStan passed.
- Pest 205/205 за 24.19 сек (+8 от 197, 812 assertions).

Реестр v1.62→v1.63 / CLAUDE.md v1.53→v1.54.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:21:50 +03:00
Дмитрий a1ea003642 phase2(xlsx-export): PhpSpreadsheet 5.7 + format=csv|xlsx на /api/deals/export
Закрыт TODO «реальный XLSX-export» из v1.51. Russian users prefer .xlsx
(1С/Excel) — заменяет CSV как default. CSV остаётся через format=csv.

Backend (DealController::export):
- Body теперь: {tenant_id, ids, format?: 'csv' | 'xlsx'}; default 'csv'.
- buildXlsx: Spreadsheet + setTitle 'Сделки' + setCellValue A1..G1
  headers + bold(A1:G1) + setAutoSize всех колонок A..G. Writer пишет
  через ob_start/php://output для возврата бинарной строки.
- Content-Type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
  + Content-Disposition с .xlsx.

Quirk: PhpSpreadsheet 5.x удалил deprecated setCellValueByColumnAndRow —
мигрировал на A1-нотацию (setCellValue('A2', $val)).

Pest +4 (DealCreateTest):
- xlsx binary с Content-Type + magic bytes "PK\x03\x04" + size >2KB.
- IOFactory::createReader('Xlsx') распаковывает: sheet «Сделки» +
  A1='ID' bold + A2/B2/C2 — реальные данные сделки.
- 422 на неизвестный format.
- Default (без format) — backward-compat CSV.

Frontend:
- api/deals.ts разделён: exportDeals (CSV string) + exportDealsXlsx
  (Blob, responseType='blob').
- applyBulkExport(format='xlsx' | 'csv') в DealsView — default 'xlsx'.
  XLSX → triggerBlobDownload (новый helper). CSV → старый CSV-helper.
  На fail — fallback на local CSV.

Vitest +3 (DealsListIntegration):
- xlsx default → exportDealsXlsx + Blob download + toast «XLSX».
- 'csv' → exportDeals + toast «CSV».
- xlsx reject → fallback на local CSV + toast «Backend недоступен».

PHPStan baseline регенерирован (удалена unmatched ignore-запись для
setCellValueByColumnAndRow). cspell-glossary +дефолтит +vnd +spreadsheetml.

Регресс:
- Lint+type-check+format passed.
- Vitest 269/269 за 18.49 сек (+3 от 266).
- Vite build 982 ms.
- Pint + PHPStan passed.
- Pest 197/197 за 26.05 сек (+4 от 193, 784 assertions).

Реестр v1.61→v1.62 / CLAUDE.md v1.52→v1.53.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:08:53 +03:00
Дмитрий ac186593f2 phase2(bulk-transition+reload): POST /api/deals/transition + reload-btn
Закрывает gap «UI меняет статус, но изменения не сохраняются на backend»
из v1.51. Reload-btn заменяет polling/SSE до прихода long-poll'а на prod.

Backend (DealController::transition):
- POST /api/deals/transition {tenant_id, ids: [1..1000 ints], status}.
- Валидация status — exists в lead_statuses (глобальная таблица).
- RLS-обёртка SET LOCAL + defense-in-depth where(tenant_id) для
  partial-update: чужие id остаются в исходном статусе.
- ActivityLog event=deal.status_changed с context={from, to, source: 'bulk'}
  для каждой ИЗМЕНЁННОЙ сделки. NO-OP (старый==новый) не пишется в audit.
- Ответ: {updated, requested, status}.

Pest +7 (DealTransitionTest):
- 422 missing fields / 404 unknown tenant / 422 неизвестный slug + не апдейт /
  batch update 3 сделок + 3 ActivityLog с правильным context /
  NO-OP не пишет ActivityLog / defense-in-depth (2 tenant'а — обновляется
  только свой) / 422 пустой массив ids.

Frontend:
- dealsApi.transitionDeals — типизированный helper с ensureCsrfCookie.
- applyBulkStatus в DealsView переписан async: optimistic local-update +
  backend-вызов если auth.user.tenant_id. На success — toast «Обновлено
  N из M.», на fail — warning toast + локальный update НЕ откатывается.
  Без auth.user — только optimistic (legacy local-mode сохранён).
- reload-btn в DealsView и KanbanView — outlined «Обновить» mdi-refresh,
  привязан к loadDeals. В DealsView :loading="loading" во время fetch'а.

Vitest +5:
- reload-btn (Deals + Kanban) — listDeals вызывается дважды.
- applyBulkStatus с tenant_id — transitionDeals + optimistic + toast.
- applyBulkStatus без tenant_id — НЕ вызывается transitionDeals.
- applyBulkStatus reject — toast warning + локальный update остаётся.

PHPStan baseline регенерирован. cspell-glossary +апдейт*.

Регресс:
- Lint+type-check+format passed.
- Vitest 266/266 за 18.16 сек (+5 от 261).
- Vite build 1.06 сек.
- Pint + PHPStan passed.
- Pest 193/193 за 23.27 сек (+7 от 186, 767 assertions).

Реестр v1.60→v1.61 / CLAUDE.md v1.51→v1.52.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 07:46:19 +03:00
Дмитрий 339e8ea53b phase2(deals-list-api): GET /api/deals + замена MOCK_DEALS на fetch с fallback
Закрыт TODO (c) из v1.50: backend-эндпоинт для списка сделок и его
интеграция в DealsView/KanbanView вместо статичного MOCK_DEALS.

Backend (DealController::index):
- Query-params: tenant_id (required, 422/404), status_in[] (whereIn),
  project_id, manager_id, search (ILIKE по phone+contact_name),
  limit clamp [1..500] default 100, offset default 0.
- ORDER BY received_at DESC, id DESC. Eager-load project + manager.
- RLS-обёртка SET LOCAL app.current_tenant_id + defense-in-depth
  where(tenant_id) — на тестах через postgres superuser RLS обходится
  BYPASSRLS, app-фильтр гарантирует изоляцию.
- Ответ: {deals: [...], total, limit, offset}; manager_name/initials
  форматируются через ManagerController::formatName/formatInitials.

Pest +12 (DealIndexTest):
- 422/404, пустой список, relations (project_name+manager_name+initials),
  RLS-изоляция, ORDER BY, status_in[], project_id, manager_id, search
  ILIKE, limit+offset, manager=null edge case.

Frontend:
- api/deals.ts::listDeals — типизированный helper c ApiDeal/ListDeals*.
- composables/dealsApiMapper.ts::mapApiDeal — converter ApiDeal→MockDeal:
  contact_name fallback на phone, manager.name='Не назначен' /
  initials='—' при null, project='—' при null, cost=0,
  receivedMinutesAgo=max(0, …) от clock-skew.
- DealsView/KanbanView: onMounted(loadDeals) async-вызывает listDeals
  если auth.user.tenant_id, на success replace через splice, на fail
  fetchError=true + v-alert warning, MOCK_DEALS как fallback.

Vitest +14:
- dealsApiMapper.spec.ts (8): 1:1, fallback'и, edge cases.
- DealsListIntegration.spec.ts (6): без tenant_id — НЕ вызывает API,
  с tenant_id — replace state, reject → fetchError + alert + fallback;
  для DealsView и KanbanView.

PHPStan baseline регенерирован. cspell-glossary +ILIKE +DTO.

Регресс:
- Lint+type-check+format passed.
- Vitest 261/261 за 19.62 сек (+14 от 247).
- Vite build 989 ms.
- Pint + PHPStan passed.
- Pest 186/186 за 22 сек (+12 от 174, 742 assertions).

Реестр v1.59→v1.60 / CLAUDE.md v1.50→v1.51.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 07:34:39 +03:00
Дмитрий eb8ca65c5d phase2(supplier-resolver): вынос дублированной логики lookup в App\Services\SupplierResolver
Закрыт TODO (a) из v1.49: общая логика выбора активного supplier'а через
project_suppliers m2m была дублирована между ProcessWebhookJob (webhook-flow)
и DealController (manual-create) — 11 одинаковых строк query-builder'а на
2 файла. Теперь — единственный источник истины + DI через app() (тот же
паттерн, что у DuplicateDetector в v1.23).

App\Services\SupplierResolver:
- resolveForProject(Project): ?int — точная копия прежней query
  (project_suppliers JOIN suppliers, is_active+is_active, ORDER BY
   sort_order, id).
- costRubSnapshot(int $supplierId): string — вынесенный snapshot цены
  для записи в supplier_lead_costs.

ProcessWebhookJob и DealController:
- Удалены private resolveSupplierId() (по 14 строк).
- Удалены локальные DB::table('suppliers')->value('cost_rub').
- Используют app(SupplierResolver::class) внутри handle()/store().

Pest +8 в tests/Feature/Services/SupplierResolverTest.php:
- null без связей / единственный активный / пропуск inactive supplier /
  пропуск inactive m2m / ORDER BY sort_order / null если все inactive /
  изоляция по project_id / costRubSnapshot формат '137.50'.

PHPStan baseline регенерирован.

Регресс:
- Pint + PHPStan passed (baseline регенерирован).
- Pest 174/174 за 21.46 сек (+8 от 166, 708 assertions).
- Vitest 247/247 за 17.53 сек (нетронут — backend-only refactor).

Реестр v1.58→v1.59 / CLAUDE.md v1.49→v1.50.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 07:11:44 +03:00
Дмитрий 515114cff5 phase2(lookups+integrity): GET /api/managers+projects + manager FK guard + SupplierLeadCost для manual
3 интеграционных доработки после backend-completion v1.57.

(1) GET /api/managers + /api/projects + manager FK guard:
- ManagerController::index — active users тенанта (is_active+deleted_at IS NULL).
  Формат {id, email, first_name, last_name, name, initials} с
  formatName/formatInitials helpers (fallback на email).
- ProjectController::index — active projects (is_active=true).
- Оба endpoint'а: tenant_id query-param, 422 без, 404 unknown, RLS-обёртка.
- DealController::store FK guard: manager_id должен принадлежать tenant'у +
  is_active. Иначе 422 (закрывает security-gap чужого менеджера).
- Pest +8 в LookupsTest.

(2) Replace MOCK_MANAGERS / MOCK_PROJECTS на API в NewDealDialog:
- projectOptions/managerOptions ref'ы с MOCK fallback.
- loadLookups через Promise.all([listProjects, listManagers]) на open
  диалога с tenantId.
- managerIdByName Map name→id для submit'а.
- Silent fallback на mock при network-error.
- Vitest +2.

(3) SupplierLeadCost для manual-leads:
- В DealController::store после Deal::create — resolveSupplierId (копия
  логики ProcessWebhookJob: project_suppliers JOIN suppliers + ORDER BY
  sort_order). Если supplier найден — SupplierLeadCost с snapshot cost_rub
  + supplier_lead_id=NULL (manual: нет внешнего id).
- Manual по-прежнему НЕ списывает баланс (Ю-2 reseller-модель — charge
  только при webhook'е); cost-аналитика всё равно нужна.
- Pest +2.
- TODO: рефактор resolveSupplierId в App\Services\SupplierResolver чтобы
  Job + Controller разделяли логику.

Старый тест manager_id=42 переписан под FK guard через User::factory.

PHPStan baseline регенерирован (+28 ignored Pest TestCall warnings).

Регресс: lint+type-check+format ; vitest 247/247 за 16.32 сек (+2);
vite build 951 ms; Pint+PHPStan passed; Pest 166/166 за 22.11 сек
(+10 от 156, 699 assertions). Реестр v1.57→v1.58, CLAUDE.md v1.48→v1.49.

Production TODO остаточные:
- resolveSupplierId → SupplierResolver service.
- XLSX-export через PhpSpreadsheet.
- GET /api/deals для replace MOCK_DEALS в DealsView/KanbanView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 06:58:49 +03:00
Дмитрий 83bb9de2bb phase2(backend-completion): POST /api/deals + webhook_hmac_required + POST /api/deals/export
3 backend-completion после tightening v1.56.

(1) POST /api/deals — manual create endpoint:
- DealController::store. Project firstOrCreate (type='manual'). Deal с
  source_crm_id=NULL. RLS-обёрнутая транзакция.
- Manual НЕ списывает баланс / НЕ дедуп / НЕ SupplierLeadCost.
  ActivityLog с context.source=manual.
- NewDealDialog получил optional tenantId prop. С tenantId — POST → backend-id;
  на error fallback на local-id + warning + dialog open.
- DealsView/KanbanView передают auth.user?.tenant_id.
- Pest +8.

(2) webhook_hmac_required flag в system_settings:
- Seed-row в db/schema.sql (default false backward-compat).
- WebhookReceiveController::isHmacRequired private helper.
- При true: запрос без X-Webhook-Signature → 401.
- Pest +3.

(3) POST /api/deals/export — backend CSV:
- DealController::export. Валидация ids[1-10000]. RLS-обёрнутый whereIn.
- Excel-friendly CSV: BOM "\u{FEFF}" PHP-литерал, ; разделитель, \r\n.
- text/csv attachment headers.
- Frontend applyBulkExport: backend → fallback на client-side
  (buildLocalCsv вынесен).
- Pest +4.

Vitest +3 (всего 245/245).
PHPStan убрал лишнюю Deal->id===null проверку (Eloquent int).
DealsView/KanbanView spec'ы получили setActivePinia.

Регресс: lint+type-check+format ; vitest 245/245 за 17.07 сек (+3);
vite build 1.04 сек; Pint+PHPStan passed; Pest 156/156 за 20.27 сек
(+15 от 141, 675 assertions). Реестр v1.56→v1.57, CLAUDE.md v1.47→v1.48.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 06:43:21 +03:00
Дмитрий 4a385b1df7 phase2(prod-tightening): HMAC+rate-limit webhook / fetch system_settings / CSV export
3 production-tightening после 7-фичного пакета v1.55.

(1) HMAC + per-token rate-limit для webhook receive endpoint:
- WebhookReceiveController::receive: tenant lookup → rate-limit → HMAC
  → payload validation.
- HMAC: опциональный X-Webhook-Signature: sha256=<hex> через hash_hmac +
  hash_equals (constant-time). Backward-compat: header missing → 202.
- Per-token rate-limit: RateLimiter с decay 60 сек. Лимит из
  system_settings.webhook_rate_limit_rps × 60. На превышении 429 +
  Retry-After. Hit ставится ДО валидации payload — иначе обходимо 422.
- Pest +5: HMAC valid/invalid 401/missing 202; rate-limit 60+1=429;
  ключ изолирован per-token.

(2) Реальный fetch system_settings в AdminSystemView:
- onMounted → adminApi.listSystemSettings() → splice replace.
- На fetch-error → fallback на mock + warning v-alert.
- Кнопка «Обновить» — ручной reload.
- Vitest +3: mount fetch / reload / error fallback.

(3) Реальный CSV-export для bulk-actions DealsView:
- applyBulkExport → CSV через Blob+a[download].
- 8 колонок, ; разделитель, \r\n, BOM через String.fromCharCode(0xFEFF)
  (литеральный U+FEFF блокируется ESLint no-irregular-whitespace).
- Filename deals_export_YYYY-MM-DD.csv.
- Empty selection → toast без download.
- Vitest +2: spy createObjectURL+anchor.click; empty без blob.

PHPStan baseline регенерирован.

Регресс: lint+type-check+format ; vitest 242/242 за 15.82 сек (+4);
vite build 903 ms; Pint+PHPStan passed; Pest 141/141 за 17.8 сек (+5,
627 assertions). Реестр v1.55→v1.56, CLAUDE.md v1.46→v1.47.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 05:49:34 +03:00