Commit Graph

195 Commits

Author SHA1 Message Date
Дмитрий 280cfcd6cf feat(routes): register POST /api/webhook/supplier/{secret}
Spec §5.1 supplier-webhook endpoint. SupplierWebhookController tests
переходят с 405 на 8/8 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:39:43 +03:00
Дмитрий e41c8f5aef feat(http): SupplierWebhookController — platform-wide /api/webhook/supplier/{secret}
Defense-in-depth: secret (≥32 chars system_setting) + IP allowlist (CIDR).
Несовпадение → 404. UNIQUE vid → 200 OK на дубль (idempotency).

Тесты пока FAIL (route регистрируется в Task 7 — пишем "красные" тесты заранее
для TDD-цикла).

Spec §5.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:38:18 +03:00
Дмитрий 605c457c49 fix(jobs): RouteSupplierLeadJob — per-Project failure isolation + 2 tests
Code-review Important: один сбой Project не должен абортить routing для
остальных tenant'ов (sharing-model). + try/catch + Log::warning +
RuntimeException только если ВСЕ projects упали.

+ 2 новых теста: mixed routing (1 dup из 3 + 2 clean) и partial failure
(soft-delete tenant в середине loop'а).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:30:06 +03:00
Дмитрий c519044319 feat(jobs): RouteSupplierLeadJob — sharing-model deal-copies + charge per tenant
Распределяет supplier_lead по eligible Liderra-проектам через LeadRouter.
Для каждого: транзакция с SET LOCAL app.current_tenant_id, lockForUpdate,
DuplicateDetector check, balance_leads--, delivered_today/month++,
BalanceTransaction, ActivityLog, NotificationService::notifyNewLead.

Spec §5-§6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:05:21 +03:00
Дмитрий 9540090bba fix(services): LeadRouter — Europe/Moscow timezone for workday-mask check
Code-review Important: Carbon::now() resolved against process TZ (UTC quirk
из memory). Reset cron в 00:00 МСК — mismatch вызвал бы off-by-one на
рубеже полуночи. Тест синхронизирован (now('Europe/Moscow')) — иначе
mismatch test/service near midnight. + комментарий про unreachable default
match arm (защищён на DB-уровне через chk_supplier_projects_platform).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:56:15 +03:00
Дмитрий 31929c9dd2 feat(services): LeadRouter — eligible Liderra projects matcher
Plan 2/5 Task 4 — sharing-model routing (spec §6): для входящего лида
возвращает Collection<Project> учитывая platform FK + active + workdays +
region (PhonePrefixService::phoneMatchesRegions) + delivered_today <
COALESCE(effective_daily_limit_today, daily_limit_target) +
tenant.balance_leads > 0. Сортировка created_at ASC, id ASC (детерминированно).

Параллельно расширил Project model fillable/casts на delivered_today
(колонка добавлена в schema v8.18 Plan 2 Task 1, но Project::class не
обновлён — без этого тесты Mass-Assignment'а ломались).

Покрытие: 9 it-blocks (sharing across tenants, paused, workdays, daily quota,
fallback to daily_limit_target, region filter, balance_leads zero, FK routing
по platform, deterministic sort). DatabaseTransactions context + set_config
(session-scoped) для очистки app.current_tenant_id — sharing-flow работает
поверх N tenant'ов, RLS bypass через postgres BYPASSRLS на dev.

PHPStan: 0 errors. Pint: clean. Pest: 9/9 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:50:28 +03:00
Дмитрий 5f08459615 feat(services): PhonePrefixService — phone → federal district bit (MVP)
Маппинг мобильных + 30+ ABC-кодов городов на 8 ФО RF (synchronized
с projects.region_mask битами). Мобильные → all-districts (255).
Полный Минсвязи справочник отложен на Plan 5+.

Spec: §6 step 2 routing geo-filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:36:43 +03:00
Дмитрий aa37f4cbed feat(models): SupplierLead model + factory (raw-payload incoming webhooks)
SaaS-level модель для supplier_leads (Plan 2/5 Task 2).
belongsTo(SupplierProject) + array cast на raw_payload + datetime *_at.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:24:45 +03:00
Дмитрий f5c7c29301 test(schema): add UNIQUE-vid behavioral test + intent-clear placeholder assert
- Add it-block для UNIQUE INDEX idx_supplier_leads_vid_unique:
  две INSERT с одинаковым vid → вторая бросает QueryException
  (прямой behavioral тест webhook-идемпотентности).
- Replace tautological strlen($secret->value) >= 16 на toBe('__SET_ON_DEPLOY__')
  — было проверкой литерала, который мы сами и записали; теперь intent-clear
  assertion того, что seed кладёт placeholder. Реальная strength-валидация
  secret'а — дело deploy-time validator'а, вне scope Plan 2.
- Add uses(DatabaseTransactions::class) — приводит файл к проектному
  pattern (см. WebhookReceiveTest, TenantModelsTest, SetTenantContextTest).
  Без него новый INSERT с vid=999000111 коллидил бы при re-run, т.к.
  Pest.php применяет RefreshDatabase глобально не делает (закомментирован).

Code-review fixes for Plan 2 Task 1.
2026-05-10 18:16:19 +03:00
Дмитрий fb55bfdd1f feat(db): supplier_leads + projects.delivered_today + 2 system_settings (v8.18)
Plan 2/5 Task 1 — слой данных для supplier-webhook flow.

- supplier_leads (SaaS-level, без RLS) — raw payload incoming webhook'ов
- projects.delivered_today — дневной счётчик для проверки daily quota
- system_settings: supplier_webhook_secret + supplier_ip_allowlist

Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §5-§6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:07:31 +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
Дмитрий 001d7819bf fix(supplier): close code-review BLOCKER+WARN — FK + CHECK + resolver guard
Code-review subagent (CV.12 в Plan 1) нашёл 1 BLOCKER + 2 actionable WARNINGs:

1. **BLOCKER** — projects.supplier_b{1,2,3}_project_id были голыми BIGINT без
   REFERENCES, вопреки явному комментарию «FK добавятся в Task 2». Task 2
   создал supplier_projects, но FK на projects не вернул. Можно было записать
   произвольный BIGINT в эти колонки.
   Fix: ALTER TABLE projects ADD CONSTRAINT … FOREIGN KEY … ON DELETE SET NULL
   для всех трёх + 3 partial index (WHERE NOT NULL) для FK lookup.

2. **WARNING** (Project-level B1+SMS guard) — CHECK существовал только на
   supplier_projects; Project::create(['signal_type'=>'sms','supplier_b1_project_id'=>…])
   проходил вопреки spec §2.2 «B1 не поддерживает СМС».
   Fix: ADD CONSTRAINT chk_projects_b1_not_for_sms
   CHECK (signal_type <> 'sms' OR supplier_b1_project_id IS NULL).

3. **WARNING** (resolver collision) — SupplierProjectResolver::resolveOrStub
   firstOrCreate на (platform, unique_key) без signal_type → при коллизии
   unique_key возвращал чужую запись с другим signal_type без ошибки.
   Fix: после firstOrCreate проверяется match signal_type, иначе DomainException.
   +1 тест на collision.

Schema bumped v8.16 → v8.17. Метрики: 60 таблиц / 111 индексов (+3) / 39 RLS.
Pest: 500/498 passed (+1 collision test). Larastan 0 errors. Pint clean.

Spec: §2.1, §2.2
Plan: Task 2 (закрытие code-review CV.12)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:32:02 +03:00
Дмитрий 44a1b21421 feat(rules): add Signal validators (Domain, Phone, SmsSender) with comprehensive datasets
- app/Rules/SignalIdentifier/DomainIdentifier.php — regex
  ^[a-z0-9-]+(\.[a-z0-9-]+)+$ (нижний регистр, без протокола/пути).
- app/Rules/SignalIdentifier/PhoneIdentifier.php — regex ^7\d{10}$
  (11 цифр, начинается с 7).
- app/Rules/SignalIdentifier/SmsSenderRule.php — 1-30 символов
  [A-Za-z0-9_-]; отвергает 11-значные номера (поставщик блокирует
  физический телефон в роли отправителя — alert "Важно!" в форме
  создания SMS-проекта).
- tests/Feature/Rules/SignalValidatorsTest.php — 24 теста с datasets:
  • Domain: 4 valid + 6 invalid (case, no-TLD, spaces, protocol, path, double-dot)
  • Phone: 3 valid + 6 invalid (8-prefix, length, plus, spaces, letters)
  • SmsSender: alpha+numeric short, 11-digit blocked, length>30 blocked,
    empty (с required), special chars blocked

Quirk: Laravel skips non-implicit rules для пустых строк. Тест empty
использует связку 'required' + правило (как в реальном FormRequest).

Pest: 499 / 497 passed / 2 skipped (473 + 24 новых = 497).
Larastan: 0 errors. Pint passed.

Spec: §3.1
Plan: Task 12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:05:19 +03:00
Дмитрий 59c57f9ec0 feat(services): add SupplierProjectResolver (resolveOrStub with B1+SMS guard)
- app/Services/SupplierProjects/SupplierProjectResolver.php — резолвер
  по ключу (platform, signal_type, unique_key). Возвращает existing supplier_project
  или создаёт pending stub (физическая sync произойдёт в SyncSupplierProjectsJob, Plan 3).
- Защита 1: InvalidArgumentException на платформу не из {B1,B2,B3}.
- Защита 2: InvalidArgumentException на signal_type не из {site,call,sms}.
- Защита 3: DomainException на B1+SMS combo (chk_supplier_projects_b1_not_for_sms).
- tests/Feature/Services/SupplierProjectResolverTest.php — 6 тестов:
  resolve existing, create stub, idempotency (no duplicates), B1+SMS guard,
  invalid platform, invalid signal_type.

Pest: 475 / 473 passed / 2 skipped (467 + 6 новых = 473).
Larastan: 0 errors. Pint passed.

Spec: §2.2, §4.1
Plan: Task 11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:02:25 +03:00
Дмитрий 99afcbc25c feat(models): extend Project with signal_type, sms_senders, supplier_b1/b2/b3 relations + scopes
- app/Models/Project.php — добавлены fillable+casts для supplier integration:
  signal_type, signal_identifier, sms_senders (jsonb array), sms_keyword,
  delivered_in_month, supplier_b{1,2,3}_project_id.
  + supplierB1/B2/B3() BelongsTo relations на SupplierProject (sharing-model).
  + scopeActiveOnDay($iso) — bitmask проверка по delivery_days_mask
    (bit 0 = Mon, bit 6 = Sun; ISO=1 → 1<<0 = 1; ISO=7 → 1<<6 = 64).
  + scopeForSignal($type, $identifier) — фильтр по сигналу (для роутинга в Plan 2).
- database/factories/ProjectFactory.php — defaults null/0 для новых полей
  (CHECK constraints не нарушаются: signal_type IS NULL → остальные опциональны).
  + state-методы asSiteSignal($domain), asCallSignal($phone), asSmsSignal($senders, $keyword).
- tests/Feature/Models/ProjectExtensionsTest.php — 6 тестов: signal_type fillable,
  sms_senders array cast + sms_keyword, SMS без keyword, supplierB1/B2/B3 relations,
  scopeActiveOnDay (bitmask Mon/Sat), scopeForSignal (3 сигнала + edge-case).

Pest: 469 / 467 passed / 2 skipped (461 + 6 новых = 467, с retry на transient
PG connection issues — на параллельных тестах с testing_rls_user GRANT тяжёл).
Larastan: 0 errors. Pint passed.

Spec: §2.1
Plan: Task 10

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:59:53 +03:00
Дмитрий 084a952bfa feat(models): add SupplierSyncLog model + factory (audit trail для AJAX-sync)
- app/Models/SupplierSyncLog.php — fillable + casts (jsonb arrays + datetime)
  + supplierProject() BelongsTo relation (nullable, ON DELETE SET NULL —
    лог переживает удаление supplier-проекта для audit-trail).
  $timestamps = false (только created_at, без updated_at — append-only)
- database/factories/SupplierSyncLogFactory.php — реалистичные действия из enum
- tests/Feature/Models/SupplierSyncLogTest.php — 4 теста: factory,
  supplier_project relation, jsonb array casts, nullable FK lifecycle

Pest: 463 / 461 passed / 2 skipped (457 + 4 новых = 461).
Larastan: 0 errors. Pint passed.

Spec: §4.3
Plan: Task 9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:53:55 +03:00
Дмитрий 31f813315d feat(models): add LeadCharge ledger model + factory + relations to Tenant/Deal
- app/Models/LeadCharge.php — fillable + casts (datetime + integer)
  + tenant() BelongsTo relation
  + deal() BelongsTo relation (по deal_id, без deal_received_at — composite PK
    на партиционированной обеспечивается БД-уровнем через FK)
  + accessor priceRubles (kopecks → float)
- database/factories/LeadChargeFactory.php — НЕ создаёт реальный Deal автоматически
  (composite FK requires (deal_id, deal_received_at) пары); тесты с FK-целостностью
  явно создают Deal::factory() и передают пару в state()
- tests/Feature/Models/LeadChargeTest.php — 4 теста: factory, tenant relation,
  deal relation, priceRubles accessor. testing_rls_user setup в beforeEach
  для проверки RLS context из не-superuser контекста.

Quirk: SET LOCAL app.current_tenant_id НЕ принимает параметрическое связывание PG —
используем string interpolation с {$tenant->id} как в RlsSmokeTest pattern.

ide-helper:models -W -M -N синхронизировал docblocks (WebhookDedupKey).

Pest: 459 / 457 passed / 2 skipped (453 + 4 новых = 457).
Larastan: 0 errors. Pint passed.

Spec: §7.4
Plan: Task 8

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:52:10 +03:00
Дмитрий 71e38ee0a9 feat(models): add PricingTier model with kopecks→rubles accessor + current() snapshot
- app/Models/PricingTier.php — fillable + casts (date, boolean, integer)
  + accessor priceRubles (kopecks → float rubles)
  + scopeActive (is_active=true AND effective_from <= today)
  + static current() — keyed by tier_no Collection<int, PricingTier>
- database/factories/PricingTierFactory.php — реалистичные ступени (300/700/1000/.../null)
- tests/Feature/Models/PricingTierTest.php — 4 теста: factory, accessor,
  scopeActive, current() snapshot всех 7 ступеней

ide-helper:models -W -M -N перегенерил docblocks (WebhookDedupKey synced
после schema v8.16).

Pest: 455 / 453 passed / 2 skipped (449 + 4 новых = 453).
Larastan: 0 errors. Pint auto-fix.

Spec: §7.2
Plan: Task 7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:48:00 +03:00
Дмитрий 62aa55f033 feat(models): add SupplierProject Eloquent model + factory + tests
- app/Models/SupplierProject.php — fillable + casts (jsonb arrays + datetime)
  + scopes: active() (inactive_since IS NULL), staleSince(N days),
  forSignal(signal_type, unique_key)
- database/factories/SupplierProjectFactory.php — корректно учитывает
  chk_supplier_projects_b1_not_for_sms (B1 не порождает SMS-проекты)
- tests/Feature/Models/SupplierProjectTest.php — 6 тестов: factory,
  array casts (workdays + regions), scopeActive, scopeStaleSince,
  scopeForSignal (3 платформы на один домен — UNIQUE (platform,unique_key))

ide-helper:models -W -M -N перегенерил docblocks для 4 существующих моделей
(SaasAdminAuditLog, SystemSetting, UserRecoveryCode, ImpersonationToken) —
синхронизировал @property после schema v8.16.

Pest: 451 / 449 passed / 2 skipped (было 443+6 новых от Task 6 = 449).
Larastan: 0 errors. Pint: passed.

Spec: §2.2
Plan: Task 6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:59:39 +03:00
Дмитрий 757f963929 fix(db): consolidate Plan 1 Tasks 1-5 into schema.sql (project convention)
Project convention использует schema.sql как single source of truth + один
load_initial_schema migration вместо incremental migrations. Мои 5 incremental
миграций конфликтовали с migrate:fresh: load_initial_schema применял
обновлённый schema.sql v8.16, а потом 000001-000005 пытались добавить уже
существующие колонки/таблицы (`signal_type already exists`).

Изменения:

- Удалены 5 incremental миграций 2026_05_10_00000{1..5}_*.
  Все DDL уже в schema.sql (v8.11 → v8.16, 4 commits 2ebe000/9b99d81/
  b08e1ed/7f694f7/9cf380f).
- В schema.sql lead_charges FK на partitioned deals(id, received_at)
  вынесен в самый конец файла (после section 5 с deals), DEFERRABLE
  INITIALLY DEFERRED. Иначе DB::unprepared() выдаёт "deals не существует"
  на load.
- Тесты в tests/Feature/Integration/ остаются — они проверяют
  структурные свойства (column existence, constraint name, RLS via
  pg_class) через information_schema, не зависят от того как именно
  schema создалась.

Verification:
- migrate:fresh OK на обеих БД (liderra + liderra_testing)
- Pest: 445 tests / 443 passed / 2 skipped / 0 failed
  (было 421 baseline + 24 новых для Tasks 1-5 = 443; +2 skipped browser tests)
- Larastan: 0 errors
- Pint: passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:50:39 +03:00
Дмитрий 9cf380f170 feat(db): create supplier_sync_log audit table (SaaS-level, append-only)
Append-only journal AJAX-синхронизаций с поставщиком crm.bp-gr.ru.
Используется для retry, отладки rt-project-* и алертов менеджеру.

- 9 columns: id, supplier_project_id (nullable FK SET NULL),
  action, request_payload (jsonb), response_body (jsonb),
  http_status, error_message, duration_ms, created_at
- 1 CHECK chk_supplier_sync_log_action
  (create/update/delete/disable/session_refresh)
- 3 индекса: supplier_project_id, action, created_at
- REVOKE ALL FROM crm_app_user (DO $$ conditional)
- No RLS (SaaS-level)

Spec: §4.3
Plan: Task 5
Test: 4/4 passed (table, action enum, FK, no RLS).

Schema v8.15 → v8.16. Метрики: 60 таблиц (+1) / 108 индексов (+3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:42:46 +03:00
Дмитрий 7f694f78e0 feat(db): create lead_charges ledger (tenant-scoped RLS, FK to partitioned deals)
Append-only ledger списаний за каждый доставленный лид. Tenant-scoped
с RLS tenant_isolation (ENABLE + FORCE + USING/WITH CHECK).

- 8 columns: id, tenant_id, deal_id, deal_received_at, tier_no,
  price_per_lead_kopecks, charged_at, created_at
- Composite FK lead_charges_deals_fk(deal_id, deal_received_at) →
  deals(id, received_at) DEFERRABLE INITIALLY DEFERRED
  (deals партиционирована — DEFERRABLE для атомарного deal+charge)
- 2 индекса: (tenant_id, charged_at), (deal_id, deal_received_at)
- RLS на (tenant_id = current_setting('app.current_tenant_id')::bigint)
- GRANT SELECT, INSERT для crm_app_user (без UPDATE/DELETE — append-only)

Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §7.4
Plan: docs/superpowers/plans/2026-05-10-supplier-foundation-plan.md Task 4

Test: 3/3 passed (table exists, composite FK to deals, RLS enforces
tenant isolation via testing_rls_user role).

Schema v8.14 → v8.15. Метрики: 59 таблиц (+1) / 105 индексов (+2) /
39 RLS (+1) / функции/триггеры без изменений.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:38:17 +03:00
Дмитрий b08e1edb33 feat(db): create pricing_tiers table (7-step volume billing, kopecks integer) 2026-05-10 12:54:02 +03:00
Дмитрий 9b99d81deb feat(db): create supplier_projects table (SaaS-level aggregate, B1/B2/B3 platforms)
Plan 1/5 Task 2 — SaaS-level агрегатная сущность для проектов у поставщиков.
Несколько Лидерра-tenant'ов могут шарить один supplier_project (sharing-model
spec §2.3): для site/call — по domain/phone; для sms — по (sender, keyword)
на B2 или (sender) на B3. RLS НЕ применяется (таблица не tenant-scoped),
defense-in-depth через REVOKE ALL FROM crm_app_user.

Колонки: platform, signal_type, unique_key (TEXT), supplier_external_id,
current_limit, current_workdays/regions (jsonb), sync_status (pending/ok/failed),
last_synced_at, inactive_since (TTL 180 дней), timestamps.

CHECK constraints (chk_supplier_projects_*):
- platform IN (B1, B2, B3)
- signal_type IN (site, call, sms)
- sync_status IN (pending, ok, failed)
- NOT (platform=B1 AND signal_type=sms) — B1 не поддерживает СМС

Indexes: UNIQUE(platform, unique_key); btree на sync_status, inactive_since.

Тесты: 6/6 (table+columns / unique / platform CHECK / sync_status CHECK / no RLS / no privileges).
Schema: v8.12 → v8.13. Метрики: 56→57 таблиц / 98→101 индексов; RLS/функции/триггеры без изменений.
2026-05-10 12:47:25 +03:00
Дмитрий 2ebe000271 feat(db): extend projects for supplier integration (signal_type, identifier, sms_senders, sms_keyword, delivered_in_month, b1/b2/b3 FK placeholders)
Plan 1/5 Task 1. Adds 8 columns + 3 CHECK constraints + 1 composite index
to projects table for supplier integration foundation. Schema bumped
v8.11 to v8.12. Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §2.1.

7/7 Pest tests pass; rollback verified.
2026-05-10 12:40:02 +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
Дмитрий e5848bddec feat(tooling): Trivy CI workflow prep — disabled until YC Docker (#26) 2026-05-10 08:45:17 +03:00
Дмитрий 7123123b48 chore(claude): allow npm run sast in settings.json (#25) 2026-05-10 08:43:52 +03:00
Дмитрий 40125ba5a5 feat(tooling): Semgrep MCP server in .mcp.json (#25) 2026-05-10 08:42:25 +03:00
Дмитрий 53fb1ec27e feat(ci): Semgrep SAST workflow — push/PR to main (#25) 2026-05-10 08:40:52 +03:00
Дмитрий 5983cc17e3 feat(tooling): Semgrep config + npm run sast script (#25) 2026-05-10 08:39:34 +03:00
Дмитрий 3112584ab8 chore(deps): add Dependabot config — npm×2 + composer weekly (#27) 2026-05-10 08:36:36 +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
Дмитрий b912724cf7 chore(frontend): Sprint 4 Phase C — bundle analyzer + dead-code cleanup (audit O-refactor-06)
- rollup-plugin-visualizer + script `npm run build:analyze` (env BUILD_ANALYZE=1)
  Output: storage/bundle-analyze.html (gzip + brotli sizes), gitignored.
- cross-env установлен для Windows-совместимости env-переменной build:analyze.
- knip + knip.config.ts (entry app.ts + router/index.ts; ignore *.story.vue + tests/).
  ВАЖНО: knip падает с oxc-parser ArrayBuffer fail на этой машине
  (Windows quirk feedback memory) — конфиг сохранён для будущих запусков на
  Linux/macOS CI. Dead-code search выполнен вручную через grep по composables/.
- Удалены 4 unused exports + 4 private helpers, инициируемых только ими:
  * mockReports.ts: MOCK_JOBS, QuotaInfo (interface), MOCK_QUOTA
  * reportsMapper.ts: reportTypes()
  * mockTenantDetail.ts: expandTenantDetail() + 4 SAMPLE_* consts
    (SAMPLE_USERS/SAMPLE_PROJECTS/SAMPLE_BALANCE_HISTORY/SAMPLE_ACTIVITY)
  * useCsvDownload.ts: csvEscape() — снят `export` (используется внутри файла)

ESLint + vue-tsc + Vitest 416/416 + build — зелёные.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:59:58 +03:00
Дмитрий 79ff60ffd9 refactor(frontend): Sprint 4 Phase B/3 — split 2 utility views (audit O-refactor-04 хвост)
ErrorView 320→178 (+ ErrorBrand 54 + ErrorIllustration 31 + ErrorActions 55 + ErrorMeta 102).
DashboardView 302→84 (+ DashboardPageHead 65 + DashboardKpiRow 97 + DashboardBalance 124).

State (config, errorCode в ErrorView; range/kpis/balance в DashboardView) остаётся
в parent ради единого route.meta-driven flow + future API-fetch'а (Phase B/1 паттерн).
DashboardPageHead использует Vue 3.5 defineModel<T>() для двусторонней привязки range.
Sub-components читают только props — без Pinia stores (mock-data flow).

Все sub-components <250 строк (acceptance threshold). Shell line counts: 178/84.

ЗАМЕЧАНИЕ по acceptance «0 components >300»: НЕ закрыто полностью. 2 файла остались
выше порога — DealsView 560 + DealDetailDrawer 386. Зафиксировано в Sprint 3 Phase C
commit 6c2f0ce: bulk-action функции (applyBulkStatus/applyBulkDelete/applyBulkExport/
undoBulkDelete/applyBulkRestoreFromTrash) и comment/reminders fetch экспонируются
через defineExpose в Vitest-тестах напрямую — дальнейшая декомпозиция требует
изменения тест-контракта (отдельным flow, не вошло в Sprint 4 Phase B).

Phase B/3 закрывает 8/12 audit-кандидатов O-refactor-04: 3 в Sprint 3 Phase C
(Top-3) + 5 в Sprint 4 Phase B/1+B/2 (admin/layout/billing/security/reminders) +
2 в B/3 (errors/dashboard). Оставшиеся: 2 крупных deals-view (defineExpose-blocked)
+ ImpersonationDialog уже <300 органически.

Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 989 ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:53:09 +03:00
Дмитрий 849bc73290 refactor(frontend): Sprint 4 Phase B/2 — split 3 user views (audit O-refactor-04 хвост)
BillingView 416→114 (+ BalanceCard 155 + TransactionsTable 113 + InvoicesTable 90
+ billingFormatters 51 composable: formatPlain/formatCost/statusChipColor/
statusLabel/formatLabel/formatIcon/txAmountClass).
SecurityTab 354→39 (+ ChangePasswordCard 17 + TwoFactorCard 218 + RecoveryCodesCard 104
+ SessionsTable 66; auth-store читается напрямую в каждом sub-component).
RemindersView 345→183 (+ RemindersFilters 51 + RemindersList 173;
ReminderDialog уже отдельный с прошлой фазы — служит как ReminderForm).

State (`activeTab`, `editingReminder`, `deletingReminderId` в RemindersView)
остаётся в parent ради единого reload-flow + confirm-dialog'ов. Auth-store
читается напрямую в TwoFactorCard/RecoveryCodesCard через useAuthStore() —
без prop-drilling. Reminders-store читается напрямую в RemindersFilters/
RemindersList.

Все sub-components <250 строк (acceptance threshold). 3 view-shells: 114/39/183.

Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 968 ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:46:14 +03:00
Дмитрий 30ef61dff8 refactor(frontend): Sprint 4 Phase B/1 — split 3 admin/layout views (audit O-refactor-04 хвост)
3 view'а с >300 строк разделены на shell + sub-components:

AdminTenantsView 377→155 (+ TenantsStatsHeader 82 / TenantsFilters 93 / TenantsTable 116).
AdminTenantDetailView 436→109 (+ TenantDetailHeader 158 / TenantDetailTabs 176
+ adminTenantDetailFormatters 43 composable).
AppLayout 466→78 (+ AppSidebar 155 / AppTopbar 269; R0.6 hard-стоп снят
явным запросом заказчика 10.05.2026).

State (filterStatuses, tenantsState, activeTab, tenant, drawerOpen) остаётся
в parent view'ах ради `defineExpose`-контракта Vitest тестов. Sub-components
читают Pinia stores напрямую (auth + notifications + reminders) — без
prop-drilling.

AppTopbar 269 строк <300 — acceptance threshold выдержан (можно дальше split на
NotificationsDropdown + UserMenu в отдельном flow, не критично).

Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 1.17 сек.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:38:08 +03:00
Дмитрий cf1aabb32e chore(cspell): добавлены UPM, gsap, LLM в словарь проекта
Сопровождает v1.83 docs-патч (PSR_v1 v1.4 формализация UPM + 21st Magic MCP).
Без этих записей pre-commit hook cspell блокирует коммит на CLAUDE.md /
PSR_v1 / Pravila / Tooling / CHANGELOG_claude_md, где термины используются.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:34:15 +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
Дмитрий f77c91d5fa feat(backend): Sprint 4 Phase A — keyset pagination в DealController::index (audit O-perf-04)
Опциональный query-параметр `cursor` (base64-encoded JSON {r:received_at, i:id}).

При cursor — keyset через PG row constructor `(received_at, id) < (?, ?)`
с использованием существующего индекса (received_at DESC, id DESC).
O(1) на любой глубине, без COUNT(*) (total не возвращается в keyset-режиме).

Без cursor — backward-compat OFFSET-путь: total + offset для существующего
frontend. Оба режима возвращают next_cursor (NULL = последняя страница).

Trick "+1 fetch" — узнаём про следующую страницу одним SELECT'ом без COUNT.

3 новых Pest-теста: keyset-навигация через cursor, 422 на невалидный cursor,
next_cursor flow. Pest 421/421 (419 + 2 skipped browser, +3 от 418 baseline).

phpstan-baseline.neon регенерирован: +2 occurrences pattern
`received_at?->toIso8601String()` (cursor build) + 7 occurrences тестовых
helper-properties Pest TestCall — все известные ignored patterns, не реальные ошибки.

Frontend integration в useDealsList/DealsView — отдельным шагом
(не блокирует backend deploy, OFFSET путь жив).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:29:54 +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