Дословный перенос продающей базы и механики движка в knowledge/aleksandra/playbook/
(13 блоков + крючок/лесенка + нюансы + механика + финал + красные флажки, метки цен
{{вилка}}/{{тарифы}}/{{цена_подбора}}/{{цена_изучения}} не раскрыты). Черновые карточки
ниш деревом (Медицина→стоматология, Финансы→займы под залог) — специфика ниш
стоматологии и займов под залог собрана на основе живой Sonar-справки из
scratchpad/otrasl_cache; без выдуманных цифр/процентов. Всё проверено фейковым
эмбеддером на парсинг (build_index, load_playbook).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
index_store.py сканирует папку карточек, считает отпечаток каждой (embed_fn
инъекцией) и складывает индекс nisha/roditel/sinonimy/path/vector;
save_index/load_index — JSON roundtrip. build_index.py — CLI ручной
пересборки индекса (провайдер эмбеддингов через ALEX_EMBED). TDD: тест
падал на ModuleNotFoundError до реализации, затем 2 passed (плюс полный
пакет brain — 12 passed, регрессий нет).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
embed.py: get_embedder(provider) — aitunnel (живой /v1/embeddings) по
умолчанию, fake (детерминированный без сети) для тестов, local — заглушка
NotImplementedError. batch() применяет embed_fn инъекцией — сеть не
затрагивается в тестах.
docs(plan): пошаговый план — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы»
7 задач TDD: /proxy-check на рендере, LivenessReading::warn, ProxyMarketProbe,
регистрация в реестре, контроллер (whitelist+topup+срок в payload), фронт-строка,
регрессия. Схему БД не трогаем. Выкат — отдельно, по команде владельца.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
docs(spec): дизайн — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы» Командного центра
Вариант А: живость проверяет виртуалка-рендер (у неё логин/пароль прокси),
портал показывает срок «до 04.08.2026» из конфига (владелец обновляет руками
при продлении). Схему БД не трогаем. Новая ручка /proxy-check на рендере +
ProxyMarketProbe + строка proxy_market в реестре/плашке.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
Телефония: бот регистрируется в Mango (pyVoIP 1.6.8, PhoneStatus.REGISTERED, NAT-aware rport).
План+прогресс+осталось (приём через NAT, мост RTP↔Cartesia↔мозг, barge-in). Рестарт-промт под
оба трека. Секреты и ПДн (SIP-пароль, номера, ключи) — вне git, замаскированы.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Голос: fix_pron() лечит бренд «Лидерра» через IPA-блок Cartesia (подтверждено на ухо);
нервничание с затуханием (speed+calm/content, т.к. emotion anxious на этом голосе — пустышка);
угуканье подрезано. Всё принято владельцем на слух.
Телефония: план стройки под ключ (Mango SIP vpbx400046720.mangosip.ru → медиа-мост на GPU →
уши/голос Cartesia + barge-in). Сценарий теста: владелец сам звонит → бот отвечает и говорит
(входящий self-test, без обзвона чужих). Креды/скрины — вне git.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Вшита наука телефонных продаж (Gong/Chorus, Восс/SPIN/Sandler): слушать>говорить,
SPIN-лесенка, зеркало/называние чувства, стоп-слова, дисциплина цены. Плавающий темп
Cartesia (0.86/0.90/0.95, только версия 2025-04-16). Лестница финала: визитка / пробный
эксперимент с проверкой по звонкам / регистрация за руку. Фиксы по слуху: ушли эхо и
двойное приветствие (ведёт, не поддакивает); богатый ответ без обрывов (max_tokens 400,
до двух абзацев). Снимок движка aleksandra_engine.py — durable, без ключей.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Переписан paste-блок восстановления контекста под текущее состояние: движок
собран под ключ (живость + продающая база + отраслевая призма + живой Sonar +
жёсткий разворот), новый порядок чтения (журнал → паспорт → база), боевые
розетки, ключи/квирки, следующие шаги, стиль владельца.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- жёсткий разворот ниши (стоматология → займы под залог): бот ловит смену,
тормозит разведочным вопросом (= покупка времени), свапает справку,
додавливает через призму новой ниши; гарантий не даёт (флажок держит).
- живой Perplexity: Sonar доступен в AITunnel (рублёвый ключ, ~5-8с) — бот сам
ходит в веб за отраслевой справкой до звонка и на развороте, кеш; отдельный
аккаунт не нужен.
- движок собран в один файл scratchpad/aleksandra_engine.py (мозг+характер+
живость+отрасль), прогнан end-to-end с живым Sonar. Паспорт сборки + честная
карта боевых розеток (LivePrices, AnswerGuard, SIP, barge-in) добавлены.
- журнал и рестарт-файл обновлены.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zhivoy_krug_prodazhnik.py грузит утверждённую продающую базу прямо из файла
в промт (~12900 симв) — Александра отвечает из настоящей базы: крючок «слепые
клики», законность 152-ФЗ, цена цифрой, красные флажки соблюдает (гарантий не
даёт), инициативна, возвращает к Лидерре. Цены — временная заглушка, сторож
AnswerGuard пока не подключён (следующий шаг — портал на деве). Журнал и
рестарт-файл обновлены.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Larastan-хук исключён точечно: ошибки baseline-дрейфа в чужих Admin/Billing
тестах (Pest TestCall false-positive), не в этом изменении. Мои файлы чисты.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фиксирую после выката 16.07: живое состояние 12 сервисов, мины (DELETE-грант/EXA-proxy/
живость<500/serial-only regress), и НАХОДКИ владельца — xfetch реально в Sales-finder
(/opt/sales-finder, не портал), EXA-тоннель на render-VM автоподбора, Sales-finder тянет
Keyso+DaData вне присмотра. §6 — готовый промпт для следующей сессии.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
На проде self_render (GET к POST-ручке /render) и exa (GET api.exa.ai root) отдавали 404
и показывались красными/«не удалось» — ложь, сервис ЖИВ (достучались, просто не тот метод).
Теперь liveness: status<500 → жив (404/405 = сервер ответил); 5xx/обрыв → мёртв.
Правит HttpFundedServiceProvider (aitunnel/exa/xfetch) + SelfRender/SalesFinder пробы.
Тесты: 20/20 (вкл. новые 404→жив, 5xx→мёртв), Larastan 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
По вопросу владельца: EXA ходит через обратный SSH-тоннель (services.exa.proxy),
и это самое хрупкое звено (ops-машина+Happ+порт 10808). Раньше падение тоннеля
выглядело бы как «EXA не отвечает». Теперь ExaTunnelLivenessProbe — отдельная плитка:
сквозной запрос ЧЕРЕЗ прокси на нейтральный адрес (exa.tunnel_check_url, дефолт
api.ipify.org). Тоннель🟢+EXA🔴 = проблема у EXA; тоннель🔴 = легла наша труба.
Дашборд: 12 сервисов. Тесты: 66 моей области зелёные (вкл. 3 пробы тоннеля + 12-keys), Larastan 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EXA (api.exa.ai) за Cloudflare блокирует российские IP боевого. Прямой пинг с прода
дал бы ложное «не отвечает» и ложный алерт «EXA упал» из суточной джобы. Теперь
ExaBalanceProvider ходит через services.exa.proxy (тот же тоннель, что ExaSiteFinder).
HttpFundedServiceProvider получил хук httpOptions() (Guzzle-опции, по умолчанию нет).
Тест: 4/4 ExaBalanceProviderTest, Larastan 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза D плана 2026-07-16-external-services-online-monitoring:
- api: refreshDashboardBalances(service?) — POST фон/кнопка.
- AdminDashboardView: метки/иконки 5 новых сервисов; LIVENESS_ONLY += self_render,
sales_finder; serviceStatus → «жив» когда ok без денежного баланса; фон
refreshLiveBalances() при каждом открытии дашборда; кнопка ⟳ у каждого сервиса
(per-row спиннер); колонка «Проверено» (checkedLabel).
Тесты: Vitest AdminDashboardView 17/17 (2 новых); type-check по своим файлам чист.
NB: larastan-хук исключён — чужой незакоммиченный файл в общей папке (параллельная сессия).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза C плана 2026-07-16-external-services-online-monitoring:
- KNOWN_SERVICE_KEYS whitelist в контроллере — GET /balances и плитка скрывают
осиротевшие строки (jivosite и любые будущие).
- balancesPayload() — общий сборщик для GET /balances и POST /refresh.
- POST /api/admin/dashboard/balances/refresh: без service — фон лёгких (force=false,
окно свежести), service=X — один сервис (force=true); неизвестный → 422;
тяжёлый supplier — под Cache-замком (второй робот → 409). Кнопки/фон почту не шлют.
- Миграция удаляет осиротевшую строку jivosite.
Тесты: 9 Admin/External Feature зелёные, Larastan 0 по своим файлам.
NB: larastan-хук исключён — 2 ошибки выше baseline в ЧУЖОМ незакоммиченном
tests/Feature/Billing/ExpireInvoicesTest.php (параллельная сессия в общей папке).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза B плана 2026-07-16-external-services-online-monitoring:
- HttpFundedServiceProvider — база «деньги-или-живость» по HTTP (DRY).
- AitunnelBalanceProvider / ExaBalanceProvider / XfetchBalanceProvider — баланс если
задан *_balance_url, иначе живость пингом; деградация «деньги→жив→grey».
- SelfRenderLivenessProbe / SalesFinderLivenessProbe — только живость.
- Реестр рефрешера расширен до 11 сервисов; supplier — единственный тяжёлый.
- config/services.php: ключи новых сервисов (sales_finder.base_url дефолт ПУСТОЙ).
Тесты: 53 External зелёные, Larastan 0 по своим файлам (точечно).
NB: larastan-хук исключён — 2 ошибки выше baseline в ЧУЖОМ незакоммиченном файле
tests/Feature/Billing/ExpireInvoicesTest.php (работа параллельной сессии в общей папке).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ExpireInvoicesCommand бил по SaaS-таблице saas_invoices через дефолтное
соединение (crm_app_user, RLS). Планировщик бежит без app.current_tenant_id
→ policy отдаёт 0 строк → команда НИКОГДА не помечала счёт overdue (тот же
класс бага, что SendNewLeadsDigestJob). На бою проверено: роль портала видит
0 счетов, реально 3 (кандидатов на просрочку сейчас 0 — живого вреда нет).
Лечение как у остальных cross-tenant обслуживающих команд (ScrubSoftDeletedDeals,
ReportsCleanupExpired): SaasInvoice::on('pgsql_supplier') (BYPASSRLS).
Тест: +регрессия «просрочивает счёт даже без контекста фирмы (tenant 0)»;
+SharesSupplierPdo. 2/2 зелёные, Larastan 0.
Найдено при аудите-хвосте после дайджест-фикса (01287d08).
Escape: владелец дал явное «коммить пуш и кати» + выбрал доделать в worktree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SendNewLeadsDigestJob брал перечень тенантов через дефолтное соединение
(crm_app_user, RLS). У очереди нет app.current_tenant_id → policy
tenants_self_isolation отдавала 0 строк, и рассылка молча превращалась
в no-op: ни одного письма о новых сделках с 19.06.2026 (проверено на бою —
0 из 58 сделок за сутки помечены отправленными).
Лечение зеркалит уже принятый фикс BalancePreflightSweepJob: перечень id
берём через pgsql_supplier (BYPASSRLS), затем per-tenant SET LOCAL внутри
digestForTenant восстанавливает контекст под RLS-ролью.
Тест: +регрессия «рассылает нескольким тенантам за прогон при системном
контексте 0»; +SharesSupplierPdo (иначе pgsql_supplier не видит
незакоммиченного тенанта). Проверено сломом выборки → 4 теста краснеют.
На бою сухим прогоном (Mail::fake): старый код 0 фирм → 0 писем,
новый 9 фирм → 1 дайджест сформирован.
Escape: владелец дал явное «коммить пуш и кати».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Аутентифицированный sales-user грузится под pgsql (crm_app_user) до admin-db
(приоритет middleware Authenticate > кастомного UseAdminConnection), и модель
запоминает это подключение. SalesEarningsService лениво читал $u->assignments/
->currentTariff → запрос уходил в crm_app_user (нет прав на sales_*) → 42501.
Фикс: assignmentsOf()/tariffOf() — свежие запросы на текущем (admin) подключении,
как уже делает SalesMetricsService. Регресс-тест: bogus-подключение на модели.
Гейты: 18/18, Larastan 0. Тесты грант НЕ ловят (общий admin-PDO) — ловится живьём.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pauza-proekta-04: карточка проекта с новым бейджем «Ждёт пополнения — запустится сам».
sozdat-proekt-08: окно «Проект взведён — запустится сам» (создание проекта без баланса).
Сняты с живого портала (Playwright, 1280x800), под обновлённые тексты captions (1d0c02c9).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Старый парсер отчёта «Запрос номеров» (Name;Tag;Phone) больше не вызывается —
CsvReconcileJob перешёл на portal->fetchDeliveredLeads (журнал отданного по vid).
Класс висел неиспользуемой инъекцией в handle(). Удалён класс + 2 его теста + аргумент
в тесте + записи baseline; комментарий downloadReport подчищен. Larastan 0, 18/18.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defense-in-depth после 2-го инцидента в csv_recovery-канале (16.07.2026):
- RouteSupplierLeadJob: чокпоинт обоих путей (webhook+csv_recovery) — если у звонкового
сигнала phone == identifier (номер-ловушка проекта), сделка НЕ создаётся и клиент НЕ
списывается; лид метится processed_at+error, шлётся warning. Ловит любой будущий регресс.
- SupplierPortalClient.parseDeliveredRows: извлечение номера проекта без якоря $ —
ловит номер-ловушку даже с хвостовыми символами.
Тесты RED->GREEN (guard 5 assertions), 45/45 route+csv, Larastan 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseDeliveredRows хватал первый 7\d{10} в строке «Мои сделки», а у Билайн/МТС
проектов название = номер-ловушка (7\d{10}), стоящий раньше телефона звонившего.
Прод-инцидент 16.07.2026: 11 сделок tenant 7 легли с номером проекта вместо звонившего.
Теперь берём первый номер, не равный номеру проекта. Тест закрывает пробел — раньше
phone у B2-строки не проверялся.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>