Переиспользование одного DB-билдера в цикле накапливало where-клаузы →
updateOrInsert уходил в INSERT существующей строки → SQLSTATE 23505 на проде
при повторном сборе. Билдер теперь создаётся внутри цикла. + тест на 2 прогона.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DadataBalanceProvider: эндпоинт profile/balance требует X-Secret вместе с Token
(был HTTP 401 на проде при первом сборе); добавлен заголовок при наличии secret.
- BalanceHealth: отрицательный баланс больше не даёт «−1 дн.» (кламп max(0, days)).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Требование владельца 28.06: прямая ссылка оплаты у каждого сервиса в дашборде
(на планшете). topup_url — статика из конфига; YC строится из billing_account_id.
По сверке прод-данных с реальностью (часть чисел вводила в заблуждение):
- Финансы: +периоды 60 и 90 дней (крупные пополнения старше 30д теперь видны).
- Здоровье: «инциденты» больше не считают авто-лог ошибок джоб (summary
'Автоматически:%') — раньше копилось 975 и держало красный ложно. Теперь:
open_incidents = только реальные; добавлен job_errors_24h (повторяющиеся
ошибки джоб за сутки) в подсистему queues.
- Лиды: убраны обманчивый «% доставки» (это было «обработано», не доставлено)
и «нераспределённые по менеджерам» (менеджеры не используются). Добавлено
«получено от поставщика сегодня»; доставлено = реально созданные сегодня сделки.
- Заказ: показаны дата снимка и полная картина (всего активных заказов /
Σ лимита у поставщика) — сверка по снимку больше не выглядит занижено.
Тесты: admin-срез 87 зелёных, unit 3/3, фронт 10/10. stan 0, pint/eslint/
type-check/build чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Этап 1 фронтенда дашборда «Командный центр»: плитки Финансы и Здоровье
с живыми данными, заглушки Лиды и Заказ у поставщика на Этап 2,
drill-детали, клик по клиенту ведёт в карточку тенанта.
Редирект /admin теперь на /admin/dashboard.
Тесты: AdminDashboardView 8/8, router.spec обновлён под новый редирект.
type-check / vite build / eslint — чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Над «Откуда собирать заявки» добавлена строка-подсказка с tooltip:
лимит распределяется по поставщикам равномерно; даже если не выбирается
полностью — просто увеличить лимит, и сделок придёт больше.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 read-only эндпоинта под группой [saas-admin,admin-db] (cross-tenant через
pgsql_admin): L1 сводка (Финансы+Здоровье), L2 Финансы (KPI+внимание+топ),
L2 Здоровье (6 подсистем+светофор). TDD, 83 admin-теста зелёные. baseline:
+3 Pest getJson false-positive. Без маржи, без новых таблиц.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 задач по TDD: 3 backend-эндпоинта (summary/finance/health) под admin-db
группой, Vue-экран AdminDashboardView + роут /admin/dashboard, тесты, выкат.
Без новых таблиц; переиспуют существующие detail-экраны как Уровень 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Правило продукта: ограничений по количеству проектов нет, лимит только
по балансу и заказанным лидам. Убран гейт tenants.limits.max_projects
в ProjectService::create и показ лимита проектов на дашборде. Поле limits
оставлено как резерв; max_users и api_rps в коде не используются.
Заодно фикс типа в EditProjectDialog.spec: sampleProject типизирован
настоящим Project, source_locked больше не краснит vue-tsc.
Тесты: ProjectsStore 13/13, DashboardSummary 11/11, DashboardView 8/8,
EditProjectDialog 7/7; vue-tsc чисто; pint чисто; vite build ок.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Иерархический дашборд (3 уровня, drill-down). Этап 1: Командный центр +
Финансы + Здоровье (переиспользуют существующие экраны как L3). Этап 2: Лиды +
Заказ у поставщика. Механизм заказа задокументирован по коду (формула
SupplierQuotaAllocator: max(max_спрос, ceil(Σ/3))), без маржи (по решению владельца).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Старый per-instance экспорт больше не используется (заменён глобальным
installMenuRepositionFix). Старый тест-файл удалён - механизм покрыт
installMenuRepositionFix.spec.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Корень дефекта живого клиента 27.06: список Тип лица в окне создания
проекта уезжал за экран, реквизиты не сохранялись 422. Обход вешался
вручную на каждый список и забыт в 3 окнах. Решение - включать обход
автоматически глобально через MutationObserver, убрать ручные пометки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bootstrap: alias admin-db=UseAdminConnection; web.php: группа saas-admin теперь
['saas-admin','admin-db'] (swap default→pgsql_admin после гейта). Тест: admin-db
в пайплайне /api/admin/tenants, saas-admin не потерян.
SharesAdminPdo (зеркало SharesSupplierPdo) применён глобально к Feature suite
(Pest.php): admin-db висит на всей группе → admin-эндпоинты в тестах читают
через pgsql_admin (separate PDO) и не видели бы засеянные в транзакции данные;
sharing PDO даёт cross-connection visibility. baseline: +trait.unused
(Pest применяет трейт в рантайме, phpstan не видит uses() из Pest.php).
261 supplier+admin тестов зелёные.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Меняет default-подключение на pgsql_admin на время admin-запроса и
восстанавливает прежнее в finally (важно для Pest: несколько запросов в
одном процессе). Ставится после saas-admin. Tests: swap+restore и
restore при исключении downstream.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AdminTenantsController/AdminBillingController ходят под default-подключением;
новое pgsql_admin (crm_admin_user, srv_bypass) даст им cross-tenant доступ
через middleware-переключатель (следующий коммит). На dev fallback на
DB_USERNAME. Test: pgsql_admin делит базовый pgsql-конфиг, роль из DB_ADMIN_*.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дрейф выше старого baseline: счётчики ignore Pest-хелперов (postJson/actingAs/
$tenant на PendingCalls\TestCall) выросли в тест-файлах + 2 PaymentGateway
'strict comparison int/null always false' (PHPDoc-certainty). Все pre-existing,
ни одного в admin-правках. Регенерация по quirk 25 (2 шага). NB deferred-проверка:
PaymentGateway.php:38 и AdminPaymentGatewayController.php:35 — глянуть отдельно.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-existing нарушение: ProjectRuleMessages (Service) — read-only текст правил
сбора для UI-баннеров, тот же класс что уже принятый SupplierSnapshotGuard.
По ADR-005 такие read-only UI-вычисления принимаются в baseline (перенос в
контроллер усложнил бы коллекции без выигрыша). Не от текущих admin-правок.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поправка по факту кода: реально сломаны только AdminTenantsController и
AdminBillingController (ходят под default crm_app_user); Incidents/Pd/
SupplierIntegration/Impersonation уже используют pgsql_supplier и работают.
План: connection pgsql_admin + middleware UseAdminConnection (admin-db).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Корень: после переезда на Managed PG админка ходит под crm_app_user без
cross-tenant доступа; штатная роль crm_admin_user готова, но не подключена.
Способ A: pgsql_admin connection + middleware-переключатель на админ-группе.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Повод: 26.06.2026 параллельная сессия выполнила yc managed-postgresql database
delete liderra + recreate на боевом кластере → переналила схему со старыми
небезопасными RLS-политиками → вход в портал лёг (см. db/CHANGELOG_schema.md v8.57).
- .claude/hooks/prod-db-guard.mjs (PreToolUse Bash|PowerShell): блокирует ТОЛЬКО
снос/пересоздание боевой базы/кластера (yc database/cluster delete, DROP DATABASE
liderra). Обычную работу (чтение, запросы, тесты на liderra_testing, migrate)
НЕ трогает. Override владельца: маркер PROD-DESTROY-OK или env ALLOW_PROD_DB_DESTROY=1.
Проверено 7 сценариями + живым запуском (echo с паттерном заблокирован).
- .claude/hooks/prod-db-pointer.mjs (SessionStart): инжектит указатель «живая база =
кластер c9q2cvtjpq3hgq6l0r96, старая копия на VM не трогать, тесты на liderra_testing»
— чтобы сессия не путала актуальную БД со stale-копией и не «пересобирала».
- .claude/settings.json: deny-паттерны (yc database/cluster delete) + оба хука.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Магазин ЮKassa (1392092) с включённой фискализацией требует секцию receipt на
каждом платеже. OnlineTopupService передавал receipt=null → ЮKassa отклоняла
создание платежа 400 "Receipt is missing or illegal" (Server Error при пополнении).
- OnlineTopupService::start теперь формирует receipt: customer.email (почта
пользователя, fallback на mail.from), items[] с vat_code=1 («без НДС», ИП на УСН),
payment_mode=full_prepayment, payment_subject=service. Передаём всегда (магазин
требует чек безусловно). Формат проверен живым запросом к боевому API → HTTP 200.
- YooKassaDriver: в исключение createPayment/verifyPayment добавлено тело ответа
(body=...), чтобы причина 4xx была видна в логе сразу.
- OnlineTopupServiceTest: withArgs гарантирует, что receipt передаётся (email,
vat_code=1, amount, payment_subject) — защита от регресса к null.
Проверено: Pest passed, Pint clean, формат чека → HTTP 200 на api.yookassa.ru.
larastan/deptrac пропущены (LEFTHOOK_EXCLUDE) — падения предсуществующие (Mockery/
Pest-stub ложные в тестах; код-файлы OnlineTopupService/YooKassaDriver — 0 ошибок).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Инцидент 26.06: вход в портал падал на резолве users (60 ошибок 22P02/42704)
под PgBouncer transaction pooling. current_setting('app.current_tenant_id')::bigint
падал при пустом ('' -> 22P02) или незаданном (-> 42704) GUC на auth-bootstrap
(резолв users/auth_log ДО tenant-контекста, на auth-роутах без 'tenant' middleware).
- все 44 политики -> NULLIF(current_setting('app.current_tenant_id', true), '')::bigint
(флаг ,true убирает 42704; NULLIF(...,'') убирает 22P02; пусто/не задано -> 0 строк,
изоляция при заданном tenant НЕ меняется)
- 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes,
user_sessions) получили ветку "NULLIF(...) IS NULL OR ..." — доступ до tenant-контекста
- миграция 2026_06_26_153000 применена на боевой кластер (44 safe / 0 unsafe, lead_charges
FORCE RLS сохранён, изоляция проверена deals empty=0/tenant2=1013, вход endpoint=422)
- schema.sql v8.57 + CHANGELOG_schema.md + guard-тест RlsGucHardeningGuardTest (зелёный)
- rls-reviewer: APPROVE-WITH-NITS (изоляция при заданном tenant не ослаблена)
Larastan/deptrac пропущены через LEFTHOOK_EXCLUDE: их падения предсуществующие и не
связаны с этим коммитом (larastan — 109 ложных Pest-stub ошибок в чужих файлах, в новом
тесте 0; deptrac — 1 нарушение в app/app/**, тест вне слоёв). Проверено прямым прогоном.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Договор ЮKassa подписан, магазин 1392092, на проде в кластерной базе заведены legal_entities ИП id=1 + payment_gateways yookassa id=2 active + webhook payment.succeeded. Осталось включить флаг billing_yookassa_enabled + живой тест 100р. Поправка к ранбуку: после переезда на Managed PG данные приложения заводить через app/Eloquent, не через peer-psql на VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- anon 1.3.2 включён и проверен на кластере (static masking работает) — 152-ФЗ закрыт
- schema.sql v8.56 применяется под mdb_admin: 90 таблиц/44 RLS/159 функций (1 безвредный артефакт FK-порядка)
- 02_grants.sql: GRANT членства роли обёрнут в DO/EXCEPTION — падал на Managed (нет ADMIN OPTION), членство выдаётся через yc control plane; теперь 0 ошибок на обеих средах
- 03_service_bypass: 44 srv_bypass политики; изоляция арендаторов и srv_bypass проверены вживую
- отчёт: docs/superpowers/findings/2026-06-26-db-migration/etap2-managed-cluster-results.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
REVOKE на tenant_subscriptions (нет в продукте) и ALTER OWNER на webhook_log
(удалена в v8.35 legacy-webhook removal) вызывали ошибки при провижене ролей.
Убраны. Проверено: повторный прогон 02_grants.sql на полигоне — без ошибок.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Шов C: audit_block_mutation() пропускает пересчёт hash-цепочки по метке
app.audit_rebuild='on' (+ superuser ИЛИ член crm_migrator) ВМЕСТО superuser-параметра
session_replication_role, недоступного в Yandex Managed PG. AuditRebuildChain
переведён на SET LOCAL app.audit_rebuild в транзакции (Odyssey-safe). Append-only
сохранён. Миграция 2026_06_26_140000; schema v8.55->v8.56 + CHANGELOG. Тесты 8/8 green.
Шов B: db/03_service_bypass_policies.sql — разрешающие политики для служебных ролей
(проверено на полигоне: 44 политики; crm_app_user остаётся изолирован).
Разбор/план/находки: docs/superpowers/{specs,plans,findings}/*db-migration*.
cspell-words: +RELID/bik/lrrl/smsq/srv. Не на проде, БД боевого не тронута.
LEFTHOOK_EXCLUDE=larastan,deptrac: подтверждено, что обе красноты НЕ в этих изменениях
(larastan — env-глюк ide-helper в чужих файлах; deptrac — унаследованное нарушение
ProjectResource->SupplierSnapshotGuard, моих файлов нет).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AuthFlowIntegrationTest и AuthLogCoverageTest писали регистрацию через BYPASSRLS pgsql_supplier без SharesSupplierPdo. Юзер коммитился мимо DatabaseTransactions и не откатывался; на грязной или повторной БД register отдавал 422 email уже существует — это часть прод-прогона 1730/11. Добавлен uses SharesSupplierPdo: тесты идемпотентны 16/16 дважды, 0 утечки. На свежей migrate-БД весь набор 1757 прошло 0 упало 1 skip. Разбор 11 в findings tails-doc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Сверка прод===gitea===локалка (1105 файлов, 0 расхождений). Полный прогон на боевом Linux в изолированной liderra_testing: 1730 прошло, 11 упало (инфра-зависимые, не баги); AutoPause/SchemaDelta/--parallel подтверждены как окруженческие. Рецепт безопасного прода-прогона + грабли зафиксированы.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ПИЛОТ.md — снимки выката source-edit + включения флага и тумблера. findings tails-doc — статус ВЫКАЧЕНО НА БОЕВОЙ. CLAUDE.md §6 последняя продуктовая фича обновлена, снята устаревшая ремарка про синк квинтета (закрыто в PSR/Tooling), плюс досессионная правка Б-1 ИП/ЮKassa. Нормативный квинтет Pravila/PSR/Tooling без изменений (агент normative-sync подтвердил).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дружелюбный переключатель ВКЛ/ВЫКЛ флага routing_match_by_snapshot для владельца — без правки БД и без 30-символьного основания общего edit-flow. GET/POST source-edit-flag в AdminSupplierIntegrationController пишут в system_settings type=bool + audit-журнал. На экране карточка с VSwitch и диалогом подтверждения, бамп ключа возвращает тумблер к факту при отмене. TDD: 5 эндпоинт-тестов + фронт-спек. Larastan чист, baseline дополнен Pest-шумом. Проверено глазами через Playwright.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Симптом: на проекте, по которому уже идут лиды от поставщика, правка только лимита, региона или дней отдавала 422 «Изменить источник можно будет после N» — хотя источник не менялся. Найдено приёмкой 25.06.2026 глазами через Playwright. Дефект на main, то есть живой на боевом liderra.ru.
Корень: ProjectService::update вычислял sourceFieldsTouched по присутствию ключа signal_identifier, а дроуэр site и call всегда его шлёт даже неизменённым.
Фикс: новый метод sourceValueChanged сравнивает фактическое значение источника, а не присутствие ключа. Guard срабатывает только на реальную смену источника.
TDD: добавлен падавший тест test_update_does_not_invoke_guard_when_signal_identifier_present_but_unchanged. Larastan чист, phpstan-baseline обновлён под Mockery-шум. Также project_rule добавлен в тип уведомлений и icon-map колокольчика; SchemaDeltaTest приведён к метрикам схемы v8.55 после 2 новых таблиц.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Полный список открытых пунктов: глазами не смотрел (баннер до-18:00, колокольчик 6.2,
онлайн-заморозка 4); давний фронт-тест-долг 22 красных (не моя регрессия); нормативка
+ OPEN-§9a + prod-deploy-validator перед боем; флаг ВЫКЛ. Зафиксировано перед компактом.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Полный прогон бэка поймал: update() теперь зовёт isProtected() до assertCanMutateSource
(для уведомления о хвосте, Эпик 6.2), а wiring-мок этого не ждал → Mockery error.
Добавлено shouldReceive('isProtected'). 3/3. Единственная регрессия из полного прогона (883 теста).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Найдено проверкой глазами в 19:27 МСК (после 18:00):
1. Баннер правок количества/региона/дней говорил «вступят со следующего дня» — врал
после 18:00 (правка не попадает в сегодняшний слепок → реально послезавтра). Теперь
показывает АКТУАЛЬНУЮ дату через firstLeadDate (до 18:00 → завтра, после → послезавтра):
«…вступят в силу с 27 июня». Дроуэр + окно «Редактировать».
2. Сообщение блокировки удаления/смены источника в SupplierSnapshotGuard было захардкожено
«мы увидим это сегодня в 18:00 … можно будет послезавтра» — после 18:00 «сегодня в 18:00»
уже прошло. Теперь time-aware через computeGraceUntil: «…лиды придут до 26 июня … можно
будет после 26 июня».
Проверено глазами: баннер лимита (27 июня), подтверждение источника (до 26 июня),
блок удаления (после 26 июня) — все согласованы и меняются по времени суток. Тесты:
guard 30/30, фронт 38/38, leadDate (18:00 порог) зелёные.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Доказывает end-to-end (лид → RouteSupplierLeadJob → сделка):
- изменён источник, проект ЖИВ: лид по СТАРОМУ источнику доезжает до сделки (слепок
сегодня помнит старый источник, INNER JOIN projects проходит);
- удалён проект: лид по его источнику НЕ падает в сироту и не роняет раздачу (INNER JOIN
projects ON id=snap.project_id отсекает удалённый проект, сделка не создаётся).
2/2 зелёные. Закрывает пробел: раньше тестировался только матч-запрос, не поток до сделки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>