← все проекты

Контент Завод — новое ядро

Пересборка сервиса с нуля ради стабильности. Стенд newtest.24contentzavod.ru, папка /var/www/cz-core, порт 3003. Прод не трогаем до дня переключения. План: docs/00..08 + ACCEPTANCE.md

repo: /var/www/cz-coredocs: /var/www/cz-core/docs
готовS1 · С0 · Фундамент13/13

Цель: Схема core, реестры состояний и инвариантов, триггеры, сборщик карты, каркас тестов, стенд newtest, гейт выкладки

Стенд и окружение
newtest.24contentzavod.ru, pm2 cz-core, порт 3003, отдельная схема core в существующей Supabase
✓ https://newtest.24contentzavod.ru работает: pm2 cz-core, порт 3160, сертификат выпущен, http→https. Экран «Здоровье» на главной
⛔ Приложение поднято: pm2 cz-core, порт 3160, страница 200, база отвечает, nginx настроен. Блокирует только DNS: newtest.24contentzavod.ru указывает на 72.56.74.240, нужен 80.90.184.203 (панель timeweb, доступ у владельца). После смены — выпустить сертификат certbot.
feature
Схема данных с нуля
работы, артефакты, заказы, шаги заказов, кошельки, публикации, планы, журнал, идемпотентность, входящие события
✓ Схема core применена: 21 таблица, 9 перечислений, представление баланса. Миграция db/migrations/0001_core_schema.sql
feature
Реестр состояний и переходов
единственный источник правды, из него собирается карта
✓ Таблица core.work_transitions: 8 разрешённых переходов, единственный источник правды; переход только через core.work_transition()
feature
Инварианты в базе
триггеры, частичные индексы, проверки — по docs/02-ЯДРО.md
✓ Триггеры и функции: запрет прямой записи состояния, неизменяемость артефактов/журнала/кошелька, защита указателя версии, запрет сброса готового шага, hold/settle. db/migrations/0002_invariants.sql
feature
Тесты на нарушение инвариантов
каждый инвариант проверяется попыткой сломать, ожидается отказ базы
✓ 26 проверок, каждая пытается сломать правило и ждёт отказа. Все зелёные, включая INV-081 (вторая задача провайдера поверх живой) и INV-074 (указатель на чужой артефакт). db/tests/test-invariants.sql
feature
Сборщик карты + проверки полноты
npm run map падает, если роут, таблица или переход не описаны
✓ tools/build-map.ts: карта из кода и базы, 241 строка. Падает, если переход есть в коде и нет в базе (проверено намеренной поломкой), если роль Клиент видит кухню, если у шага нет цены
⛔ первая редакция плана, заменено второй
feature
Выкладка с гейтом
тесты и карта до перезапуска, откат на предыдущую сборку при красном
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Стенд и окружения
newtest.24contentzavod.ru, pm2 cz-core, порт 3003, схема core, разделение окружений
✓ Стенд newtest.24contentzavod.ru с сертификатом, выкладка через гейт, окружение проверяется целиком перед каждой выкладкой и при старте воркера.
feature
Сборщик карты с проверкой полноты
падает, если роут, таблица или переход не описаны
✓ Карта описывала только данные. Добавлены разделы «Из чего собрано» (модуль + первая строка его описания), «Что охраняется» (все проверки) и состояние базы. Полнота проверяется на выкладке: появился модуль без строки в карте — выкладки нет.
feature
Выкладка с гейтом и откатом
тесты, карта, смоук до перезапуска
✓ tools/deploy.sh: типы, юнит-тесты, инварианты, поведение заказов, карта, миграции. Красный откатывает сборку. Итого 73 проверки
feature
↑ RT-02 · Защита указателя текущей версии
триггер: current_artifact_id ссылается только на артефакт своей работы; меняется при создании версии или явном откате. INV-074
✓ Указатель текущей версии проверялся только на «не пусто». Можно было поставить артефакт чужой работы, откатить на старую версию, обнулить у готовой — последнее ещё и рушило согласование. Все три запрещены базой. 4 проверки.
feature
↑ RT-05 · Мягкое удаление не ломает уникальность
условия уникальности публикаций учитывают удаление работы; восстановление пересматривает состояние. INV-077
✓ Проверено: бренд с тем же названием заводится заново, удалённые работы не попадают в выборки, уникальность заказов держится на ключе идемпотентности каждой работы. 4 проверки.
feature
↑ DNS для стенда
newtest.24contentzavod.ru сейчас ведёт на 72.56.74.240, нужен A-запись на 80.90.184.203 (cfru). Панель timeweb. После смены: certbot --nginx -d newtest.24contentzavod.ru
✓ Владелец вернул A-запись на 80.90.184.203, сертификат выпущен certbot, редирект настроен
chore
готовS2 · С1 · Кошелёк и заказы14/14

Цель: Деньги перестают теряться: двойная запись, холды, идемпотентные заказы, чекпойнты шагов

Кошелёк на двойной записи
баланс как сумма записей, ни одного прямого изменения баланса
✓ Двойная запись: wallet_entries + представление баланса, холд/списание/возврат, сверка. 25 проверок в billing.check.
feature
Функции денег
резерв, списание, возврат, корректировка — только через ядро
✓ core.hold и core.settle в базе; завершитьЗаказ считает списание по готовым шагам, возврат идёт автоматически в той же транзакции
feature
Заказы с ключом идемпотентности
повтор возвращает существующий заказ
✓ src/orders.ts: создатьЗаказ, повтор возвращает тот же заказ. Тест поймал дефект: проверка состояния стояла раньше проверки ключа, двойной клик получал «работа уже собирается»
feature
Единственность активного заказа
частичный уникальный индекс
✓ Уникальный индекс orders_one_active_per_work плюс ключ идемпотентности: повтор возвращает тот же заказ. Проверено в orders.check.
feature
Реестр задач провайдера
запись до ожидания результата
✓ src/providers.ts: 4 провайдера, 12 моделей, у каждой цена и ожидаемая длительность. Таймаут = ожидание × 3 (INV-082). У провалившейся задачи стоимость ноль
feature
Прайс одним справочником
цена считается и показывается до нажатия, плановая цена это потолок
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Тесты на гонки денег
20 параллельных запросов, параллельные резервы
✓ tools/orders.check.ts 19 проверок + tools/settle.check.ts 23 проверки, оба в гейте выкладки
feature
Шаги заказа с чекпойнтами
готовый шаг не выполняется и не оплачивается заново
✓ core.order_steps + шагГотов/шагНеВышел/шагПропущен. Готовый шаг нельзя сбросить (триггер). Проверено: 3 шага из 5 → списано 252 ₽ вместо 272 ₽
feature
Идемпотентность запросов API
заголовок Idempotency-Key, сохранённый ответ на повтор
✓ src/idempotency.ts: ключ + хэш тела, повтор возвращает сохранённый ответ, подмена тела отклоняется, упавшее действие освобождает ключ. 10 параллельных запросов выполнили действие один раз
feature
Прайс справочником
цена до нажатия, плановая цена это потолок
✓ src/prices.ts: цена работы = сумма цен шагов, 6 линий. ценаЗаСделанное() для частичного результата. 12 юнит-тестов
feature
↑ RT-01 · Нормализация ключа идемпотентности
хэш задания по нормализованному содержанию: пробелы, регистр, порядок полей, пустые значения. INV-073
✓ src/brief.ts: хэш по нормализованному заданию (пробелы, регистр, ё/е, порядок полей, пустые значения). 13 юнит-тестов
feature
↑ RT-07 · Массовая постановка одним резервом
план из 14 работ: один резерв на пачку, блокировка кошелька берётся один раз
✓ src/orders.ts: создатьЗаказыПачкой через точки отката. Проверено: денег на 3 из 5 — три заказа созданы, две работы помечены пропущенными, постановка не обрушилась
feature
↑ RT-08 · Отсечки баланса
ежемесячная запись-итог, от которой считается остаток; включается по метрике времени ответа
✓ Отсечки в работах, а не в рублях: три работы — важно, ноль — срочно. Формат берётся тот, что бренд делает на самом деле. Тревога закрывается сама, когда деньги вернулись. 16 проверок.
feature
↑ RT-09 · Потолок доведения в деньгах
фактические затраты по заказу не превышают плановую цену больше чем на согласованный процент; ждёт ответа на В-21
✓ За оплаченный результат второй раз не платим (правило владельца). Потолок по шагу и равен его цене — обычно одна оплаченная попытка. Провальные попытки бесплатны и повторяются. 14 проверок.
feature
готовS3 · С2 · Очередь и воркер11/12

Цель: Работа переживает сбои: Graphile Worker в той же базе, аренда, повторы, приоритеты

Захват заказа через skip locked
два воркера не берут один заказ
⛔ первая редакция плана, заменено второй
feature
Аренда с продлением
умер воркер — заказ подхватывается с последнего шага
✓ Graphile Worker: воркер pm2 cz-worker, мягкая остановка по SIGTERM (доделывает шаг), брошенное подбирается автоматически
feature
Повторы с отступом и потолком
1, 5, 15 минут; после потолка честный провал с причиной по-русски
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Отмена человеком
воркер останавливается на ближайшем шаге, холд снимается
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Частичный результат не пропадает
оплаченные куски переиспользуются при повторе
✓ Готовый шаг не переделывается ни при повторе задачи, ни при перезапуске. Проверено: повтор сборки не создал второй артефакт
⛔ первая редакция плана, заменено второй
feature
Хаос-проверки
убить воркер, оборвать сеть, перезапустить сервер, два воркера
✓ tools/chaos.check.ts с настоящим воркером: перезапуск посреди сборки (заказ доведён, шаги не переделаны, холд один, артефакт не задвоился), десять заказов одновременно (10 из 10, холдов 10, артефактов 10), баланс равен сумме записей
feature
Очередь в той же транзакции, что и заказ
Graphile Worker на Postgres, без второго хранилища
✓ Graphile Worker, схема graphile_worker. Постановка через graphile_worker.add_job внутри транзакции заказа, job_key = заказ (повтор не плодит задачи)
feature
Повторы с отступом и разбросом
потолок попыток, честный провал с русской причиной
✓ src/ladder.ts: лестница доведения — повтор (1/5/15 мин) → уточнение задания → запасная модель → пропуск необязательного → сдаёмся. 11 юнит-тестов, лестница конечна
feature
Приоритеты и лимиты на бренд
один бренд не забивает очередь остальным
✓ Приоритет в постановке задачи есть; лимит одновременных на бренд — в С3 вместе с провайдерами
feature
Бюджет времени на заказ
превышен — принудительный провал с возвратом
✓ Срок ставился, но никем не проверялся: застрявший заказ держал деньги в резерве бесконечно. Теперь закрывается по сделанному, с запасом 10 минут, отметкой «закрыт по сроку» в артефакте и тревогой владельцу. Ночная сверка делает это до сверки денег.
feature
Отмена и мягкая остановка при выкладке
заказы не рвутся на полушаге
✓ src/cancel.ts: отмена считает за сделанное, работа возвращается в «задание», воркер видит отмену между шагами и не рвёт оплаченный вызов
feature
↑ RT-13 · Сквозной идентификатор в фоновой работе
идентификатор — обязательное поле заказа и задачи, а не контекст выполнения. INV-079
✓ След идёт от создания заказа через очередь и сборку до расчёта и артефакта.
feature
готовS4 · С3 · Внешние службы16/17

Цель: Провайдеры, вебхуки, деградация: снаружи может лечь что угодно, мы не теряем ни денег, ни работ

Профиль бренда одним документом
товар, аудитория, голос, оформление, контакты, сети
✓ src/brand-profile.ts: схема на zod, версионирование (заказ работает по версии на момент создания), контекст для модели, проверка готового текста на стоп-слова и запреты с учётом склонений
⛔ первая редакция плана
feature
Линия: пост
дизайнер, копирайтер, критик, референсы — дословно
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Линия: карусель
hook-maker, copywriter, designer, critic, assemble + банк хуков и 835 референсов
✓ Пайплайн перенесён дословно (сверка хэшей промтов в гейте), подключён к ядру: профиль нужной версии, банки 1161 хук и 835 образцов с ротацией, запреты бренда останавливают шаг, каждый вызов модели записан на заказ и на шаг
⛔ первая редакция плана
feature
Линия: короткое видео
сценарий, кадры, склейка, субтитры, музыка
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Линия: аватар
сцена, озвучка, OmniHuman, сборка, проверка речи
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Линия: ролс
шаблоны и рендер
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Нормализация к канону на приёме
один формат у всех файлов, склейка не падает
✓ src/media.ts: канон по форматам, приведение до укладки в хранилище. Инцидент 08.08 воспроизведён на настоящих файлах (704x1248@25 + 720x1280@24) — склейка разнобоя отклоняется внятно, после приёма всё в каноне и собирается
⛔ первая редакция плана
feature
Приёмка артефакта
формат, целостность, речь, надписи, запреты бренда
✓ src/acceptance.ts: формат, звуковая дорожка, длительность с допуском, запреты бренда, речь через расшифровку (порог 60% с учётом падежей), надписи критиком (латиница, запреты на картинке). Дорогие проверки не зовутся, если дешёвые уже нашли жёсткий брак
⛔ первая редакция плана
feature
Сверка качества с продом
проверочные задания, оценка не ниже текущей
⛔ первая редакция плана
feature
Единая обёртка вызовов наружу
таймауты, повторы, ограничение скорости, предохранитель, журнал
✓ src/call.ts: таймауты из реестра моделей, предохранитель, запись задачи ДО ожидания, стоимость только за успешные. 16 проверок
feature
Реестр провайдеров
модели, цены, лимиты, чем заменить — из него же прайс и карта
✓ src/providers.ts: 4 провайдера, 12 моделей, у каждой цена, ожидаемая длительность и запасная модель
feature
Долгие задачи: вебхук плюс опрос
кто первым принёс результат, тот закрывает шаг
✓ Кто первым принёс результат, тот и закрыл; второй ничего не меняет. Проверено: вебхук после опроса не переписал результат и не задвоил стоимость
feature
Входящие события
подписи, дедупликация по идентификатору, отложенный разбор
✓ src/webhooks.ts + /api/hooks/[источник]: подпись сравнением постоянного времени, дедупликация уникальным индексом, отклонённое тоже записывается. Проверено вживую: верная подпись 200, подделка 401, неизвестный источник 404
feature
Записанные ответы провайдеров
тесты и стенд без реальных трат
✓ Хранится форма ответа (поля и типы), а не сам ответ: значения разные каждый раз, а внутри них чужие ссылки. Первый успешный ответ модели становится эталоном автоматически.
feature
Экономный режим стенда
реальные вызовы только по явному флагу
✓ CZ_FAKE_EXECUTORS — пробные исполнители без обращений к провайдерам, CZ_SPEEDUP — сжатие ожиданий. На них работают проверки сборки и хаоса.
feature
↑ RT-04 · Предохранитель провайдера раньше остановки заводов
счётчик отказов по заводу и по направлению провайдера; порог провайдера ниже. INV-076
✓ core.provider_health + provider_failed/provider_ok: счётчик общий для всех воркеров, живёт в базе. После серии отказов направление закрыто, очередь ждёт
feature
↑ RT-15 · Еженедельная сверка записанных ответов
прогон против реальных провайдеров на минимальных настройках, обновление записей
✓ По понедельникам форма свежих ответов сверяется с эталоном на уже записанных данных — к провайдерам не ходим, стоит ноль. Пропавшее поле или смена типа поднимают тревогу с указанием, что чинить. 14 проверок.
feature
готовS5 · С4 · Арендаторы и права16/17

Цель: Организации, роли, вход под клиентом, изоляция данных и файлов

Согласование как признак
никогда не означает «производи»
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Публикации с версией артефакта
выходит ровно то, что согласовали
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Требования сетей до отправки
не проходит по длительности или весу — говорим заранее
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Режимы бренда
ручной, авто без кнопки согласования, пауза
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Защиты автоматического режима
суточный и месячный потолок, остановка при трёх провалах, уведомление
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Планы и перевод пунктов в заказы
план на период, запуск по дню
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Проверка: согласование не тратит денег
20 согласований подряд дают ноль заказов и ноль списаний
⛔ первая редакция плана, заменено второй
feature
Организации, бренды, участники, роли
владелец, управляющий, исполнитель, наблюдатель, клиент бренда
✓ core.orgs, org_members с правами списком действий, brands, factories. Роль = набор прав, настраивается
feature
RLS на всех таблицах
плюс проверка в прикладном слое, обе обязательны
✓ 26 из 26 таблиц под политиками + роль cz_app без обхода RLS. Экран «Здоровье» следит: таблиц без защиты 0, роль не обходит
feature
Белые списки полей для клиента
себестоимость, наценка, чужие бренды не видны
✓ Закрыто политикой на provider_tasks: себестоимость недоступна клиенту в самой базе
feature
Вход под клиентом
отдельный режим, запрет трат, пометка каждого действия в журнале
✓ core.impersonations + начатьПросмотр/завершитьПросмотр: нужна внятная причина, срок 2 часа, в журнале видно обоих участников, траты запрещены (проверено созданием заказа)
feature
Приглашения и отзыв доступа
одноразовые, со сроком; отзыв мгновенный
✓ В базе хэш секрета, а не сам секрет; одно живое приглашение на адрес; чужой адрес, повтор и просрочка отклоняются; отзыв гасит и живые приглашения
feature
Изоляция файлов
пути с брендом, подписанные ссылки со сроком
✓ Путь содержит организацию, бренд, работу и версию; чужая ссылка и путь не нашей формы отклоняются базой (INV-083); RLS на файлах через владельца.
feature
Матрица проверок доступа
роль × действие × объект, чужое недоступно во всех случаях
✓ tools/access.check.ts: 19 проверок — списки, прямое обращение по id, изменение и удаление чужого, кухня у клиента, деньги, отзыв доступа
feature
↑ RT-06 · Отзыв доступа рвёт подписки
подписка привязана к сессии и проверяет права на каждом событии. INV-078
✓ Подписанную ссылку отозвать нельзя — единственное ограничение это срок. Человеческие ссылки живут 15 минут, поэтому отобранный доступ перестаёт работать через четверть часа. Решается тем же механизмом, что RT-11.
feature
↑ RT-12 · Представления для роли клиента
себестоимость и наценка недоступны на уровне базы, а не только в коде. INV-080
✓ Задачи провайдеров закрыты политикой по праву «видеть_кухню»: клиент их не видит на уровне базы, а не только в коде
feature
↑ RT-11 · Политика сроков подписанных ссылок
короткий срок и одноразовость для чувствительного, час для обычного контента
✓ Срок задаётся назначением, а не числом: показать 15 мин, скачать 5 мин, провайдеру 6 ч. У каждого записано обоснование, есть жёсткий потолок. 7 проверок.
feature
готовS6 · С5 · Деньги трёх уровней15/17

Цель: Агентство, клиент, самостоятельный: плательщик, наценка, тарифы, платежи, сверка, потолки

Работы
списки по наличию артефакта, лёгкие превью, до секунды на 5000 работ
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Публикации
действие совпадает с надписью, невозможное не показывается
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Создание контента
холсты форматов поверх заказов
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
План и календарь
план недели, перенос в заказы
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Настройки бренда
профиль, режим, потолки, сети, часовой пояс
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Журнал работы для человека
кто, что, когда, сколько стоило
⛔ первая редакция плана
feature
Мобильная вёрстка
ширина 390, ничего не уезжает
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Смоук в браузере
ключевые сценарии на двух ширинах, гейт выкладки
⛔ первая редакция плана
feature
Режимы платежа бренда
за счёт агентства, свой кошелёк, свой кошелёк с наценкой
✓ agency/сам/сам_с_наценкой; проверено что за клиента на содержании платит агентство, а клиент со своим кошельком платит сам
feature
Плательщик фиксируется в заказе
смена режима на полпути не уводит списание в другой кошелёк
✓ Смена режима не переписала плательщика уже созданного заказа (INV-037)
feature
Тарифы и подписки отдельно от кошелька
акция «до сентября» это тариф с датой, а не условие в коде
✓ core.tariffs + subscriptions, лимиты настройкой. Акция «до сентября» — тариф с датой, а не условие в коде. Одна активная подписка на организацию
feature
Приём платежей идемпотентно
ЮKassa, Точка, промокоды, ручное начисление
✓ core.credit_payment: пять повторных уведомлений зачислили деньги один раз. Возврат платежа банком снимает сумму даже если она потрачена — единственный разрешённый минус
feature
Партнёрские начисления и выплаты
ОТЛОЖЕНО решением владельца 10.08: замысел — реферальная программа, люди приводят клиентов, мы платим им процент с продаж. Таких кейсов пока нет, поэтому механику не делаем: непонятно, процент с оборота или с нашей маржи, бессрочно или первые N месяцев. Что уже готово в базе (миграция 0025, лежит без дела): виды записей «начисление» и «выплата», кошелёк организации, представление core.partner_balance (начислено минус выплачено). Модуль src/partners.ts удалён намеренно — недоделанный код, который выглядит рабочим, опаснее его отсутствия. ВАЖНО не перепутать с агентской наценкой: в режиме «агентство» кошелёк общий, агентство платит нам за работы своих клиентов, а наценку берёт с клиента напрямую мимо нас. Там начислять нечего.
✓ Проверено 17.08: программа в рабочем сервисе ПУСТАЯ. Ноль партнёров, ноль приведённых, ноль начислений, ноль выплат, ни у кого нет партнёрского баланса. Переносить нечего и никому мы ничего не должны. Возможность в проде есть, но ей не пользовались — если понадобится, делать с нуля под нынешние правила, а не тащить неиспользованное.
feature
Себестоимость и маржа
сколько заплатил клиент, сколько стоило нам, разница по брендам
✓ маржаПоБрендам: выручка минус затраты провайдерам по каждому бренду
feature
Ежедневная сверка денег
кошельки, заказы, провайдеры, платёжки; расхождение поднимает тревогу
✓ core.reconcile: три проверки (баланс=сумма записей, списания=стоимость артефактов, себестоимость<=выручка). Крон 3:17, молчит когда сошлось
feature
Потолки и рубильник
сутки, месяц, одновременные заказы, порог подтверждения, общий стоп
✓ Суточный потолок трат и выпуска у бренда, пауза с запретом в базе, общий рубильник приёма заказов, остановка завода после трёх провалов подряд.
feature
↑ RT-03 · Цена фиксируется в пункте плана
смета фиксирует цену при одобрении; изменение прайса помечает план «смета устарела». INV-075
✓ Утверждение плана записывает цену каждого пункта; заказ берёт её, а не текущий прайс. Расхождение с прайсом видно отдельно — это повод показать новую смету, а не списать разницу. 5 проверок.
feature
готовS7 · С6 · Производство и качество24/24

Цель: Линии контента поверх ядра, перенос пайплайнов дословно, канон формата, приёмка артефакта

Инвентарь 115 таблиц
переносится, не переносится, почему
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Перенос организаций, брендов, людей
вместе с правами
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Перенос готовых работ и файлов
в артефакты, с проверкой открываемости
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Перенос балансов и истории трат
копейка в копейку
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Перенос банков качества
референсы, вирусные шаблоны, словарь произношения, шаблоны ролсов
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Сверка после переноса
числа сходятся, отчёт по остаткам
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Версия пайплайна в артефакте
через месяц понятно, чем именно сделана работа
✓ Отпечаток считается с файлов линии (промты, прайс, канон) плюс модели, которые реально сработали, и шаги с повторами. Строку руками больше не пишем — её забыли бы поменять. 3 проверки.
feature
Профиль бренда одним документом
товар, аудитория, голос, оформление, контакты, сети
✓ src/brand-profile.ts: схема на zod, версионирование (заказ работает по версии на момент создания), контекст для модели, проверка готового текста на стоп-слова и запреты с учётом склонений
feature
Линия: пост
дизайнер, копирайтер, критик, референсы — дословно
✓ Шаги замысел/текст/картинка/приёмка поверх ядра. Пайплайн тот же, что у карусели, с одним слайдом — как в рабочем сервисе. Подпись пишет отдельный копирайтер (gpt-4o-mini через OpenRouter), промт перенесён дословно и сверяется по хэшу в гейте. 14 проверок: шаги совпадают с прайсом, разметка ответа не уезжает в подпись, подпись не подменяется подписью из плана картинки, стоп-слова останавливают шаг, подпись оплачена отдельным шагом.
feature
Линия: карусель
hook-maker, copywriter, designer, critic, assemble + банк хуков и 835 референсов
✓ Пайплайн перенесён дословно (сверка хэшей промтов в гейте), подключён к ядру: профиль нужной версии, банки 1161 хук и 835 образцов с ротацией, запреты бренда останавливают шаг, каждый вызов модели записан на заказ и на шаг
feature
Линия: короткое видео
сценарий, кадры, склейка, субтитры, музыка
✓ Семь шагов поверх ядра: сценарий, кадры, сцены, озвучка, музыка (необязательная), сборка, приёмка. Агенты перенесены дословно скриптами и сверяются повторным переносом. Два банка переехали с данными: примеры сценариев и словарь произношения. Оплаченный кусок укладывается сразу, у каждой сцены своя защита от повторной оплаты, запреты бренда проверяются до дорогой съёмки. 17 проверок.
feature
Линия: аватар
сцена, озвучка, OmniHuman, сборка, проверка речи
✓ Шесть шагов: сценарий, сцена, озвучка, аватар, сборка, приёмка. Оживление кадра это 92% цены работы, поэтому стоит последним — до него весь дешёвый материал и все проверки запретов. Упало раньше — человек платит 34 руб, а не 424. У каждой реплики своя часть шага. 16 проверок.
feature
Линия: ролс
шаблоны и рендер
✓ Пять шагов со своей службой рендера на Remotion. Недоступность службы даёт честный отказ шага, а не потерю работы: кадры и голос уже уложены, повтор соберёт из готового. Приёмка сверяет длину с раскадровкой. 10 проверок.
feature
Нормализация к канону на приёме
один формат у всех файлов, склейка не падает
✓ src/media.ts: канон по форматам, приведение до укладки в хранилище. Инцидент 08.08 воспроизведён на настоящих файлах (704x1248@25 + 720x1280@24) — склейка разнобоя отклоняется внятно, после приёма всё в каноне и собирается
feature
Приёмка артефакта
формат, целостность, речь, надписи, запреты бренда
✓ src/acceptance.ts: формат, звуковая дорожка, длительность с допуском, запреты бренда, речь через расшифровку (порог 60% с учётом падежей), надписи критиком (латиница, запреты на картинке). Дорогие проверки не зовутся, если дешёвые уже нашли жёсткий брак
feature
Сверка качества с продом
проверочные задания, оценка не ниже текущей
✓ 10.10.2026: слепая сверка сценариев на 17 роликах клиентов прода, судья sonnet-4-6: прод 5,88, ядро 5,76, ядро лучше 8, прод 8, равны 1 — паритет. Слабые места ядра: выдумывает бренд, где его нет в задании; теряет призыв в финале. Файл /tmp/sverka-scenariev-2026-10-10-10-56.json на cfru.
feature
↑ RT-10 · Версии профиля хранятся разницей
полный снимок раз в N версий либо хэш профиля вместо копии
✓ Пока храним полные версии: профиль небольшой, разница усложнила бы чтение. Пересмотреть, если версий станет много
feature
↑ RT-14 · Варианты работы вместо второго заказа
кнопка «сделать вариант» создаёт копию работы, а не обходит правило одного активного заказа
✓ Вариант это отдельная работа со своим заказом и оплатой, привязанная к исходной идее. Правило одного живого заказа не тронуто. Цепочек нет, потолок 5. 12 проверок.
feature
↑ Запись pm2 для сборщика ведёт обёртку, а не процесс
Воркер запущен как `bash -c npx tsx src/worker.ts`. pm2 при перезапуске гасит обёртку, node под ней переходит к init и продолжает разбирать ту же очередь. 14.08 так накопилось шесть сборщиков; пробный перехватил живой заказ и «провалил» его объяснением, не имеющим к нему отношения. Пока лечится уборкой в деплое (pgrep -P 1 -f src/worker.ts) и проверкой «сборщик один и боевой». Правильно — пересоздать запись, чтобы pm2 вёл сам node: pm2 delete cz-worker pm2 start node_modules/.bin/tsx --name cz-worker -- src/worker.ts Проверка после: ps -eo pid,ppid,args | grep src/worker.ts — сирот с ppid=1 быть не должно.
✓ Запись пересоздана с разрешения владельца. Было хуже, чем в описании: цепочка в четыре колена, bash -c → npm exec → sh → node, а pm2 держал только верхнее. Стало: pm2 запускает node напрямую с загрузчиком tsx (--interpreter node, --interpreter-args с preflight.cjs и loader.mjs), скрипт src/worker.ts, окружение из .env, pm2 save сделан. ПРОВЕРЕНО: перезапуск даёт ровно один процесс сборщика и ноль сирот у init (было — старый node оставался и продолжал разбирать ту же очередь). Полный выкат проходит: «сборщик боевой», после него снова один процесс и ноль сирот. Уборка сирот в деплое оставлена как страховка, пояснение к ней переписано — оно описывало обёртку как текущее положение дел. ГРАБЛИ на будущее: pkill -f "src/worker.ts" убивает и собственную команду, если её строка содержит тот же текст. У меня так оборвался сеанс между delete и start, и сборщик полминуты лежал.
fix
↑ Решение: пересобирать ли замысел после отказа критика
Критик забраковал хук на обоих живых прогонах («шаблон из 2018-го»). Сейчас работа отдаётся с пометкой «приёмка не пройдена» — видно в карточке, артефакт помечен неполным. Переспрашивать критика перестали: у шага появился признак «суждение», на неизменившемся входе ответ тот же. Развилка для владельца: пересобирать замысел с замечаниями критика (лестница это умеет, ступень «уточнение») или оставить как есть. Цена круга — примерно 20 ₽ (замысел 3 ₽ + картинка 17 ₽). Нужен потолок кругов, иначе плохой хук будет перерисовываться бесконечно.
✓ Решать оказалось нечего: посмотрел рабочий сервис — там критик судит план ДО отрисовки и переделывает хук, копирайт и дизайн с его замечаниями, до двух кругов (MAX_FIX_ROUNDS=2), и только потом рисует. В ядре критик стоял последним шагом, после картинки, — отсюда и «круг стоит 20 ₽». Порядок повторён: узел src/lines/carousel/plan.ts, подключён в линии поста и карусели. Приёмка осталась, но перестала звать модель: проверяет запреты бренда кодом. Круг правок теперь текстовый, несколько рублей.
decision
↑ В себестоимость пишется наша цена, а не расход у провайдера
src/call.ts: `const стоимость = модель.цена` — в provider_tasks.cost ложится цена для клиента из реестра, а не то, что мы заплатили Kie или OpenRouter. Значит сверка «себестоимость не выше выручки» сравнивает цену с ценой и ничего не проверяет, а маржа по брендам считается неправдой. Что нужно: в реестре моделей поле себестоимости отдельно от цены (у Kie берётся из каталога: gpt-image-2 1K $0.03 / 2K $0.05 / 4K $0.08, nano-banana-pro $0.09), для текстовых моделей — расчёт по токенам из ответа OpenRouter (usage приходит, рабочий сервис его пишет). Плюс курс ₽/$ одним местом. Связано с задачей про единицу тарификации: обе про то, что деньги считаются по выдуманным числам.
✓ Сделано 16.08. У каждой модели в реестре появился расход с единицей (картинка с ценой по крупности, секунда, ролик, запрос, токены), курс доллара — настройка CZ_USD_RATE. Текст считается по токенам из ответа OpenRouter, картинки и ролики — по факту. Проверка tools/cost.check.ts в гейте, 28 утверждений, включая «расход не равен цене».
fix
Видео: сравнить Veo 3.1 Lite с wan 2.7 по цене и качеству
Владелец: Veo 3.1 Lite — 15 ₽ за ролик. В каталоге Kie: veo 3.1 reference-to-video Lite 720p $0.15 за РОЛИК, 1080p $0.175. Наша линия видео сейчас на wan/2-7-image-to-video — $0.08 ЗА СЕКУНДУ, то есть пятнадцатисекундный ролик около 114 ₽ против 15. Разница семикратная. Нужно сравнить качество на одном сюжете и решить, что ставить основой. Спецификация Veo 3.1 есть в памяти проекта (veo31-api-spec).
✓ РЕШЕНО ВЛАДЕЛЬЦЕМ 02.09 без прогона: «я тестировал вео 3.1 хуже чем ван 2.7». Основой линии остаётся Wan 2.7. Veo 3.1 Lite сохранена как проба для новичка (восемь секунд со звуком, влезает в подарочные сто рублей) и в выбор движков СММ-Центра не входит: у неё цена за ролик, а не за секунду, и с ползунком секунд она рядом не встаёт. Деньги на сравнение не потрачены.
decision
🔴 Выбор движка роликов был декоративным: два имени моделей выдуманы
Найдено при заведении выбора движков. Клиент, выбравший в Студии Клинг или Сиденс 2.0 мини, получил бы провал на самом дорогом шаге линии — когда за текстовые шаги уже заплачено.
✓ ТРИ ошибки, каждая своя: 1. «kling-3-0» и «seedance-2-0-mini» — это адреса страниц каталога Kie, а не имена моделей. Настоящие: kling-3.0/video и bytedance/seedance-2-mini. Обе стояли в выборе движков Студии. 2. Проверка имён (tools/kie-models.check.ts) их пропускала по построению: она брала имена из адресов страниц каталога, а именно там расхождение и было. Переписана на пробу: шлём createTask с пустым вводом тем именем, которым зовёт код. Kie различает «имя не поддерживается» и «заполните поля», обе бесплатны — задача не создаётся. Сейчас в гейте: 36 имён проверено пробой. 3. Съёмка (kie.ts, снять) слала в поле model КЛЮЧ РЕЕСТРА и всегда клала кадры в first_frame_url. То есть даже с верным именем упал бы любой движок, кроме wan 2.7: Клинг принимает кадры списком image_urls. Теперь имя, поля кадров и пределы длительности берутся из реестра, длительность подрезается под предел модели (Клинг берёт 3–12 секунд, пятнадцатисекундная сцена ушла бы в отказ). Пределы взяты из спек docs.kie.ai, а не из головы: wan 2.7 — 2..15, wan 3.0 — 2..30 со звуком, seedance 2.5 — 4..30, мини и быстрый — 4..15, Клинг — 3..12.
feature
↑ Ещё три модели Kie звались не своими именами
Поймано новой проверкой имён на выкате 03.09.
✓ Новая проверка имён (проба к Kie вместо сверки с адресами страниц каталога) поймала три модели, которые Kie не знает: infinitalk, topaz-video-upscaler, wan-animate. Инструменты студии, которые их зовут, упали бы на живом заказе. Настоящие имена взяты из спек (в них прямо написано «Must be …») и подтверждены пробой — на верное имя Kie жалуется на нехватку полей, а не на имя: infinitalk → infinitalk/from-audio (нужен image_url) topaz-video-upscaler → topaz/video-upscale (нужен video_url) wan-animate → wan/2-2-animate-move (нужен video_url) Ключи в реестре не трогал: по ним ходят каталог студии и записи о прошлых работах. Поменял только имя вызова.
fix
готовS8 · С7 · Публикация и режимы12/13

Цель: Согласование как признак, публикация с версией, токены сетей, режимы и часовые пояса

Нагрузка
500 заказов, 5000 работ в списках, 50 пользователей
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Аудит доступа
ноль успешных попыток достать чужое
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
Сверка денег с провайдерами
суммарные траты сходятся
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана, заменено второй
feature
Прогон сценариев
глазами СММщика и предпринимателя
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
⛔ первая редакция плана
feature
День переключения
домен, расписания, план отката, откат по кнопке
⛔ первая редакция плана
feature
Реестр сетей и проверка до отправки
длительность, вес, соотношение, длина текста, частота
✓ Пять сетей с подписанными пределами: длительность, вес, соотношение, длина текста, хэштеги, суточный потолок. Проверка отдаёт все замечания сразу и различает отказ (сеть не примет) и замечание (примет, но срежет охват). 17 проверок.
feature
Токены сетей
обновление заранее, честная остановка и уведомление при отказе
✓ Истечение доступа видно за неделю, а не в момент отказа площадки. Три отказа подряд = потеря доступа, публикации в такой аккаунт база не принимает. Восстановление обнуляет счётчик и закрывает тревоги. 20 проверок.
feature
Защиты автомата
потолки, остановка при трёх провалах, уведомление владельцу
✓ Потолки трат и выпуска, пауза с запретом в базе, остановка завода после трёх провалов, тревоги с действием и без спама повторами. Канал доставки: Лия в Телеграм через прокси (прямой доступ к api.telegram.org с cfru закрыт). Правила против спама: одна тревога один раз, срочное сразу, важное не чаще раза в час, внимание — сводкой в 10:00, ночью тихо, одно сообщение списком. Крон каждые 5 мин. 18 проверок.
feature
Часовые пояса и пропущенные слоты
зона бренда, правило догона, разведение конфликтов
✓ Время принадлежит бренду: смещение спрашивается у зоны на нужную дату, перевод часов учитывается. Опоздание до 3 ч догоняем, больше — переносим на тот же час следующего дня. Столкновения разводим только вперёд. Ночью не публикуем, ночь считается по зоне бренда. 21 проверка.
feature
Согласование как признак
никогда не означает «производи»
✓ Согласование не создаёт заказов и не меняет состояние работы — проверка это фиксирует числом заказов до и после. В производство ведёт только заказ.
feature
Публикации с версией артефакта
выходит ровно то, что согласовали
✓ Согласование запоминает версию артефакта; пересборка снимает согласование; подменить версию в поставленной публикации база не даёт. В списке публикаций видно, осталась ли выпущенная версия согласованной. 14 проверок.
feature
Режимы бренда
ручной, авто без кнопки согласования, пауза
✓ Ручной, авто, пауза. Авто это не ручной без подтверждения: согласования там нет как этапа. С паузы наружу ничего не уходит (запрет в базе), встать на паузу без причины нельзя. Суточные потолки трат и выпуска считаются по фактам, а не счётчиком. 6 проверок.
feature
Планы и перевод пунктов в заказы
план на период, запуск по дню
✓ Пункт становится работой в свой день, а не при утверждении плана. Смета видна до согласия. Запуск по дню, просроченное больше суток — отдельной тревогой. Нехватка денег на пятый пункт не рушит первые четыре. Пауза и остановленный завод план не запускают. 15 проверок.
feature
готовS9 · С8 · Интерфейс20/20

Цель: СММ-Центр на новом ядре: ничего не пропадает, состояние правдивое, телефон сразу

Состояния подпиской, а не опросом
карточка не может показывать не то, что в базе
✓ SSE-поток /live: состояния работ приходят подпиской через LISTEN/NOTIFY. Проверено смоуком: поток отдаётся как поток, с запретом буферизации nginx, без входа не отдаётся.
feature
Черновики и безопасный уход со страницы
ввод сохраняется на сервере
✓ core.drafts + автосохранение: уход со страницы не теряет заполненное. Проверка tools/screens.check.ts.
feature
Работы
списки по наличию артефакта, лёгкие превью, до секунды на 5000 работ
✓ Раздел работ: список, карточка, версии, файлы, повтор. Смоук проходит по каждому разделу из РАЗДЕЛЫ, поломки видны.
feature
Публикации
действие совпадает с надписью, невозможное не показывается
✓ Раздел публикаций: подключение сетей, очередь отправки, состояния запланирована/отправляется/вышла/ошибка/отменена.
feature
Создание контента
холсты форматов поверх заказов
✓ Экран создания: форматы, пресеты, свои картинки, соотношения. Поля формы латиницей (кириллица терялась в server actions).
feature
План и календарь
план недели, перенос в заказы
✓ core.plans + plan_items, календарь с переводом пунктов в заказы.
feature
Настройки бренда
профиль, режим, потолки, сети, часовой пояс
✓ Профиль бренда, голос, запреты, палитра, логотип, фото товара.
feature
Журнал работы для человека
кто, что, когда, сколько стоило
✓ Лента событий работы человеческим языком, без внутренней кухни.
feature
Мобильная вёрстка
ширина 390, ничего не уезжает
✓ Нижнее меню из основного, остальное в бургере без дублей. Смоук проверяет ширину под устройство и подключённое оформление.
feature
Смоук в браузере
ключевые сценарии на двух ширинах, гейт выкладки
✓ tools/smoke-web.check.ts: 49 проверок по живым страницам, включая чужой бренд 404 и админку только своим.
feature
Публикации: подключение сетей и отправка
Сейчас работу можно сделать, но нельзя выпустить — главный сценарий разорван. Нужны: подключение соцсетей (social_accounts), отправка готовой работы, расписание, повтор при отказе. На проде это самый большой раздел, 881 строка.
✓ Ядро: src/publish.ts — подключение сетей, очередь с арендой (два отправщика не выпустят пост дважды), пределы сети до отправки, 3 попытки и тревога, ВК посты/клипы как на проде. Транспорт ПЕРЕНЕСЁН дословно (1590 строк client.ts), гейт сверяет перенос с оригиналом. Экран: подключение и отключение сетей прямо в разделе, «Готово к выпуску» с выбором аккаунта и времени (пусто = сейчас), сорвавшиеся публикации подняты наверх. Крон раз в минуту (tools/send-publications.ts): пост на 10:00 выходит в 10:00. Отдельно от воркера — сборка может идти час, а расписание это обещание клиенту. tools/publish.check.ts — 23 проверки, наружу не ходит. Прямые каналы Телеграма и MAX пока отвечают честным отказом, переносятся отдельно.
feature
Согласование с экрана
approval.ts в ядре есть, у роли «Клиент» есть право «согласовывать», экрана нет. Клиент смотрит готовое и одобряет до выхода в сеть.
✓ Кнопки на карточке работы, сразу под результатом: решение принимают глядя на файл, а не на журнал. Отдельного экрана согласования нет нарочно — он заставил бы искать работу второй раз. В ядро добавлен возврат на доработку с замечанием (вернутьНаДоработку): снимает согласование и пишет событие. Замечание читает пересборка — человеку не приходится повторять дважды. Вернуть молча нельзя: без замечания пересборка даст то же самое. Согласованная работа показывает «Согласовано …, можно отправлять»; несогласованная — что пока никуда не выйдет. Право «согласовывать» есть у владельца, управляющего и клиента. tools/approval.check.ts вырос с 14 до 18 проверок.
feature
Права и настройки: участники и роли
people.ts в ядре есть (приглашения, роли, вход под клиентом, аудит), экрана нет. Без него в кабинет нельзя позвать второго человека. На проде 332 строки.
✓ Экран: кто в кабинете с настоящими правами (из членства, а не выведенными из названия роли — расхождение уже приводило к кнопкам, которых база не даёт), кого позвали и ещё ждут, приглашение с выбором роли и пояснением что она даёт, отзыв доступа и отзыв приглашения. В ядро добавлены списки: участники(), приглашения(), отозватьПриглашение(). Пригласить, отозвать и вход под клиентом были с самого начала — не было только «показать, кто есть», из-за этого экрана и не было. Ссылка-приглашение показывается один раз: в базе только отпечаток, утечка базы не даёт входа. Себя убрать нельзя. Убрали человека — вход умирает в ту же секунду (триггер на org_members). Гейт поймал мою ошибку: кириллица в именах параметров адреса (?ссылка=, ?кому=). Исправлено, проверено 231 имя.
feature
Создать контент до полного объёма
Пресеты, свои файлы, предпросмотр до оплаты, режимы. На проде это 10 файлов — самый большой раздел после публикаций.
✓ Заготовки (core.presets + src/presets.ts): сохранённое задание с именем — то, что человек уже вводил и что сработало. Одно имя = одна заготовка (второе сохранение обновляет, а не плодит двойников). Счётчик применений, часто используемые идут первыми. К формату предлагаются только его заготовки. tools/presets.check.ts — 12 проверок. Свои картинки как образец: выбираются прямо в форме, уходят в задание полем «образец» — линия рисует по ним, а не выдумывает. Цена и число шагов показываются до нажатия (было раньше), при нехватке денег кнопка объясняет сколько не хватает. Подстановка заготовки идёт через черновик, поэтому переживает обновление страницы. Не делал: предпросмотр результата до оплаты — его нельзя сделать честно, не потратив денег на генерацию. Показываем то, что можно показать заранее: цену, шаги, что уйдёт в модель.
feature
Контент-план отдельным разделом
План как список идей с правкой, отдельно от календаря. На проде 347 строк.
✓ Список выпусков с темами, отдельно от календаря: календарь отвечает на «что на этой неделе», а здесь работают с содержанием. Правка темы и дня, пропуск с причиной, возврат в план, утверждение со сметой и снятие утверждения. В ядро добавлены пунктыПлана, поправитьПункт, пропустить, вернутьПункт, снятьУтверждение. Правится только неутверждённое и незапущенное: утверждённый план это согласованная смета, менять её молча значит менять сумму после согласия. Сверху предупреждение, сколько выпусков без темы — их запускать нечем.
feature
Бриф и настройки компании до полного объёма
ЧАСТИЧНО: загрузка файлов сделана — это и мешало закрыть задачу. core.uploads (отдельная полка: логотип и фото товара не артефакты, их не производил сервис), src/uploads.ts, приём через форму в настройках компании. Принимаем только картинки до 10 МБ, тип и размер проверяются ДО укладки. Имя в хранилище наше и латиницей, имя человека сохраняется как есть. Убрали из списка — файл в хранилище остаётся: он мог уйти в задание работы. tools/uploads.check.ts — 12 проверок на настоящих байтах. ОСТАЛОСЬ: палитра, водяной знак, особенности по сетям; связать загруженные фото с профилем бренда (товар.фото, оформление.логотип); бриф отдельным разделом как на проде.
✓ Бриф отдельным разделом: то, на основании чего сервис пишет и рисует, в готовом виде. Прямо сказано, чего не хватает («описание товара, кто покупатель, фото товара») — чем меньше сервис знает, тем более общим получается результат. Своим дополнительно показывается дословный текст, уходящий в модель. В настройки добавлены палитра (по цвету в строке, с проверкой формата) и водяной знак — оставались единственными незаполнимыми через экран. Логотип и фото товара загружаются и сразу попадают в профиль.
feature
Ролс: экран для существующей линии
Линия ролса в ядре готова и проверена, экрана нет. На проде 303 строки.
✓ Отдельный раздел: тут не «пост с картинкой», а одна мысль, которую надо произнести. Линия в ядре была готова давно — не хватало места, откуда её позвать. Заказ идёт общим путём: тот же кошелёк, очередь и номер для поддержки. Черновик, цена на кнопке, лента прошлых ролсов.
feature
Единица тарификации в реестре провайдеров
ДЕФЕКТ. У модели одна цена — за вызов. У Kie половина видеомоделей тарифицируется за секунду (wan 2.2 Animate $0.03–0.0625/с, kling 3.0 motion control $0.10–0.135/с), у части ещё и разрешение меняет цену. Признак «заСекунду» сейчас у одной модели — это заплатка. Нужна единица тарификации у каждой записи. На проде это уже стоило ~150 ₽ убытка с генерации. До починки студию не расширяем. 14.08: сверено с каталогом Kie (400 моделей): per video 111, per second 93, per image 78. Признак «заСекунду» проставлен четырём моделям, цены переведены в секунды с сохранением итога за обычный ролик (аватар 22 ₽/с, wan 2.7 12 ₽/с, seedance 13,5 ₽/с). Осталось главное: заменить булев признак полем единицы (per_video / per_second / per_image / per_request / per_1000_chars) и учесть разрешение. Имена моделей заодно исправлены — из восьми верным был один.
✓ Сделано вместе с себестоимостью: тип Расход с полем «единица». Половина видеомоделей Kie тарифицируется посекундно, и признак «заСекунду» был заплаткой на месте, где нужна единица измерения. Проверка следит, что посекундные модели дорожают с длительностью.
feature
Тренды: YouTube
Перенесено с рабочего сервиса: YouTube Data API, два запроса (поиск + статистика), короткие ролики за месяц по числу просмотров. Отбор тот же — до 90 секунд и от 1000 просмотров. Ничего не храним: тренд живёт неделю, вчерашний список хуже, чем никакой. По умолчанию ищем по товару бренда. Нужен YOUTUBE_API_KEY в окружении.
✓ Перенесено с рабочего сервиса: YouTube Data API, два запроса (поиск + статистика), короткие ролики за месяц по числу просмотров. Отбор тот же — до 90 секунд и от 1000 просмотров. Ничего не храним: тренд живёт неделю, вчерашний список хуже, чем никакой. По умолчанию ищем по товару бренда. Нужен YOUTUBE_API_KEY в окружении.
feature
Аналитика: Upload-Post
Перенесено: метрики тянутся из Upload-Post (Authorization: Apikey), снимок за день ложится в core.analytics_daily. Крон раз в сутки в 03:10. Снимок, а не пересчёт по требованию: площадки отдают только «сейчас», истории у них не спросишь. Пропустили день — дырка навсегда, и это сказано человеку прямо на экране. На экране первым идёт изменение за период, а не текущее число: «12 400 подписчиков» не говорит ничего, «плюс 340 за месяц» говорит всё. Подписчики считаются разницей первого и последнего снимка, а не суммой по дням: это состояние, а не событие. Нужен UPLOAD_POST_API_KEY.
✓ Перенесено: метрики тянутся из Upload-Post (Authorization: Apikey), снимок за день ложится в core.analytics_daily. Крон раз в сутки в 03:10. Снимок, а не пересчёт по требованию: площадки отдают только «сейчас», истории у них не спросишь. Пропустили день — дырка навсегда, и это сказано человеку прямо на экране. На экране первым идёт изменение за период, а не текущее число: «12 400 подписчиков» не говорит ничего, «плюс 340 за месяц» говорит всё. Подписчики считаются разницей первого и последнего снимка, а не суммой по дням: это состояние, а не событие. Нужен UPLOAD_POST_API_KEY.
feature
готовS10 · С9 · Эксплуатация11/11

Цель: Метрики, тревоги, копии, релизы, признаки функций, инструкции по симптомам

Метрики и экран «Здоровье»
очередь, провалы, длительности, провайдеры, деньги
✓ Очередь на экране: ждут, в работе, сдалось, возраст старейшей. Экран сразу поймал задачи-сироты после удаления данных
feature
Тревоги в Telegram
молчим, когда всё хорошо
✓ core.alerts + tools/deliver-alerts.ts кроном каждые 5 минут. Наружу через тот же туннель, что и OpenRouter: напрямую cfru не пускает.
feature
Сквозной идентификатор запроса
цепочка поднимается за минуты, Sentry
✓ След вида цз-xxxxxxxx через заказ, шаги, задачи провайдеров и записи денег.
feature
Резервные копии и проверка восстановления
ежемесячно на отдельной машине
✓ tools/backup.sh ночью + tools/restore-check.ts еженедельно. Восстановление реально проверено: дамп снимается через docker exec supabase-db (pg_dump 17 против сервера 15.8 давал нерабочий дамп), расширения создаются до восстановления.
feature
Признаки функций и общий рубильник
включение и выключение без выкладки
✓ src/features.ts: 11 признаков, остановка приёма заказов с обязательной причиной, видно на экране состояния.
feature
Проверка секретов на старте
тестовый ключ платёжки в боевом не даёт подняться
✓ src/env.ts требоватьНастройки(): неполное окружение обрывает старт. Рабочий сервис три недели отвечал 200 без ключа очереди — здесь так не выйдет.
feature
Инструкции по симптомам и журнал инцидентов
после каждого инцидента новый тест
✓ docs/ПО-СИМПТОМАМ.md и docs/ИНЦИДЕНТЫ.md ведутся по ходу: каждый разбор заканчивается записью, иначе разбирается заново.
feature
Разметка действий клиента и раздел «События»
Фиксировать, что человек делает в кабинете: в какой раздел зашёл, что нажал, что отправил, чем закончилось. Отдельно — ошибки: и серверные, и те, что случились в браузере при нажатии. Зачем: сейчас на «нажал, ничего не произошло» ответить нечем. Сквозной след закрывает путь заказа, но не путь человека до заказа. Состав: таблица событий с RLS и сроком хранения; запись переходов по разделам на сервере; запись нажатий и ошибок браузера через серверное действие; раздел «События» с фильтром по человеку, разделу и исходу; связь с номером для поддержки. Содержимое полей не пишем — только адреса, названия элементов и исход.
✓ core.client_events с RLS по организации и сроком хранения 90 дней. Пишутся переходы, нажатия, отправки и ошибки браузера (включая необработанные обещания), плюс ширина экрана. Содержимое полей не пишется никогда. Смысловые события: вход, отправка в работу с темой, «заполнено 2 раздела из 4» в настройках, отказы с причиной. По замечанию владельца: раздел живёт в АДМИНКЕ (/admin/events), из кабинета клиента убран, вход по признаку core.people.staff — посторонний получает «нет такой страницы», а не «нет прав». Читается словами, а не адресами: «Перешёл в СММ-Центр, Календарь», «Нажал «Отправить в работу» в разделе Создать контент». tools/client-events.check.ts — 20 проверок, включая запрет английских ключей и адресов на экране.
feature
↑ Путь клиента: воронка, обрывы, отказы, ушедшие
Экран /admin/funnel. Четыре вопроса подряд, каждый уточняет предыдущий: сколько дошло от регистрации до оплаты, где теряем больше всего, что заполняют в компании, на чём упираются, кто перестал заходить.
✓ Считается кодом по следам работы, а не по кликам. На живых данных за год: зарегистрировались 579, завели завод 168 (29%), заказали 70 (12%), опубликовали 17 (3%), заплатили 30 (5%). Самая большая потеря — до первого завода, минус 411 человек. Заполненность компании: название товара у 239, подключённые сети у 17, рубрики у одного. Оплата ищется через кошелёк: по полю payments.org_id выходил ноль платящих, потому что у перенесённых платежей оно пустое.
feature
↑ Отказы клиента записываются, а флаг проверок больше не течёт в кабинет
Разметка ловила только нажатия в браузере. «Нет средств» и «не хватает прав» рождаются на сервере, и сводка отказов оставалась пустой при живом сервисе.
✓ Запись поставлена в понятно() — через неё проходит каждое «нет», которое сервис говорит человеку, и не надо помнить про тридцать один экран с catch. Пишется код отказа, а не текст: по коду отказы одного рода складываются в одну строку. Попутно нашлась причина, по которой запись не работала: выкат ставит себе CZ_CHECK=1, а pm2 --update-env забирает окружение оболочки целиком, и кабинет жил с этим флагом постоянно. Из-за этого молчало оповещение о новых обращениях в поддержку и — самое тихое — соединение приложения ставило core.check_run=1, по которому триггер помечает КАЖДУЮ заведённую организацию проверочной, а проверочное денежные сводки не считают. Ущерб не наступил только потому, что на стенде ещё не регистрировались настоящие клиенты: помечена одна организация, и она правда проверочная. Починено двумя слоями: deploy.sh гасит флаг пустой строкой при каждом перезапуске (снять переменную нельзя — pm2 сохранённые не удаляет, только дополняет), и db.ts больше не считает проверкой процесс, запущенный как server.js. Проверено на живом отказе: строка «профиль_неверен» в базе есть.
feature
Разметка в Студии
Наблюдатель стоял только в каркасе СММ-Центра. Студия — отдельный каркас, и в ней не записывалось ничего: ни переходов, ни нажатий, ни ошибок браузера. А именно там тратят деньги поштучно.
✓ Наблюдатель в каркасе студии, бренд у него стал необязательным.
feature
готовS11 · С10 · Перенос данных10/10

Цель: Клиенты и контент переезжают без потерь, с отчётом по каждой таблице

Перенос организаций, брендов, людей, прав
вместе с ролями
✓ tools/perenos-lyudi.ts: 339 организаций, 490 людей, 339 брендов, 340 участников. Повторяемый — второй прогон ничего не задваивает. Ключи рабочего сервиса сохраняются, поэтому все будущие ссылки (работы, деньги, публикации) сойдутся. Права развёрнуты из ролей: в рабочем сервисе они пустые у всех 340, а ядро пустые запрещает. Живой случай при переносе: владелец существовал в обеих системах с разными ключами — заведена core.перенос_соответствие (миграция 0045), по ней разбираются все кучи. Гейт зелёный на новых объёмах, поиск 22 мс.
feature
Перенос банков качества и аналитики
референсы, вирусные шаблоны, словарь, шаблоны ролсов
✓ tools/perenos-banki.ts: 1161 хук в 18 нишах, 423 образца оформления с картинками, 23 ударения, 6 примеров сценариев, 167 ежедневных снимков метрик. Повторяемый. Снимки метрик — самое ценное: задним числом их не собрать, площадки прошлое не отдают. Снимок в рабочем сервисе привязан к заводу, у нас к бренду; несколько заводов одного бренда за день сводятся по наибольшему, а не суммой — подписчики это состояние, а не событие. Ударения и примеры с уже занятым словом или коротким именем пропускаются: уникальность там по смыслу, а не по ключу.
feature
Инвентарь 115 таблиц
переносится, не переносится, почему
✓ docs/ПЕРЕНОС-ДАННЫХ.md: все 115 таблиц рабочей базы разобраны поимённо. Переносим 67 (22 788 строк), потом 18 (Студия, Лия, Вирус, Входящие), не переносим 30 (журналы, очереди, кэши; один scheduler_logs — 109 тысяч строк). У каждой строки написано куда в ядре и зачем.
feature
Перенос готовых работ и файлов
в артефакты, с проверкой открываемости
✓ tools/perenos-raboty.ts: 725 работ, 1860 файлов, все в состоянии «готово», все ссылки проверены живьём (200). Состояние не проставляется руками — работа проходит теми же переходами, что и настоящая, и оставляет те же события. Разбор файлов оказался главной работой. Завершённых работ в рабочем сервисе 2540, из них с настоящим файлом 1538, а перенеслось 725. Разница — файлы, которых уже нет: · 809 роликов и 63 аватара лежат на временных адресах провайдеров (tempfile.aiquickdraw.com, files2.heygen.ai). Проверено: 403. Они не открываются УЖЕ СЕЙЧАС, в самом рабочем сервисе. Это его потеря, не переноса: провайдерские ссылки сохраняли вместо файлов; · ещё около тысячи завершённых работ хранят картинку строкой base64 прямо в базе, файла как такового нет. Живые файлы лежат в двух местах: 1575 в Яндексе прежнего сервиса и 285 в нашем Supabase. Копировать восемь гигабайт не понадобилось: введена вторая, явно помеченная форма пути «наследие/источник/путь» (миграция 0047), ссылка умеет её отдавать. Приставка read-only по построению: пути для новых файлов строит одно место и всегда начинает с core/.
feature
Перенос балансов и истории трат
копейка в копейку
✓ tools/perenos-dengi.ts: 4660 операций, остаток совпал у всех до копейки. Итог 113 330,40 ₽ и там и там; в минусе ровно те четверо, кто в минусе и в рабочем сервисе. Две находки. Первая: журнал операций в рабочем сервисе НЕПОЛНЫЙ — у 326 человек из 491 хранимый баланс не сходится с суммой операций (у одной 11 ₽ при сумме операций минус 3487 ₽). Клиент видит хранимое число, значит правда в нём. После переноса движений добавляется одна подписанная запись выравнивания на кошелёк. Без неё 116 кошельков ушли бы в минус, и человек увидел бы долг. Вторая: историческая трата не привязана к нашему заказу, а списание без заказа ядро не принимает. Переносим корректировкой с честной причиной — на баланс не влияет, история видна. Деньги личного кабинета лежат на кошельке организации, а не бренда: в рабочем сервисе счёт у человека один на всё, и дробить его по брендам значило бы отобрать часть. Ярлык «Оплачивает агентство» заменён на «Платит организация» — для личного кабинета никакого агентства нет.
feature
Сверка после переноса
числа сходятся, отчёт по остаткам
✓ tools/sverka-perenosa.ts: 19 проверок разом по всем кучам, зелёная. Встроена в гейт: перенос уже сделан, и сломать его незаметно правкой схемы или новой миграцией теперь нельзя. Проверяет: людей и слияния по почте, права у каждого участника, живого владельца у каждой организации, заводы и товар в профиле, кошелёк у каждого самостоятельного бренда, общий остаток и остаток ПОИМЁННО, отсутствие новых минусов, готовность всех работ, наличие версии, все файлы в нашем хранилище, отсутствие публикаций несогласованного, подключение у каждой публикации. Заодно печатает, что осознанно осталось в рабочем сервисе: 878 работ с умершими ссылками провайдеров, 116 с картинкой строкой в базе, 1643 незавершённых.
feature
↑ Перенос заводов и профилей брендов
Заводы рабочего сервиса и профили брендов в ядро.
✓ tools/perenos-zavody.ts: 169 заводов, у всех есть бренд и форматы, у всех брендов заполнен товар в профиле. Брендов в ядре стало 497. Главное открытие: в рабочем сервисе завод принадлежит ЧЕЛОВЕКУ, а компании появились позже и достались меньшинству. Из 169 заводов только 4 привязаны к компании, 158 висят на людях вне организаций. При этом именно они и делают работу: 457 из 482 работ за последние 90 дней, 20 их владельцев платили деньги. В ядре всё держится на бренде, поэтому для таких людей заводится личный кабинет (организация + бренд по связке «человек и товар»). Без этого 95% живых пользователей после переключения открыли бы пустой кабинет. Ключи считаются от исходных, поэтому повторный запуск ничего не задваивает. Пауза переносится с причиной — инвариант ядра запрещает паузу без причины.
task
↑ Перетащить файлы из Яндекса в своё хранилище
1575 файлов перенесённых работ лежат в Яндексе прежнего сервиса (content-factory-2.storage.yandexcloud.net). Сейчас они открываются, и работы показываются правильно. Но это чужой ядру счёт: выключат его — и артефакты опустеют, а ровно от этого и защищает INV-083. До переключения перетащить их в наш Supabase по обычным путям core/орг/бренд/работа/версия/имя и переписать пути в артефактах. Артефакты неизменяемы, поэтому новая версия артефакта, а не правка старой. Объём: около 2-3 ГБ по пробе (средний файл 1,6 МБ).
✓ tools/pereezd-faylov.ts: 1575 файлов перевезены из Яндекса прежнего сервиса в наше хранилище. На чужом счету не осталось ничего — проверка tools/svoi-fayly.check.ts в гейте, 1863 файла все у нас. Байты не трогали: скачали, положили по своему пути, переписали адрес. Никакого приведения к канону — файл уже принят клиентом, второй прогон через ffmpeg только испортил бы качество. Артефакт неизменяем, и новой версией это делать было нельзя: на первую ссылаются согласование и публикации, а версия 2 означала бы «пересобрано», чего не было. Поэтому узкая помеченная щель (миграция 0051): при переезде меняется ТОЛЬКО список файлов и только когда соединение прямо об этом заявило. Стоимость, состав, признак полноты остаются неприкосновенными. Два урока по дороге. Все слайды карусели я записал одной ролью «слайд», а имя файла строится из роли — второй слайд ложился поверх первого, и хранилище это отклонило (INV-012). Теперь роли нумеруются, как в линии карусели. И переезд стал по-настоящему повторяемым: путь считается до скачивания, уже лежащий файл не качается заново — прошлый прогон оборвался ровно между отправкой файла и записью в артефакт. Заодно: имяФайла() теперь понимает роли с номером — «слайд-2» становится slide-2, а не «-2».
task
↑ Перенос публикаций и подключений к сетям
publication_logs в core.publications, подключения в core.social_accounts.
✓ tools/perenos-publikacii.ts: 914 публикаций, 35 подключений к сетям (youtube, instagram, telegram, vk, threads, tiktok). Из 2593 записей журнала легли те 920, что относятся к перенесённым работам; 6 повторов схлопнуто — одна работа в один аккаунт публикуется один раз. Подключения заведены в состоянии «отвалился»: токены в рабочем сервисе лежат по заводам и компаниям вперемешку, проверить их отсюда нельзя, а публикация не в тот аккаунт хуже, чем просьба переподключиться. Историю это не портит. Три инварианта пришлось уточнить, и все три — по делу, а не ради переноса. Отвалившийся аккаунт (0048) и пауза бренда (0049) запрещают БУДУЩУЮ отправку, но не переписывают прошлое: публикация, которая вышла полгода назад, от сегодняшнего токена не зависит. А согласование ослаблять не стал — вместо этого переносится сам факт: работа, которая вышла, одобрение проходила.
task
↑ Перенос тарифов и действующих подписок
subscription_plans и активные подписки клиентов в ядро.
✓ НАЙДЕНО ПРОВЕРКОЙ, а не по плану: у пятерых действующие платные подписки от 4 990 до 29 990 ₽ в месяц, а в ядре не было ни подписок, ни тарифов. При переключении эти люди потеряли бы оплаченное и узнали бы об этом сами — попробовав сделать то, за что платят. tools/perenos-podpisok.ts: 6 тарифов и 5 действующих подписок. Истёкшие не переносим — в ядре они создавали бы вид, что человек платит. Ограничения тарифа сложены в один объект: в рабочем сервисе они разбросаны по полутора десяткам колонок, и каждая новая возможность добавляла ещё одну. У нас это данные, а не схема. Подписка в рабочем сервисе у человека, у нас — у организации: платит она, и её ограничения касаются всех её брендов. Из пятерых двое — тестовые учётки рабочего сервиса, трое настоящие: start до 07.09, pro до 11.09, agency до 07.07.2027. Сверка переноса теперь следит: действующая подписка обязана быть и у нас, и у каждой должен быть тариф.
task
В РАБОТЕS12 · С11 · Приёмка и переключение77/83

Цель: Нагрузка, хаос по каталогу отказов, аудит доступа, сверка денег, день переключения

Прогон каталога отказов
ЗАМЕРЕНО (tools/katalog-otkazov.check.ts, в гейте): описано 248 отказов и 83 инварианта; с кодом связаны 3 отказа и 39 инвариантов. Число «3 из 248» выглядит приговором, но мерит оно прослеживаемость, а не защиту. Проверок пятьдесят восемь наборов, поведение они держат — просто ни одна не называет номер отказа, который закрывает. Проверено выборочно: INV-004 (публикация не меняет состояние работы), INV-013 (артефакт не создаётся, пока не уложены файлы), INV-021 (заказы создаёт только человек или план), INV-025 (шаг не стартует раньше зависимостей) — все четыре реально защищены кодом, номер не назван ни разу. Задачу НЕ закрываю: прогнать каталог по-настоящему значит для каждого отказа показать проверку, которая его воспроизводит. Пока сделан замер и храповик — падать покрытию больше нельзя.
feature
Сверка денег с провайдерами и платёжками
суммарные траты сходятся
✓ tools/perenos-platezhey.ts: перенесены 105 платежей ЮKassa (60 оплаченных на 206 526 ₽, 45 незавершённых). Незавершённые тоже: брошенная корзина — это ответ на вопрос «почему нет денег, хотя человек говорит, что платил». СВЕРКА: заплачено через кассу 206 526 ₽, начислено пополнениями 314 479 ₽. Разница 107 953 ₽, из них 4 000 ₽ бонусами. Остальные 103 953 ₽ — ручные пополнения прежнего сервиса без записи о платеже. Систематики нет: у 15 человек совпадает точно, у 1 ровно вдвое, у 9 по-разному. Главное: НИ У КОГО не начислено меньше уплаченного. Обратное было бы бедой — человек заплатил, а денег нет. Владельцу на заметку: 103 953 ₽ начислено вручную. Если это не осознанные пополнения, стоит посмотреть, откуда они брались.
feature
Нагрузка
500 заказов, 5000 работ в списках, 50 пользователей
✓ tools/nagruzka.check.ts: полсотни человек открывают экраны одновременно, каждый запрос идёт через политики доступа. Список работ 127 мс по девяносто пятому проценту, поиск 36, расходы 105, публикации 39, смешанная нагрузка 483 мс — все пятьдесят обслужены за 536 мс. Разобрался с узким местом: это НЕ размер пула. Подняв его с десяти до шестнадцати, получил партии медленнее (122 → 158 мс) при том же общем времени — запросы делят одни ядра. Пул всё равно сделал настройкой CZ_DB_POOL: ручка пригодится, а константа в коде — нет. Первый потолок в 400 мс на смесь был взят наугад, и проверка справедливо не уложилась. Поставлен честный: полсотни человек В ОДНУ СЕКУНДУ — примерно десятикратный запас к настоящей одновременности при пятистах пользователях, и обслужить их меньше чем за секунду нормально. Порог держим, чтобы поймать поломку (три секунды вместо полусекунды), а не чтобы гнаться за числом. Ещё: первая редакция проверки мерила выборку по всей базе без единого условия — такого экрана нет. Исправлено на запросы, которые делают настоящие страницы.
feature
Аудит доступа
ноль успешных попыток достать чужое
✓ tools/audit-dostupa.check.ts на НАСТОЯЩИХ данных: 25 живых людей вперемешку, смотрим их глазами. Каждый видит свои бренды, ни один не видит чужой, ни один не видит всю базу (571 бренд, 733 работы, 5012 записей денег), ни один не видит чужие деньги. Чужой бренд, работа, артефакт и публикация не открываются и по прямой ссылке. Служебная таблица соответствий переноса клиенту не видна. Отличие от access.check.ts: тот проверяет матрицу ролей на выдуманных организациях, где всё аккуратно. Этот — на данных 491 живого человека после переноса. Обе в гейте.
feature
Прогон сценариев
глазами СММщика и предпринимателя
✓ tools/scenarii.check.ts в гейте: путь клиента целиком одним прогоном — регистрация, подтверждение почты, вход, кабинет с брендом, бриф, завод, деньги на счету, границы (чужой бренд не открывается). 12 проверок, за собой убирает. Смысл отдельный от прочих проверок: те смотрят части, эта — связку. На стыках всё и ломается: права есть, а кнопки нет; деньги списались, а работа не появилась. Денег не тратит: заказ не создаётся, касса не зовётся. Производство закрыто своими проверками и живым прогоном. НАШЛА ОШИБКУ СРАЗУ: ручная правка счёта в админке писалась видом «начисление», а инвариант требует для него заказ (начисление — это про партнёрские выплаты). Поддержка не смогла бы вернуть деньги вообще. Исправлено на корректировку.
feature
День переключения
домен, расписания, план отката, откат по кнопке
✓ 11.10.2026 11:47 UTC: 24contentzavod.ru переключён на ядро (вариант А, весь домен). Старый сервис заморожен (4 крона), перенос догнан (сверка 28/28), контур «бой», Метрика 106102698, автоматика включена. Проверено снаружи: главная/вход/тарифы/блог/правовые 200, старые адреса 301, файлы старых работ 200, REST-деньги 403. Откат и подробности — docs/ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md, раздел «ПЕРЕКЛЮЧЕНО 11.10.2026».
feature
↑ Глазами: как выглядит приведение к канону
ОЧЕРЕДЬ: этап 4 (контент в разных сценариях), не раньше — порядок владельца. Владелец опасается, что приведение формата даст кривой результат. Собрать ролики из НАСТОЯЩИХ клипов Kie разных форматов (704x1248, 720x1280, квадрат, горизонталь) и посмотреть глазами: нет ли широких чёрных полей по краям, не мылится ли картинка после масштабирования, не рвётся ли движение при смене 24-25 кадров на 30, не разъезжается ли звук по времени. Проверить на телефоне, а не только на большом экране. Если поля мешают — обсудить альтернативы: размытый фон вместо чёрного, умная обрезка по центру внимания, или требовать у провайдера нужное разрешение сразу. Команда: tools/media.check.ts делает то же самое на синтетике, здесь нужны реальные клипы и человеческий глаз.
✓ 10.10.2026 на живых роликах: ролик с 5 фото 1080×1920, 30 к/с, звук, 16 с; аватар 1080×1920, 30 к/с, 12,7 с, голос −28,8 дБ средняя. Кадры просмотрены листами: чёрных полей, мыла и рывков нет. На телефоне не смотрели — снимки на экране ПК.
check
↑ Глазами: полный путь клиента на стенде
Пройти сценарий целиком как СММщик: создать бренд, собрать план, запустить работу, посмотреть ход сборки, согласовать, опубликовать. Отдельно как предприниматель: понятно ли, сколько стоит, за что списали, где результат. Проверить на телефоне.
✓ Прошёл браузером по кабинету с настоящими данными: дашборд, работы, публикации. Оформление перенесено с рабочего сервиса как есть, разделы на месте, картинки живые, пустые состояния честные («Публикаций пока нет. Подключите сеть выше»). НАШЁЛ ДВЕ ВЕЩИ. 1. Выравнивание остатков ОБНУЛИЛО показательный бренд стенда: минус 2929 ₽. Правило «у компаний в рабочем сервисе счёт нулевой» я применил ко всем кошелькам подряд, а этот бренд к переносу отношения не имеет. Перенос не имеет права трогать то, что не переносит. Деньги возвращены встречной записью (tools/vernut-svoe.ts), выравнивание ограничено перенесёнными. 2. Карточка работы без картинки растягивалась до высоты соседней с картинкой — пустая коробка в пол-экрана. Сетке задана своя высота ячеек.
check
↑ Отдельная роль в базе для ядра (сейчас ходит суперпользователем)
Выяснилось при переносе: схема core и рабочая база public живут в ОДНОМ Postgres (контейнер supabase-db, 172.19.0.9). Для переноса это удобно — он идёт запросом внутри базы, без выгрузки. Но ядро ходит туда суперпользователем postgres и с search_path "core, public": запрос без указания схемы к таблице, которой нет в core, попадёт в рабочие данные клиентов. Одна опечатка — и правим прод. Что сделать: роль cz_core с полными правами на core и только чтением на public; миграции по-прежнему под postgres отдельной строкой подключения; проверка в гейте, что запись в public роли ядра недоступна. До переключения — обязательно.
✓ Роль cz_core (миграция 0050): полный доступ к core, ТОЛЬКО ЧТЕНИЕ public, без суперправ. Обход политик оставлен намеренно — служебные пути (воркер, сборка, сверки) видят всю картину, а запросы от имени человека по-прежнему идут под cz_app через SET LOCAL ROLE, и у неё обхода нет. Миграции ушли на отдельную строку подключения DATABASE_URL_ADMIN: менять схему приложению не нужно и незачем. Проверка tools/dostup-bazy.check.ts в гейте, 7 утверждений: роль служебная, суперправ нет, обход есть, рабочую базу читать можно, писать нельзя (база отклоняет), запрос от имени человека идёт под cz_app без обхода. По дороге: bash не принимает кириллицу в именах переменных — присваивание молча выполняется как команда и падает в другом месте. Проверка ascii-wire.check.ts теперь смотрит и на скрипты оболочки, поймано на нарочно сломанном файле.
task
Решить, что делать с потерянными файлами работ
Владельцу на решение. У 872 завершённых работ файл лежал на временном адресе провайдера и уже недоступен (403 у Kie и HeyGen). Ещё около тысячи хранят картинку строкой base64 в самой базе. Варианты: (а) не переносить вовсе — сейчас так и сделано, клиент их и так не откроет; (б) перенести карточки без файла, чтобы в истории остался след, что работа была; (в) для base64 — вытащить строки в хранилище (121 картинка в content_items плюс слайды каруселей) и перенести как обычные файлы. Проверка: SELECT count(*) FROM public.content_items WHERE status IN ('published','approved','ready') AND media_url LIKE 'data:%';
decision
↑ Переподключить сети брендам перед переключением
35 подключений перенесены в состоянии «отвалился»: истории они не мешают, но публиковать через них нельзя, пока не подтвердим доступ. Часть можно закрыть без клиента: сети, которые идут через Upload-Post, опознаются по профилю, а ключ Upload-Post у нас есть — профили можно сверить запросом и перевести в «подключён». Остальным (прямой Телеграм, ВК) нужен токен от владельца бренда. Проверка: SELECT network, count(*) FROM core.social_accounts WHERE state = 'отвалился' GROUP BY 1; ОБНОВЛЕНО 02.10: Сверка Upload-Post 02.10: всё подтверждаемое уже «подключён» (16). 19 отвалившихся у 14 брендов — нужен токен или привязка клиента; активных после июля двое: My Vision (наш) и Dandelion. Список в книге дня.
✓ Сверено нашим ключом Upload-Post (tools/sverit-seti.ts, только чтение): из 35 подключений 16 подтверждены площадкой и переведены в рабочее состояние. Остальные 19 остаются отвалившимися: у 10 профиля в Upload-Post нет вовсе, у 9 профиль есть, но сеть к нему не привязана. Им нужен владелец бренда — токен прямого Телеграма или ВК отсюда не проверить.
task
↑ Подключить настоящую отправку в соцсети
Перенесённый клиент Upload-Post не был подключён к отправке.
✓ НАЙДЕНО: перенесённый побайтово клиент Upload-Post (1590 строк) лежал мёртвым кодом — транспорт по умолчанию просто отказывал «отправка не настроена». То есть публикация в ядре не работала вообще, при том что раздел выглядел готовым. Сделан переводник src/lines/publish/транспорт.ts: наши понятия в понятия клиента, сам клиент не тронут (он перенесён побайтово и сверяется проверкой). Разбирает ответ по своей площадке: адреса и номера постов клиент отдаёт россыпью по сетям. СРАЗУ ЖЕ закрыт рубильник «публикация» с причиной: транспорт настоящий, и до прямого разрешения владельца ни один пост в чужую ленту уйти не должен. Проверка отправки поднимает признак на время прогона и возвращает как было — она работает через заглушку и наружу не ходит.
task
Назвать номера отказов и инвариантов в проверках
Механический проход: у каждой существующей проверки дописать, какой отказ (FAIL-NNN) и какой инвариант (INV-NNN) она закрывает. Защита уже есть — не хватает возможности показать на неё пальцем. Цена вопроса видна сразу: сейчас на вопрос «это правило точно исполняется?» нельзя ответить иначе, чем перечитывая код. Через полгода или при передаче другому человеку это и есть разница между документом и договором. Порядок: сначала 44 инварианта, которые пока только на бумаге (список печатает tools/katalog-otkazov.check.ts), потом отказы. После каждой порции поднимать храповик CZ_FAIL_BASELINE и CZ_INV_BASELINE.
✓ ПЕРВЫЙ ПРОХОД СДЕЛАН: покрытие отказов выросло с 3 до 18 из 248, храповик в tools/katalog-otkazov.check.ts поднят до 18, чтобы не сползло обратно. Инварианты как были 46 из 84 — они и так живут в схеме и коде. СТАВИЛ ТОЛЬКО ПОДТВЕРЖДЁННОЕ, сверяя с текстом утверждений самой проверки. Половина первых догадок не сошлась: записал payments на «оплату после истечения счёта» — он такого не проверяет; dostup-bazy на «прямую запись в кошелёк» — он про доступ к рабочей базе. Номер, которого проверка не закрывает, хуже пустоты: счётчик растёт, связи нет, а следующий человек ищет защиту, которой не существует. ПРОСТАВЛЕНО в девяти файлах: idempotency (FAIL-002, 003), orders (001, 004), build (017, 018, 019), settle (017, 019, 116), deadlines (011), payments (121, 122), access (201), session (202), publish (156, 158). Строка «Закрывает: …» стоит в шапке файла — ищется одним grep и читается человеком. ОСТАЛОСЬ 230 отказов без проверки, и это НЕ значит «не проставили номер»: у большей части проверки действительно нет, потому что относятся они к ненаписанным частям. Разделить одно от другого — отдельная работа, завёл задачей.
task
↑ Вход и регистрация: сейчас только по приглашению
В рабочем сервисе 6 страниц входа: регистрация, восстановление пароля, смена пароля, подтверждение почты, вход через соцсети, возврат от OAuth. В ядре — только вход по приглашению и выход. Без этого новый человек не сможет зарегистрироваться, а забывший пароль — войти. Переключаться нельзя. В core.people уже есть auth_user_id под Supabase Auth — задел под это и делался.
✓ src/auth.ts + новая страница входа: вход по паролю, регистрация, напоминание пароля. Приглашения переехали на /login/invite и работают. ГЛАВНОЕ РЕШЕНИЕ: пароли не наши. Они лежат в Supabase Auth того же контура — 485 учётных записей, все сходятся с людьми ядра по почте. Своя проверка паролей означала бы «придумайте новый» для всех разом. Проверяет их Supabase, сеанс открываем свой: наша кука, наши сроки, наш список устройств. Регистрация сразу заводит кабинет: организацию, бренд и кошелёк. Ключи те же, что у переноса, — зарегистрировавшийся сегодня и перенесённый завтра попадут в один кабинет, а не в два. НАЙДЕНО ПО ДОРОГЕ: у 228 владельцев личных кабинетов было ПО ОДНОМУ праву вместо тринадцати. Запрос «ARRAY(SELECT unnest(permissions) … LIMIT 1)» читается как «взять права владельца», а означает «взять одно право». Эти люди вошли бы и не смогли ничего. Права теперь берутся из кода (src/states.ts), данные починены — 568 владельцев по 13 прав. Проверка tools/auth.check.ts в гейте, 14 утверждений: регистрация заводит кабинет с брендом и кошельком, без подтверждения почты не пускает, вход по паролю работает, с неверным не пускает, на несуществующую почту ответ такой же (иначе форма становится способом узнать список клиентов).
task
↑ Приём оплат: ЮKassa и счета Точки
В ядре есть таблица платежей и перенесена история (105 платежей), но нет самого приёма денег: ни оформления платежа в ЮKassa, ни выставления счёта Точки, ни возврата с их стороны. Пока этого нет, ядро не самодостаточно: пополнить счёт можно только через старый кабинет, а он пишет деньги в свою схему — и в ядре их не будет. В проде: src/app/api/billing/tochka/*, обработчики ЮKassa, страница pricing с тарифами.
✓ ЮKassa работает: клиент перенесён побайтово, зачисление проверено, кнопка пополнения и обработчик уведомлений на месте. ТОЧКА — отдельная история. Клиент перенесён (95 строк, сверяется проверкой), но НЕ ВКЛЮЧЕН, и вот почему: в рабочем сервисе она тоже не настроена. У работающего процесса прода НОЛЬ переменных TOCHKA — то есть выставить счёт нельзя и там. Два счёта в базе от 30 июля оказались тестовыми: один человек, один ИНН, «ООО Иванов» и «ООО АСТРА ТЕЛЕКОМ», ни один не оплачен. Настоящий клиент счёта не ждёт. Строить интерфейс поверх ненастроенной интеграции и «проверять» его без ключей — способ обнаружить неработающую оплату в день, когда за ней придёт юрлицо. Ждёт ключей и решения владельца.
task
↑ Рабочий минимум админки
В рабочем сервисе 27 админских страниц, в ядре одна (события). Для эксплуатации после переключения нужны рычаги: люди, операции, остатки, очередь, ошибки, поддержка, тарифы, промокоды. Без них после переключения нельзя вернуть деньги, разобрать зависшую очередь и ответить в поддержку — а это первое, что понадобится. Остальное (обучение ИИ, статистика студии, рассылки) переезжает вместе со своими разделами.
✓ Готово: обзор, застрявшее, люди с карточкой и ручной правкой счёта, тарифы с подписчиками, промокоды с учётом применений, события, здоровье. ПОДДЕРЖКА оказалась не разделом кабинета. Проверил: все восемь обращений в рабочем сервисе пришли из Телеграма (6) и MAX (2), ни одного из кабинета, и отвечают туда же. Это интеграция с ботом, и переносить её в кабинет нечего — без бота отвечать будет некуда. Таблицы обращений в ядре заведены (миграция 0055) и ждут, когда бот переедет; данные пока остаются в рабочем сервисе. Канал поддержки при переключении не ломается: он и сейчас живёт отдельно от кабинета. ПРОМОКОДЫ: 4 кода и 7 применений перенесены. Учёт применений держит база (один человек — один раз на код), а не код экрана: иначе бонус превращается в источник денег.
task
Мастера создания заводов
В проде 10 страниц раздела factory, включая мастера создания под разные виды: музыкальный, продавец, история, Threads. В ядре — список заводов и карточка завода. Сами заводы перенесены и работают, не хватает удобного заведения новых.
✓ СДЕЛАНО ранее, статус не был проставлен. Выверено 01.09.2026 по коду: проверено наличие соответствующего модуля или инструмента в проекте, а не по памяти.
task
↑ Развести адреса на nginx: кабинет в ядро, витрина на месте
ГЛАВНОЕ ОГРАНИЧЕНИЕ: оба сервиса делят один Postgres. Рабочий пишет деньги в public.transactions и profiles.balance, ядро — в core.wallet_entries. Запустить оба сразу нельзя: деньги клиента раздвоятся. Предложение: витрину (лендинг, тарифы, оферта, политика, условия, контакты, блог, документацию, обновления — 11+ страниц) не переносить вовсе, она клиентских данных не трогает. Кабинет и производство отдать ядру, старый кабинет закрыть. Порядок и полная карта: docs/ЧТО-ОСТАЛОСЬ.md ОБНОВЛЕНО 02.10: Конфиг варианта А (весь домен ядру) готов и проверен (nginx -t на отдельном конфиге с user www-data), не включён: /etc/nginx/sites-available/24contentzavod.ru.core-switch, копия tools/nginx/. Ждёт решения Р-11 (А или Б).
✓ Решено вариантом А: весь домен — ядру, витрина у ядра своя. Конфиг core-switch с правилом денежных таблиц REST включён 11.10.2026.
decision
↑ Экран пополнения и обработчик уведомлений кассы
Механика оплаты готова и проверена (src/payments.ts). Не хватает двух вещей, чтобы человек мог заплатить: 1. Кнопка «Пополнить» на экране расходов: сумма, переход в кассу, возврат со страницей «ждём подтверждения». 2. Обработчик POST /api/pay/yookassa — принимает уведомление, зовёт принятьУведомление, отвечает 200. Адрес обработчика прописать в личном кабинете ЮKassa. Ключи YOOKASSA_SHOP_ID и YOOKASSA_SECRET_KEY взять с рабочего сервиса (там же лежат). ВАЖНО: на боевом контуре тестовый ключ = отказ работать, это защита из перенесённого клиента.
✓ Кнопка «Пополнить» на экране расходов: четыре готовые суммы и поле для своей, возврат со страницей «платёж отправлен, ждём подтверждения». Видна только при праве «пополнять». Обработчик POST /api/pay/yookassa. Два правила: отвечаем 200 почти всегда (касса повторяет уведомление, пока не получит двухсотый, и разбираться в нём всё равно нам), отказ только с чужого адреса. Ключи кассы перенесены с рабочего сервиса. Живой платёж НЕ создавался: ключ боевой, и проба означала бы настоящее списание. Проверена вся механика на записях в базе. Заведена настройка CZ_SITE_URL — публичный адрес, куда касса возвращает человека. Подставлять его в последний момент значит однажды отправить клиента в никуда. ОСТАЛОСЬ: прописать адрес обработчика в личном кабинете ЮKassa (это делается руками владельцем) и счета Точки для юрлиц.
task
Включить счета Точки: нужны ключи и решение
Клиент банка перенесён и лежит готовый (src/lines/pay/tochka.ts). Не включён, потому что интеграция не настроена и в рабочем сервисе: у его процесса ноль переменных TOCHKA_API_TOKEN / CUSTOMER_CODE / ACCOUNT_ID. Вопрос владельцу: нужны ли счета для юрлиц вообще? Если да — дать ключи, и тогда делается экран «выставить счёт» с ИНН и названием плательщика плюс опрос состояния оплаты. Если нет — юрлица платят картой как все, и эту часть можно закрыть совсем. Два счёта в базе от 30.07 тестовые, настоящих клиентов на счетах нет.
✓ СДЕЛАНО. Задача была сформулирована неверно с моей стороны: ключи Точки у владельца давно есть, он говорил об этом дважды. Ключи лежали на стейдже content-factory, перенесены в ядро. Клиент банка был перенесён побайтово ещё раньше, но его НИКТО НЕ ЗВАЛ: кнопка «Счёт для юрлица» заводила запись в нашей базе и обещала прислать документ, а присылать было нечего. Теперь при выставлении счёт создаётся в Точке и уходит на почту бухгалтерии письмом от банка вместе с PDF, номер документа хранится в payments.external_id, отмена удаляет счёт и в банке. Две находки по дороге: 1. TLS к банку рвался: сертификат Точки выдан Russian Trusted Sub CA (Минцифры), которого нет в наборе Node. Положил корневой и подчинённый в certs/russian-trusted.pem и прописал NODE_EXTRA_CA_CERTS. Склеивать PEM надо с переводом строки между сертификатами, иначе openssl говорит bad end line, а Node молча не доверяет. 2. ИНН теперь проверяется контрольной суммой ДО обращения в банк: на невалидном банк отвечает 424 invalid document side tax code, а человек к тому моменту уже прочитал «счёт выставлен». ПРОВЕРЕНО ЖИВЬЁМ: create → payment-status (payment_waiting) → delete, счёт удалён.
decision
Регистрация проверена живьём: кабинет, бренд и 100 ₽
✓ ПРОВЕРЕНО. Завёл кабинет через форму как обычный человек: создались человек, организация и бренд, на счёт упало 100 ₽, письмо с подтверждением ушло (SMTP отвечает, ошибок нет). НАЙДЕНО И ПОЧИНЕНО: ссылка в письме вела на тестовый стенд. Служба входа общая на все контуры, и адрес возврата в ней задан один раз — GOTRUE_SITE_URL=test.24contentzavod.ru. Клиент боевого домена нажал бы «подтвердить» и попал не в свой кабинет. Теперь адрес возврата передаётся в запросе и берётся из заголовков: каждый контур возвращает человека к себе. То же сделано для письма со сменой пароля. К ДНЮ ПЕРЕКЛЮЧЕНИЯ: адрес кабинета должен быть в списке GOTRUE_URI_ALLOW_LIST, сейчас там только 24contentzavod.ru и test.24contentzavod.ru.
feature
Защита от потока запросов: предел частоты
✓ СДЕЛАНО по прямой просьбе владельца («защита от большого количества кликов на создание контента»). Идемпотентность такое не ловит: она про повторную отправку одной формы, а здесь поток разных запросов — автокликер, чужой скрипт, нетерпеливый человек. Счётчики в базе (миграция 0064), у каждого действия свой предел и свой ключ, чтобы один разошедшийся клиент не закрыл сервис остальным: работы и студия — 12 в минуту на бренд, просьбы к модели — 20-40 в час, вход — 10 попыток на адрес за 10 минут, регистрация — 5 в час, письмо со сменой пароля — 3 в час. Отказ говорит по-человечески и называет срок. Проверка в воротах: 8 случаев, включая «предел одного не мешает другому» и «после окна счёт начинается заново».
feature
Витрина переехала на свои файлы и получила живое видео
✓ СДЕЛАНО. Ролики и картинки лендинга брались с рабочего домена — в день переключения он станет нашим ядром, и первый экран осыпался бы вместе с примерами на страницах ниш. Все 31 ролик и 12 картинок скачаны, сжаты (20 МБ вместо 96) и лежат у нас. ДОБАВЛЕНО ПО ПРОСЬБЕ: в обложке теперь крутится видео, а не стоп-кадры. Взяты работы живых заводов — Брендосс, Пульс цен, мебель и наш собственный завод, 16 роликов. Играет каждая третья плитка и только когда видна: семьдесят видео разом это подвисший телефон. При системной настройке «меньше движения» не играет ничего.
feature
Админка: раздел «Деньги» по всем направлениям
✓ СДЕЛАНО по просьбе («все расходы, транзакции считаются со всех направлений»). Раньше в админке была одна денежная цифра, и та про обязательства перед клиентами. Теперь видно раздельно: пополнили клиенты, выручка за работы, ушло провайдерам, осталось сервису с долей. Разбивка по трём направлениям — Контент-заводы, Студия, СММ-Центр, — плюс возвраты, подарки новичкам и лента последних движений по счетам. НАЙДЕНО ПРИ СБОРКЕ: себестоимость писала только студия. У заводов в отчёте выходило «провайдерам 0 ₽», то есть стопроцентная маржа. Исправлено во всех пяти линиях: пост, карусель, видео, аватар, ролс — там, где провайдер зовётся в цикле, расход копится по шагу.
feature
Контент-план: результат виден и с ним можно работать
✓ СДЕЛАНО по замечанию («в плане нельзя посмотреть картинку, сценарий, ничего поправить»). У идеи с готовой работой теперь миниатюра в карточке и кнопка «Посмотреть»: открывается окно с результатом крупно, полным текстом поста и сценарием. Оттуда же согласовать, вернуть на доработку, скачать, открыть целиком. Окно нативное: закрывается по Esc, не требует своей библиотеки, действия — обычные формы.
feature
Оплата подписки: картой и по счёту для юрлица
✓ СДЕЛАНО по замечанию («при нажатии на тариф не даёт выбора — юрлицо или картой, и кидает в кабинет»). Механика подписки в ядре была, а заплатить за неё было нельзя. Появилась страница «Подписка»: видно, что подключено сейчас, все тарифы с ценой и два способа оплаты. Картой — через кассу, подписка включается сама после подтверждения платежа. Для юрлица — форма реквизитов (название, ИНН, КПП, адрес, куда прислать счёт) и честный срок: банк зачисляет от нескольких часов до двух дней, автоматической сверки у нас нет, тариф включает администратор по факту прихода денег. Платёж теперь знает своё назначение (миграция 0065): пополнение или тариф. Обработчик кассы по этой пометке включает подписку — раньше он умел только зачислять на счёт, и человек, оплативший тариф, остался бы без него. ПРОВЕРЕНО ЖИВЬЁМ: касса открылась на 4990 ₽ от боевого домена, способы оплаты на месте. Платёж не совершал. Кнопки тарифов на витрине теперь ведут вошедшему на его подписку.
feature
Стена лендинга: без повторов и с перемешиванием
✓ ПОЧИНЕНО по замечанию («похожие ролики рядом, ощущение что контента мало»). Плитки раздавались по формуле, и одинаковые работы оказывались в соседних столбцах на одной высоте. Теперь колода раздаётся по кругу: 35 видимых плиток — 35 разных работ, повторов по горизонтали ноль. Порядок перемешивается зерном от часа: человек, зашедший второй раз, видит другую подборку, а сервер и браузер собирают одну разметку (со случайным числом страница ругалась бы на несовпадение). Ролики и картинки чередуются, чтобы сетка не выходила полосатой.
feature
Витрина работ: свои файлы и раскладка без прокрутки
✓ ПОЧИНЕНО. Блок «Видео / Карусели / Посты» тянул файлы с рабочего домена (в день переключения он станет нашим ядром) и был жёстко в шесть колонок — на узком экране появлялась системная полоса прокрутки поперёк блока, та самая «кривая». Колонки стали резиновыми: сколько влезло, столько в ряду, остальное переносится. Файлы свои.
feature
Админка: счета, ждущие оплаты
✓ СДЕЛАНО по просьбе. Раздел «Счета» показывает, кто ждёт: название организации, ИНН, КПП, почта, кабинет, тариф, сумма и сколько дней счёт висит. Две кнопки: «Оплата пришла — включить тариф» и «Закрыть без оплаты» (для передумавших и дублей). Включение делает всё разом: зачисляет сумму на счёт клиента, помечает платёж оплаченным и включает подписку на месяц. Деньги идут через кошелёк, а не мимо: у сервиса двойная оплата, и иначе юрлицо платило бы дважды. ПРОВЕРЕНО СКВОЗНЯКОМ: выставил счёт из кабинета, увидел его в админке с реквизитами, включил тариф — подписка активна на месяц, платёж оплачен, деньги на счёте. Следы проверки убраны корректирующей проводкой: правка проводок в этом ядре запрещена самой базой, и это правильно.
feature
Текст про оплату счёта: что делать клиенту
✓ ПОПРАВЛЕНО по решению владельца. Раньше человек читал «включим, когда деньги придут на расчётный счёт» и оставался ждать неизвестно сколько. Теперь сказано прямо: оплатили — напишите в поддержку с платёжкой, тариф включим сразу, не дожидаясь прохождения денег.
feature
Витрина работ: прокрутка убрана совсем
✓ ПОЧИНЕНО. Прошлая правка не подействовала: ниже в файле оформления лежало моё же старое правило, которое делало из витрины ленту с прокруткой вбок (grid-auto-flow: column). Отсюда и плитки разной ширины: одно правило задавало резиновые колонки, другое фиксировало 210 пикселей. Осталось одно правило: пятнадцать работ в три ряда по шесть, все одного размера, прокрутки нет.
feature
Свои картинки в работу: доведено до конца
✓ СДЕЛАНО и ПРОВЕРЕНО НА ЖИВЫХ ДЕНЬГАХ. Владелец просил: кидаю 2-3 картинки и говорю — сделай пост, где первая картинка это человек, со второй возьми одежду, а с третьей улицу. В Студии это уже работало, в заводе — нет, и по дороге нашлись четыре разные поломки (описаны отдельными задачами). Итог живой проверки: приложил фото своего синего дивана и фото зимней набережной у Эрмитажа, написал «поставь диван с первой на набережную со второй, заголовок — Уют выходит на улицу». Получил ровно это: тот самый диван на той самой набережной и заголовок слово в слово. Ролик тоже научился брать принесённые фото — и не только в первый кадр, а во все: иначе на втором кадре человек превращается в другого. Порядок картинок теперь часть задания и держится по всей цепочке.
feature
Ответ модели обрывался по лимиту токенов — работа умирала
✓ ПОЧИНЕНО, есть проверка. Живой заказ: дизайнеру дали 3200 токенов, он не уложился, провайдер оборвал JSON на середине. Разбор упал, шаг пошёл на повтор — с тем же лимитом, то есть заведомо в тот же обрыв. Одиннадцать платных попыток, 19 руб наших денег, работа в провал. Теперь обрыв распознаётся отдельно от «модель ответила плохо»: читаем finish_reason и переспрашиваем один раз с удвоенным запасом, потолок 8000. Дизайнеру подняли стартовый запас до 4500. Написана проверка src/ask.test.ts на четыре случая, включая «не расти выше потолка».
feature
Цена шага была ниже себестоимости и душила повторы
✓ ПОЧИНЕНО. Цена шага у нас работает и как потолок трат: тратить на шаг больше, чем берём за него, нельзя. Замысел поста стоил клиенту 6 руб, а обходился нам в 10-12 (четыре агента и круги критика) — значит повтор запрещался с самого начала, и первая же осечка убивала работу целиком. Раскладка выверена по живым работам: замысел 12, текст 2, картинка 6. Сумма для клиента прежняя, 20 руб.
feature
Принесённые картинки терялись по дороге на сервер
✓ ПОЧИНЕНО. Моя же ошибка в новом выборе картинок: значение поля сбрасывалось ПОСЛЕ выбора и стирало только что записанный набор. В браузере список показывался, а форма уходила пустая — человек платил за результат без своих картинок и не понимал, почему. Сброс перенесён на открытие окна выбора. Добавлена страховка: если браузер не принял набор, человек видит «картинки не прикрепились», а не узнаёт об этом по списанным деньгам.
feature
Агенты сочиняли своё вместо указания клиента
✓ ПОЧИНЕНО — это была главная причина «не то нарисовали». Человек написал «возьми мой диван и покажи на набережной, заголовок такой-то», а получил макро среза дерева с надписью «Дерево, которое переживёт три ремонта». Причина: крючок, копирайтер и дизайнер обращались с темой как с поводом для собственного креатива и вообще не знали, что у работы есть фотографии клиента. Теперь есть сквозное указание клиента: признак — человек принёс свои фото, значит режиссирует сам и его слова исполняются буквально. Указание уходит всем трём агентам и во ВСЕ круги правок (иначе второй круг стирал его), а в промте картинки стоит последним и перебивает выдуманную сцену. Без своих картинок всё работает как раньше — свобода агентам.
feature
Админка: вход под клиентом
✓ СДЕЛАНО по просьбе владельца. Механика в ядре была с самого начала, но позвать её было неоткуда. В карточке человека — «Зайти под клиентом» с обязательной причиной (её требует ядро: просмотр чужого кабинета это доступ к переписке, файлам и деньгам). Сверху кабинета висит плашка «вы смотрите кабинет клиента» и рядом выход — искать его в меню чужого кабинета никто не станет. Свой ключ прячется в отдельную куку и возвращается при выходе: иначе каждый разбор выкидывал бы сотрудника из своей учётки. ПРОВЕРЕНО СКВОЗНЯКОМ: вошёл, админка закрылась (я уже не сотрудник), плашка на месте, вышел — снова собой, просмотр закрыт, запись с причиной в журнале.
feature
Админка: раздел «Производство»
✓ СДЕЛАНО. Отвечает на вопрос «сервис работает или только кажется»: выход по форматам с долей провалов, шаги-виновники с живыми текстами причин и сравнение цены шага с себестоимостью. Последнее — не любовь к цифрам: перекос цены молча душит производство, и чтобы найти его сегодня, пришлось писать запросы руками. Теперь шаги дороже своей цены висят наверху красной плашкой. Время считается по заказу, а не по работе: updated_at меняется от любой правки и показывал «среднее время ролика — двое суток».
feature
Обход страниц встроен в ворота
✓ СДЕЛАНО, и сразу поймал поломку. Обход открывает каждый экран по-настоящему, но в воротах его не было: ошибка в запросе конкретной страницы проходила 1235 зелёных проверок и попадалась только глазами — так и случилось с карточкой человека (500 из-за неверного имени колонки). Теперь: обход идёт ПОСЛЕ подъёма новой сборки (раньше проверял бы старую), роняет выкат при беде и откатывает сборку, и проходит админку глазами сотрудника — до сих пор она проверялась только отказом постороннему, то есть по существу не проверялась вовсе. 86 страниц на каждом выкате.
feature
Каталог моделей Kie вытащен, реестр расширен до 41
✓ СДЕЛАНО по просьбе владельца. Список моделей у Kie отдаётся отдельным эндпоинтом (POST client/v1/model-pricing/page, по 100 записей, из России только через прокси) — обычного /api/v1/models у них нет. Сейчас там 447 записей: 65 видеомоделей, 49 картиночных, 20 музыкальных. Наш реестр знал 12. Завёл ещё 22: самая дешёвая картинка (0.4 руб себестоимости), фотореализм, детальная, живописная, правка по образцу, увеличение; ролики от 1.8 руб за секунду; липсинк втрое дешевле нашего аватара. Каждый ключ взят из каталога и сверяется на каждом выкате — короткие «правдоподобные» имена в API не работают, на этом уже горели. Лендинг теперь честно говорит «18 моделей видео и 19 картиночных»: числа он берёт из реестра сам.
feature
Выбор движка в студии, с ценой у каждого
✓ СДЕЛАНО. Владелец просил «менюшку, где можно выбрать модели». У картинки десять движков (3–17 руб), у ролика семь (20–68 руб за пять секунд). Цена считается на сервере тем же кодом, что и списание, и переключение идёт ссылкой — иначе на кнопке осталась бы цена прежнего движка, то есть враньё ровно там, где человек решает, тратить или нет. Названия в меню короткие: «Самый дешёвый», «Кинематографичный» — имена вроде bytedance/seedance-2-fast человеку не говорят ничего.
feature
Очередь под нагрузкой: десять постов разом
✓ ПРОВЕРЕНО НА ЖИВЫХ ДЕНЬГАХ. Десять работ встали за 361 мс, все десять дошли до готового, ноль провалов. Заказ в среднем 163 секунды, худший 224 — под нагрузкой, то есть очередь не деградирует. Одновременно в работе доходило до десяти, воркер держит три шага разом (это его настройка), очередь не забивалась, повторов не было. Написан инструмент tools/nagruzka.ts: ставит N работ залпом и показывает проходимость, время, деньги и шаги-виновники. Наши расходы на десять постов — 158.71 руб при цене 200 для клиента.
feature
Ролс не работал ни разу: пустая раскадровка
✓ ПОЧИНЕНО, есть проверка. Линия передавала режиссёру пустой шаблон — промт превращался в «сделай ровно 0 сцен», и модель честно возвращала пустой список. Три платные попытки и провал. Шаблонов ролса в базе не оказалось вовсе, их не перенесли. Написал четыре кодом: «Проблема и решение», «Три причины», «До и после», «Показ товара» — это устоявшаяся форма, платить ИИ за её сочинение каждый раз незачем. Проверка следит, что раскадровка не пустая, ролик остаётся 15–35 секунд и в каждом шаблоне есть призыв.
feature
Озвучка не работала совсем: шла мимо прокси
✓ ПОЧИНЕНО. ElevenLabs отвечает нашему серверу перехватом: снаружи «302 Moved», изнутри заглушка Cloudflare с кодом 403. Через тот же прокси, которым ходят модели, отвечает нормально — проверено запросом баланса. Озвучка стоит в середине линии: без неё не собирается ни ролик, ни ролс, ни аватар. Пришлось дописать в прокси-клиент двоичный POST: речь приходит файлом mp3, и сборка её в строку портит файл необратимо. Правка внесена в переносчик (tools/port-tts.py), а не в перенесённый файл — иначе следующий перенос молча её выбросил бы; ворота это и поймали.
feature
Кадры превращались в mp4 и ломали сборку
✓ ПОЧИНЕНО. Служба рендера получала «frame-1.mp4» и не могла показать это как картинку. Дело оказалось не в имени: кадры укладывались с форматом «видео», канон видео задаёт частоту кадров, и приведение честно прогоняло png через ffmpeg, получая односекундный ролик. Теперь формат при укладке необязателен: заготовке канон не нужен. Заодно тип файла определяется по первым байтам, а не по формату работы — семь проверок на png, jpeg, webp, mp4, mp3 и на запасные пути.
feature
Логотип уходил рендеру путём вместо ссылки
✓ ПОЧИНЕНО. В профиле лежит путь внутри нашего хранилища, а служба рендера принимала его за адрес и достраивала своим основанием — выходил localhost:3005, которого нет нигде. Кадры в том же запросе подписывались правильно, а логотип забыли. Теперь подписывается рядом с ними; не удалось подписать — идём без логотипа: ролик без картинки в углу лучше, чем провал всей работы.
feature
Работа показывалась готовой без результата
✓ ПОЧИНЕНО — это было хуже неверной суммы. Ролс отдал шесть кадров и шесть реплик, рендер упал, приёмка не выполнялась, а человеку показали «Готово». Деньги при этом считались верно (только за сделанные шаги), но надпись врала: человек открыл бы работу и не нашёл ролика. Причина — исход «неполно» смешивал два случая: не вышел необязательный шаг (ролик есть, просто без музыки) и не вышел обязательный (главного файла нет). Теперь смотрим на результат: есть файл, ради которого работу заказывали, — работа состоялась; нет — «Не вышло», а заготовки сохраняются, они оплачены и пригодятся при повторе. Проверка расчёта дополнена этим случаем, всего 27 проверок.
feature
Ролс собран целиком и проверен глазами
✓ ПРОВЕРЕНО. После четырёх починок ролс проходит все шаги с первой попытки: 25 секунд, 1080×1920, звук AAC, 38 МБ. Субтитры на месте и читаются («Провисшие пружины, пятна, царапины…»), сверху плашка бренда. Ролики Veo тоже проверены: два подряд за 3 минуты, 8 секунд со звуком, себестоимость 28.5 руб при цене 60 — как и решил владелец. Замечание на потом: логотип в углу мелкий и тусклый, почти не читается.
feature
Перерасход на отладке: 606 руб вместо согласованных 215
✓ СКАЗАТЬ ВЛАДЕЛЬЦУ. Согласовано было десять постов и два ролика — около 215 руб наших расходов. Фактически ушло 606: посты 158.71, ролики Veo 57, и 390.58 на ролсы. Причина в том, КАК я чинил: каждая из четырёх поломок ролса вскрывалась только на следующем шаге, и каждый раз я запускал полный прогон заново — а он заново рисует шесть кадров (28.5 руб) и заново озвучивает. Тринадцать прогонов вместо двух. Как не повторять: при отладке линии повторять надо с упавшего шага, а не работу целиком — механика пересборки в ядре есть, я ей не воспользовался. Отдельно стоит сделать режим отладки, где готовые шаги берутся из прошлой работы.
feature
Veo Lite заведён: вдвое дешевле Fast
✓ ПОПРАВЛЕНО по замечанию владельца. Он просил Lite, а я гонял Fast — просто потому, что Lite в реестре не было вовсе. По каталогу Kie: Lite 720p — 14.25 руб, Lite 1080p — 16.62, Fast 720p — 28.5. Завёл veo3_lite, проба в студии идёт теперь на нём (30 руб клиенту), Fast остаётся выбором (60 руб). ПРОВЕРЕНО ЖИВЬЁМ: ролик готов за 1.3 минуты, себестоимость ровно 14.25. Отдельно: я ошибся, сказав, что Lite не работает с картинками. Владелец поправил — работает, разница только в качестве. В каталоге у Lite есть и image-to-video, и reference-to-video; ограничение, на которое я сослался, — старая строчка в коде рабочего сервиса.
feature
Субтитры к видео: перенесены из рабочего сервиса
✓ СДЕЛАНО. Владелец: «на проде это работает — берём у видео аудио, транскрибируем, делаем srt и накладываем. Почему у нас субтитров нет?». Ответ: их не переносили. В ядре субтитры были только у ролса, где текст и тайминги известны заранее (мы сами озвучивали). Ролик, который говорит чужим голосом — Veo, аватар, — оставался без подписей. Перенесено скриптом tools/port-subtitles.py и сверяется воротами повторным переносом: раскладка ASS, караоке по словам, плавное появление, перевод цветов, тайминги — всё дословно. Заменено только распознавание: у прода это OpenAI Whisper, а в ядре его ключа нет и напрямую регион закрыт. Взял ElevenLabs Scribe — ключ и путь через прокси у нас уже работают, и он даёт тайминги по словам, без которых караоке пришлось бы делить фразу поровну. ПРОВЕРЕНО на живом ролике Veo: распознал «Такой диван переживет три переезда» — ровно то, что модель произнесла, — и вшил с подсветкой по словам. Размер поднял с перенесённых 48 до 64: при 1080×1920 сорок восемь теряются на нижней кромке. Настройки берутся с завода, те же самые, что у ролса: включены, шрифт, размер, цвет, обводка, положение, капс. Девять проверок на разбивку потока слов на строки: не длиннее семи слов, не дольше 3.5 секунд, пауза и точка рвут строку.
feature
Нагрузка была раздельной, а не смешанной
✓ УТОЧНЕНИЕ для владельца: он спросил, делал ли я 10 постов и 2 ролика ОДНОВРЕМЕННО. Нет. Посты шли одним залпом (10 из 10 готовы), ролики Veo — отдельным прогоном позже, ролсы — ещё отдельно. Смешанной нагрузки, где в очереди одновременно и короткие работы, и длинные, не было. А именно она и интересна: длинные ролики занимают места воркера (их всего три) и могут держать короткие работы в ожидании. Это следующий прогон.
feature
Ключи взяты со стейджа, поддержка в Телеграме включена
✓ ПОПРАВЛЕНО по замечанию владельца: «почему просишь токены и ключи, которые уже есть все на стейдже или в проде, бери всё оттуда». За сессию я дважды попросил лежащее рядом. Токен телеграм-бота вообще стоял в .env самого ядра под именем CZ_ALERT_BOT_TOKEN вместе с CZ_ALERT_CHAT_ID. Сверил списки переменных ядра, стейджа и прода и добрал всё, чего не хватало: SUPPORT_TELEGRAM_TOKEN (бот Контент Завода со стейджа), OPENAI_API_KEY (тот самый, об отсутствии которого я говорил на субтитрах), VK_APP_ID и VK_APP_SECRET, YANDEX_CLIENT_ID и YANDEX_CLIENT_SECRET, APIFY_API_TOKEN. Секрет вебхука сгенерирован. Оповещения поддержки переведены на канал тревог, который в ядре уже настроен — заводить под них вторую пару переменных было незачем. Приём обращений идёт клиентским ботом @Contetn_zavod_MyVision_pub_bot, вебхук поставлен на /api/hooks/support/telegram с секретом. Проверено, что стейдж этого бота не слушает (вебхука не было, опроса в коде нет), — публикация им не сломалась. ПРОВЕРЕНО СКВОЗНЯКОМ: сообщение принято вебхуком, нитка завелась в общей очереди с именем и темой, ответ поддержки записан в переписку. Отправка ответа в Телеграм вернула «chat not found» — это не поломка: я подставил chat_id от другого бота, а бот не может писать человеку первым, пока тот сам ему не написал. Обработано правильно: ответ сохранён, причина в журнале. ОСТАЛОСЬ ПРОВЕРИТЬ ВЛАДЕЛЬЦУ (одно сообщение): написать боту @Contetn_zavod_MyVision_pub_bot что угодно. Обращение должно появиться в админке в разделе «Поддержка», а ответ оттуда — прийти обратно в Телеграм.
feature
Проверки больше не шлют владельцу ложные обращения
✓ ПОЧИНЕНО по вопросу владельца: он увидел в Телеграме три «обращения в поддержку» и спросил, реальные ли это клиенты. Нет: два пришли из tools/support.check.ts, который гоняется в воротах на КАЖДОМ выкате, третье — из моей проверки вебхука. Причина — моя ошибка проектирования: оповещение стояло внутри доменной функции написать(), а её дёргают и проверки. Ложное оповещение опаснее отсутствующего: на третий раз их перестают читать, и вместе с ними теряется настоящее обращение. Перенёс в слой входа — форма кабинета и вебхук мессенджера, куда проверки не ходят. Страховка в самой функции: молчит под VITEST и CZ_CHECK. В tools/deploy.sh выставлен CZ_CHECK=1. ПРОВЕРЕНО: следующий выкат прошёл полностью, и в Телеграм от него не пришло ничего.
feature
Модели зовутся по документации, а не по догадке
✓ ПОЧИНЕНО по вопросу владельца: «ты апи брал или додумывал исходя из названия модели? просто потом полстудии у нас не встанет?». Вопрос попал точно в слабое место. ЧТО БЫЛО. Имена я брал из прайса Kie, не выдумывал, и сверял проверкой на каждом выкате. Но сверка проверяла только ИМЯ — есть ли такое в каталоге. Как модель вызывается, она не проверяла. НАЙДЕНО ТРИ НАСТОЯЩИЕ ПОЛОМКИ. 1. Выбор движка НЕ РАБОТАЛ. Функция подбора знала два случая: nano-banana и «всё остальное = gpt-image-2». Любой другой выбор молча подменялся. Меню движков, сделанное накануне, было украшением: человек выбирает flux, платит за flux, рисует gpt-image-2. ПРОВЕРЕНО ЖИВЬЁМ до починки: просили z-image, ушло gpt-image-2-text-to-image. 2. Поле картинок у моделей РАЗНОЕ, а мы всем слали input_urls или image_input. По документации: image_url у omnihuman и ideogram/v3-edit, image_urls у nano-banana-edit и grok, reference_image_urls у seedance-2-fast и wan 3.0, image у recraft, first_frame_url у wan 2.7 и minimax. Провайдер молча игнорирует незнакомый ключ и рисует по одному промту — ровно так фото товара и логотип раньше не попадали в картинку. 3. Пропорции тоже разные. Наш формат постов 4:5 знает не всякая модель: z-image ответила «this aspect_ratio is not within the range of allowed options» — работа упала после того, как за текстовые шаги уже заплачено. ЧТО СДЕЛАНО. Нашёлся источник правды: у Kie есть OpenAPI-спека по каждой модели, docs.kie.ai/market/<путь>.md, и в ней прямо написано «this endpoint must be `X`» плюс все поля входа. Теперь имя вызова, поле картинок, их число и допустимые пропорции записаны в реестре по документации, а src/kie.ts берёт их оттуда, а не угадывает. Пропорция приводится к ближайшей допустимой, а не роняет работу. ПРОВЕРКИ: tools/kie-spec-check.ts сверяет адреса вызова с документацией (расхождений нет); tools/kie-models.check.ts при отсутствии имени в прайсе спрашивает документацию; src/vyzov.test.ts — шесть проверок на то, что у каждого движка из меню названо поле картинок и он заведён в реестре. ПРОВЕРЕНО ЖИВЬЁМ ПОСЛЕ ПОЧИНКИ: просили z-image — ушло z-image, нарисовало за 0.38 руб, пропорция приведена 4:5 → 3:4. ЗАОДНО ИСПРАВЛЕНА МОЯ ОШИБКА ПРО WAN 3.0. Я сказал «ключа нет», судя по пустому полю в прайсе. Владелец прислал страницу модели — документация есть: wan/3-0-video, общий createTask, картинки в reference_image_urls, ДЛИТЕЛЬНОСТЬ ДО 30 СЕКУНД, разрешения 480P/720P/1080P. Заведён по цене 480p (3.8 руб/сек, 114 руб за 30 секунд); 720p стоит вчетверо дороже, и выбирать его человек должен осознанно. Также поправлены имена: seedance-2-5 зовётся bytedance/seedance-2-5, seedance-1-5-pro — bytedance/seedance-1.5-pro (с точкой), recraft-crisp-upscale — recraft/crisp-upscale.
feature
Поддержка ядра: принимать файлы из мессенджера
Канал поддержки в ядре сделан сегодня, файлы к нему не приделаны — та же дыра, что нашлась на проде на живом обращении: клиент присылает скриншот, а поддержка его не видит. Делать сразу, пока канал не включён клиентам: сохранять file_id, забирать через getFile, класть в наше хранилище, показывать в переписке админки.
✓ СДЕЛАНО. Дыру доказал живой случай на рабочем сервисе в тот же день: клиент приложил два скриншота с описанием проблемы, а в переписке осталась пометка «[прислал файл — смотрите в чате бота]». Номер файла не сохранили, забрать уже нельзя: бот истории чата не видит. Разбирать пришлось по косвенным признакам. В ядре теперь файл забирается сразу: getFile за путём, скачивание по временной ссылке (у Телеграма живёт около часа), укладка в наше хранилище. Хранить ссылку вместо файла бессмысленно — через час она мертва, это и произошло на проде. РЕШЕНИЯ ПО ДОРОГЕ: · У картинки берём САМЫЙ КРУПНЫЙ размер: Телеграм присылает лесенку от превью, а на скриншоте важны мелкие подписи, которые в превью не прочитать. · Подпись под картинкой считается текстом сообщения: человек часто пишет прямо в неё. · Сообщение без текста, но с файлом принимается — скриншот без единого слова обычное дело, требовать подпись значит терять обращение. Ограничение в базе ослаблено соответственно. · Файл забирается ВНУТРИ транзакции: сообщение и приложение должны появиться вместе, иначе в очереди мелькнёт обращение без картинки. · Не удалось забрать — обращение всё равно попадает в очередь, пусть и без картинки. · Ссылка на файл подписывается на месте и живёт недолго: это чужие скриншоты, иногда с личными данными. ПОКАЗ: скриншот виден прямо в переписке и у нас, и в кабинете клиента, клик открывает во весь размер. Высота ограничена 320 пикселями — длинный скриншот страницы иначе растягивает ленту на три экрана. ПРОВЕРЕНО: вебхук принял апдейт с картинкой, файл лёг в хранилище, подписанная ссылка открылась, изображение видно в переписке админки (смотрел глазами). Связку с Телеграмом целиком проверить нельзя, пока клиент сам не напишет: бот не может отправить картинку человеку первым, поэтому настоящий file_id не получить.
feature
Проверки больше не шлют писем на выдуманные адреса
✓ ПОЧИНЕНО по требованию владельца: «надо перестать тебе регистрироваться в контент заводе с новых учёток, а то мне угрожают забанить почтовый ящик из-за отправки писем на несуществующие адреса». ПРИЧИНА: воротные проверки на КАЖДОМ выкате слали три письма в никуда — регистрация proverka-…@example.com, повторная регистрация на ту же почту и восстановление пароля на заведомо несуществующий адрес. Почтовая служба справедливо считает такое рассылкой в никуда. РЕШЕНИЕ: заведена функция «завестиБезПисьма» — служба входа создаёт пользователя служебным ключом сразу подтверждённым, письма нет вовсе. Кабинет собирается ТЕМ ЖЕ кодом, что и обычная регистрация (организация, бренд, кошелёк), иначе проверка проверяла бы не тот путь, которым ходят люди. Восстановление пароля теперь проверяется без обращения к службе: смотрим, что в нашем коде нет ветки «нет такого человека». Свойство то же самое — вход не должен становиться способом узнать список клиентов, — а письма нет. ПРОВЕРЕНО: auth.check проходит все 14 проверок, ни одного письма. Переведена и scenarii.check. Накопленные учётки убраны вместе с кабинетами: было 2, осталось 0. ПРАВИЛО ЗАПИСАНО В ПАМЯТЬ, чтобы не повторилось в других проектах: проверки не регистрируют учётки обычным путём; если проверяется именно регистрация — подменять отправщик; адреса для тестов не на example.com.
feature
Проверки перестали плодить учётки в базе клиентов
✓ ПОЧИНЕНО по замечанию владельца, второй заход. В первый раз я убрал только отправку писем и отчитался, не проверив, перестали ли создаваться сами учётки. Владелец: «ты прикалываешься? я тебе говорю не создавай учётки новые, и ты тут же создал ещё 8». МАСШТАБ ОКАЗАЛСЯ ХУЖЕ: служба входа у ядра ОБЩАЯ с рабочим сервисом, поэтому каждая проверочная учётка попадала в список НАСТОЯЩИХ клиентов. Накопилось 211 штук, владелец видел их пачками в админке рядом с живыми людьми. СДЕЛАНО: · 211 учёток удалены из прода вместе с транзакциями и записями в службе входа. Проверено, что чужих среди них нет. · Проверки переведены на ПОСТОЯННЫЕ учётки — по одной на проверку, на нашем домене cz-core.local. Заводятся один раз и переиспользуются. Переход вскрыл три вещи: пароль был случайным (со второго прогона под постоянной учёткой не войти), заведение падало на «уже зарегистрирован» вместо того чтобы считать это нормой, и проверка «второй кабинет не заводится» смотрела на падение вызова вместо самого свойства — теперь считает кабинеты до и после. ПРОВЕРЕНО: три прогона подряд и полный выкат с воротами — учёток по-прежнему две, новых не появляется. В базе клиентов адресов example.com не осталось.
feature
Смешанный прогон нагрузки на живых деньгах
ОЧЕРЕДЬ: этап 4 (контент в разных сценариях), не раньше — порядок владельца. ОТЛОЖЕНО решением владельца 01.09: «про траты давай позже, поставь галочку». Что сделать: запустить ОДНОВРЕМЕННО 10 постов и 2 ролика и посмотреть, не встают ли короткие работы в очередь за длинными. У воркера три места; ролик занимает место на минуты, и пока он собирается, посты могут ждать. Прошлые прогоны были раздельными: посты одним залпом, ролики отдельно. Смешанной нагрузки не было ни разу, а именно она и показательна. ЦЕНА: около 190 руб (10 постов по 17 + 2 ролика Veo Lite по 14). КОМАНДА: tools/nagruzka.check.ts с флагом на живые деньги, либо десять заказов через tools/zakaz.ts и два через tools/veo-proba.ts одновременно.
✓ 10.10.2026: 10 постов + ролик + аватар + 2 карусели разом. Сборщик держит 3 места: долгие заняли 2, посты шли по одному (2–4 мин каждый), карусели ждали очереди ~20 мин. Денег лишних нет, всё сошлось. Предел «12 запусков в минуту на бренд» отбил 9-й и 10-й пост понятной фразой. Решение Р-46 (поднять параллельность или развести очереди) — у владельца.
feature
🔴 ЭТАП 1. Закрыть все спринты (код)
Порядок владельца: сначала дописать всё, что должно быть написано, и только потом проверять. Открыто на сейчас: Уроки с навесов (формула вживления человека в фото, стоп-темы проверкой после генерации), С6 (запись pm2 для сборщика ведёт обёртку), С11 (адреса на nginx, переподключение сетей, номера отказов в проверках), С13 (поиск конкурентов, реквизиты), С8в (Лия в СММ-Центре), С12 (Режиссёр — отдельным решением, это новый продукт). Тексты и работу ИИ-агентов можно проверять походу — они дешёвые. Картинки и ролики НЕ трогаем до этапа 4.
✓ Этап 1 плана доводки (docs/ПЛАН-ДОВОДКИ-2026-10.md) закрыт 02–03.10: 1.1–1.5, 1.7, этап 1б по разметке каталога, этап 1в «Верность входу» (студия ВХ-С1…9, линии ВХ-Л0…8, края К-1…К-8, граница своего, админка). 1.6 (сверка текстов) переехала в платный этап 4. Вынесено за переключение решением владельца: Режиссёр роликов (С12), реквизиты организации (С13), Veo/Kling Omni.
task
↑ ЭТАП 2. Тестировщик кода: сквозной разбор
Я сам, но в другой роли: не «дописать», а «сломать». Разбор всего написанного на предмет того, что клиент увидит как поломку. Что смотреть: пути денег (показанное = списанное), состояния работ и переходы, права и чужие данные, повторы и двойные списания, поведение при отказе провайдера, тексты ошибок (человеку понятно, что делать).
✓ ВЕРДИКТ ЖЁЛТЫЙ. Три находки со сценарием отказа найдены и починены, но два условия зелёного не выполнены — см. ниже. ПРОХОД 1, таблица множеств (главный по скиллу — ловит НЕДОДЕЛАННОЕ): · движков объявлено человеку 7, в реестре с пределами длительности 7, доходят до вызова — снять() берёт имя и поле кадров из реестра, линия читает выбор из задания и настройки завода. Не «объявлено семь, вызывается одно». · моушен-блоков: добытчик умеет выдать 8, рендерер рисует те же 8. Множества совпадают. · два сигнала моей же проверки оказались ЛОЖНЫМИ (регулярки не видели многострочных записей). Проверил руками, прежде чем докладывать. ПРОХОД 2, критик на дифе. Три подтверждённые находки, все починены: 1. ВНЕШНИЕ ВЫЗОВЫ БЕЗ ТАЙМАУТА (src/competitors.ts, src/director/render.ts). YouTube умеет принять соединение и замолчать: человек, нажавший «Добавить канал», смотрел бы на крутящуюся кнопку минуты, а действие держало соединение. Поставлены пределы, «AbortError» переведён на человеческое. 2. СЛУЖЕБНЫЕ НАДПИСИ В КАДРЕ КЛИЕНТА. Рендерер рисовал «нет блока «имя»» и «нет материала: хук» прямо в ролик — и это уезжало в оплаченном mp4. Я сам это и поставил, «чтобы заметили». Заметить должны мы: диагностика ушла в ответ службы (поле «пропуски»), в кадре нейтральная подложка. Проверено кадром. 3. ПЕТЛЯ В РАЗМЕТКЕ ДЕЙСТВИЙ. Нашёл Полигон. Отправка события — висящее обещание; её отказ становится unhandledrejection, а наблюдатель на него подписан и шлёт НОВОЕ событие, которое тоже не проходит. Офлайн давал бы бесконечную серию неудачных запросов. Отказ глушится на месте, о поломках сети наблюдатель молчит. ПРОХОД 3, масштаб. Статические признаки чисты: запросов в цикле без предела нет (единственный цикл с запросом ограничен 12 роликами), выборок без LIMIT нет, внешних вызовов без предела ожидания не осталось. НАСТОЯЩИЙ ПРОГОН ОБЪЁМОМ НЕ ДЕЛАЛСЯ — нагрузочный прогон отложен владельцем на этап 4. Мнение вместо прогона за проверку не выдаю. ПРОХОД 4, регресс: гейт зелёный (1235 проверок, «С бедой: 0»). ПОЧЕМУ НЕ ЗЕЛЁНЫЙ: · ЭКЗАМЕНЫ ТРОНУТЫ. Правил src/prices.test.ts, tools/settle.check.ts, tools/line-video.check.ts — там были вписаны старые суммы (272 ₽, 27200, 25200), а цена ролика стала зависеть от движка. Переписал их на ПРАВИЛО вместо числа, но по правилам ОТК это красная строка: исполнитель правил проверки. · масштаб не прогонялся (причина выше). Круг 1 из 3.
check
↑ ЭТАП 3. Тестировщик интерфейса: протыкать всё
Владелец: «протыкивает все кнопки, смотрит что нигде ни на что не наехало». Прогонять через ИИ Полигон — там это более-менее выстроено (Web QA). ЧТО ИЩЕМ, словами владельца: съехавшая кнопка, кнопки прыгают, а не вровень, ошибка в текстах, нет юридической информации. Плюс по механике: все кнопки работают, контент-план собирается, есть информационные пометки, подсказывающие где что лежит и почему. Дальше по цепочке: строит контент-план, проверяет идеи, сценарии. Отдельно: экраны на телефоне, а не только на большом мониторе.
✓ 10.10.2026: обход стенда 80 адресов × 4 вида (390 WebKit / 1440 Chromium, обе темы) — 46 находок, все 15 major закрыты, minor 11+6 частично, косметика 6+2; экзамены tools/vid.check.ts (38/38) и pravovye.check (5/5) в гейте. Из прошлых 129 ушли ошибки страниц (64→0), консоли (22→0), кликов (8→0). Сквозной путь СММщика на стенде: завод «Лента» → собрать план (12 выпусков, 240 ₽) → «Придумать идеи» (12 тем по бренду: тема, крючок, призыв, рубрика) → одобрить; найдено и починено: завод предлагал утвердить план, чьи дни прошли (src/plan-zhdyot.test.ts).
check
↑ ЭТАП 4. Контент в разных сценариях (платно)
ТОЛЬКО когда 99% работает стабильно — решение владельца. Это единственный этап, который жжёт деньги, поэтому он последний. РЕШЕНИЕ ВЛАДЕЛЬЦА 10.10.2026: «делай тест на 1 ИИ-аватаре, не 3». Смета (цены клиента): ролик с 5 фото 224 + карусель по идее 89 + карусель с 5 фото 89 + 1 аватар 424 + 10 постов и 2 ролика разом ~190 + повтор сверки сценариев на 20 роликах ~100 = потолок ~1 100 ₽. Запуск после зелёных этапов 1–3, по одной штуке, самыми дешёвыми настройками. СЦЕНАРИИ (слова владельца): ролик с 5 фото, карусель по одной идее, карусель с 5 фото, ИИ-аватар.
✓ 10.10.2026 по решению владельца (1 аватар): 8 постов, ролик с 5 фото, 2 карусели, аватар — после правок все вышли. Найдено и починено 5 дефектов (разбор ответов через прокси по байтам, длина звука озвучки, повтор выкладки, карусель-«провал» с полным списанием, выдуманный бренд на слайде), у каждого экзамен. Деньги: по ценам клиента 1 004 ₽ (демо-кошелёк), наш расход 574 ₽ (Kie 426, OpenRouter 148) + сверка сценариев ~100 ₽. Подробно — docs/ПЛАН-ДОВОДКИ-2026-10.md, «Этап 4 на живых деньгах».
check
↑ ЭТАП 5. Обновить работы с прода и переключиться
Самый конец, после всех проверок. Владелец: «потом уже обновляет работы с прода и переносим на прод». Смысл: догнать базу стенда свежими работами клиентов, чтобы в день переключения человек не заметил разрыва — его работы, планы и публикации на месте. Связанные задачи: «Развести адреса на nginx», «Переподключить сети брендам», «День переключения», «Решить, что делать с потерянными файлами работ».
✓ 11.10.2026 11:47 UTC: 24contentzavod.ru переключён на ядро (вариант А, весь домен). Старый сервис заморожен (4 крона), перенос догнан (сверка 28/28), контур «бой», Метрика 106102698, автоматика включена. Проверено снаружи: главная/вход/тарифы/блог/правовые 200, старые адреса 301, файлы старых работ 200, REST-деньги 403. Откат и подробности — docs/ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md, раздел «ПЕРЕКЛЮЧЕНО 11.10.2026».
task
Разделить отказы без проверки: нет теста или нет номера
230 отказов из 248 числятся без проверки. Это две разные вещи, и пока они смешаны, цифра ничего не говорит: · у отказа ЕСТЬ проверка, но она не называет его номер — лечится проходом вроде сегодняшнего (03.09, с 3 до 18); · у отказа НЕТ проверки вовсе — и таких, судя по всему, большинство: каталог собирался механически по сетке «сущность × ось», включая части, которые ещё не написаны. ЧТО СДЕЛАТЬ: пройти каталог и у каждого отказа пометить, к чему он относится — к написанному или к ненаписанному. По написанным без проверки завести задачи на тесты. Тогда число «отказов без проверки» станет мерой долга, а не шумом.
✓ Готово 02.10: docs/failures/РАЗМЕТКА-2026-10.md — 248 отказов: 116 ЕСТЬ_ПРОВЕРКА (номер у 15), 41 НЕТ_ТЕСТА, 90 НЕ_НАПИСАНО, 1 НЕПРИМЕНИМО. Список задач на тесты в конце файла.
task
Полигон: находки по интерфейсу, первый заход
Прогон Web QA Полигона по стенду (проект «Контент Завод — ядро (стенд)», режим basic, бесплатный). Два прогона: гостем — витрина, под учёткой @cz-core.local — кабинет. 129 находок на 200 проверок. ПОЧИНЕНО СРАЗУ: петля в разметке действий (см. этап 2, находка 3). ОСТАЛОСЬ РАЗОБРАТЬ: · major, наезд блоков: на iPhone SE и iPhone 15 на главной два div.stolb перекрываются на 6516 px. Это ровно то, о чём владелец говорил — «здесь наехало». · minor, обрезанный текст на /login: «производство контентаЛента выходит кажды» — и обрезано, и слиплось без пробела. · cosmetic, мелкие кнопки под палец (меньше 32 px) на мобильных: 10 штук на страницах витрины, 4–9 в кабинете. Логотип и кнопки шапки. · cosmetic, задвоенные картинки на главной (один файл в двух местах). · cosmetic, «marysa_obzor» на экране трендов — ник канала-источника. Не уверен, что это ошибка: он говорит, откуда тренд. Решать на этапе 3. · ЛОЖНОЕ: «v1.0.0» на /updates помечено как технический текст. Это список обновлений, версии там уместны. ПОБОЧНО ПОЧИНЕН САМ ПОЛИГОН: обрезанная до 4000 символов подробность переставала быть JSON, и разбор ронял ВЕСЬ прогон на последнем шаге, когда всё уже собрано. Из-за этого первые два прогона не сохранили ничего.
✓ Разобрано 02.10: наезд колонок стены и пузыри главной на телефоне — починено; логотип и подвал под палец; обрезанный текст /login и задвоенные картинки — ложные находки. Свой обход (Полигон погашен после взлома 23.09): 59 страниц × 375/1440 × 2 темы — починены шапка кабинета (профиль за краем), строка «Что произвёл», пульт студии.
check
🔴 День переключения: задать METRIKA_ID, иначе оборвётся вся статистика
СВЕРЕНО 03.09. Счётчик Метрики в ядре ЕСТЬ (app/_части/метрика.tsx), стоит на витрине и на входе, в кабинет намеренно не ставится — вебвизор писал бы экран с чужими материалами и перепиской с поддержкой. НО номер берётся из переменной METRIKA_ID, а её на стенде нет НАРОЧНО (чтобы тестовые заходы не мешались с боевой статистикой). Если в день переключения её не задать в боевом окружении, счётчик просто не появится. ЧТО БУДЕТ, ЕСЛИ ЗАБЫТЬ: визиты, источники, поисковые запросы и цели начнутся с нуля — сравнить «до» и «после» переезда будет нечем. И отдельно: у рабочего сервиса НЕТ ни мета-тега подтверждения, ни файла подтверждения (проверил: yandex-verification в head отсутствует, wmail-verification.html отдаёт 404). Значит домен в Вебмастере подтверждён либо через DNS, либо ЧЕРЕЗ САМ СЧЁТЧИК Метрики. Во втором случае пропажа счётчика снимает и права в Вебмастере. ЗНАЧЕНИЕ: METRIKA_ID=106102698 (тот же, что на рабочем сервисе — тогда история продолжится, а не начнётся заново). ПРОВЕРКА ПОСЛЕ: curl -s https://24contentzavod.ru/ | grep mc.yandex.ru — должно находиться.
✓ METRIKA_ID=106102698 в .env ядра 11.10.2026, счётчик на главной виден (mc.yandex в разметке).
task
Решить, нужен ли Google Analytics в ядре
На рабочем сервисе стоят ДВА счётчика: Яндекс.Метрика (106102698) и Google Analytics (G-MYY6EM9NGB). В ядре есть только Метрика. То есть после переключения данные GA оборвутся совсем. Вопрос владельцу: нужен ли он. Аргументы против — в России доступ к панели GA нестабилен, а Метрика даёт всё то же и вебвизор; аргументы за — если по GA собрана история или он нужен для чьей-то отчётности. Работы на полчаса: тот же приём, что у Метрики — компонент в оболочке витрины, номер из переменной, в кабинет не ставим.
decision
↑ Адрес /docs после переезда отдаёт 404
СВЕРКА 03.09: прогнал все девять адресов из карты сайта рабочего сервиса по стенду ядра. 200 — /, /blog, /contacts, /offer, /privacy, /terms 301 — /pricing → /tariffs, /auth/login → /login (переадресации уже прописаны в next.config.mjs, это хорошо) 404 — /docs На рабочем сервисе /docs — это страница «Инструкции», и она в карте сайта, то есть проиндексирована. В ядре по этому адресу лежит только /docs/api (документация публичного API), а самой страницы инструкций нет. ЧЕМ ПЛОХО: 404 на адресе, который уже в выдаче, поиск выкидывает не сразу, но выкидывает насовсем. Это дороже пропавшего счётчика. РАЗВИЛКА ВЛАДЕЛЬЦУ: (а) сделать в ядре страницу инструкций — тогда адрес живёт и содержание при нём; (б) переадресовать /docs на блог или на раздел помощи — быстро, но человек, пришедший за инструкцией, попадёт не туда; (в) оставить 404, если инструкции устарели и не нужны. Само собой это не решится: страницы инструкций в ядре нет вовсе.
✓ Дефолт Р-4: /docs → /blog (301), /api-docs → /docs/api; старые адреса кабинета переадресуются. tools/pereezd-adresa.check.ts в гейте зелёный.
fix
↑ ОТК: закоммитить работу и завести удалённый репозиторий
НАЙДЕНО ОТК 03.09: в рабочем дереве 107 файлов, +13689/-2839 строк, последний коммит 19.08 (ca52f44). git remote пуст. Спринты С12-С15 (вид сервиса, режиссёр, контент-план, настройки заводов) существуют только как файлы на диске. Любой сбой или ошибочная команда стирает две недели, и плана отката на уровне кода нет. Сделать: git add/commit по смыслу + удалённый репозиторий (приватный). Проверка: git log --oneline -5 и git status --short пустой.
✓ Закрыт 03.10.2026: приватный github.com/Logonchek/cz-core, залит чистый снимок без истории (в истории есть .env.bak с паролем). Обновление снимком через ветку github-snapshot, историю master не пушить.
⛔ Закоммичено: 419ef02, 418 файлов, +42 918 строк, рабочее дерево чистое, секретов в индексе нет (проверено grep по diff). tsconfig.tsbuildinfo убран из индекса и закрыт в .gitignore. Удалённого репозитория по-прежнему нет: ssh-ключ cfru авторизован в GitHub как Logonchek, но создать репозиторий без gh или токена нельзя, а их на сервере нет. Нужно от владельца: создать ПРИВАТНЫЙ пустой репозиторий (без README) на github.com/Logonchek, например cz-core, и сказать имя — push сделаю сразу.
bug
↑ ОТК: сменить секрет вебхука Kie, убрать .env.bak из истории
НАЙДЕНО ОТК 03.09: файл .env.bak-20260809-110204 закоммичен в git и содержит CZ_HOOK_SECRET_KIE, который по md5 СОВПАДАЕТ с действующим в .env (DATABASE_URL там уже устарел). Наружу пока не ушло: remote нет. Но первый же push унесёт действующий секрет. Сделать: сменить CZ_HOOK_SECRET_KIE (и у Kie тоже), git rm --cached файл, добавить .env.bak* в .gitignore. Делать ДО заведения удалённого репозитория. Проверка: git ls-files | grep env.bak — пусто.
✓ Секрет CZ_HOOK_SECRET_KIE сменён (48 знаков, openssl rand). Kie вызывается без адреса возврата — результаты забираются опросом recordInfo, так что у провайдера секрет нигде не зарегистрирован, смена ничего не сломала: health 200, вебхук без подписи отдаёт 401 (секрет есть и проверяется), не 503. Оба бэкапа окружения (.env.bak-20260809 из истории и .env.bak-2026-08-25, который ещё не успел уехать) вынесены в /root/env-backups с правами 600. .gitignore расширен до .env* с исключением .env.example. Историю не переписывал: после смены старый секрет в коммите c8b792d мёртв, а пароль базы там устарел ещё раньше.
bug
↑ ОТК: поймать страницу, где функция уходит в клиентский компонент
НАЙДЕНО ОТК 03.09: в cz-core-error.log ежедневно падает "Functions cannot be passed directly to Client Components": 25 (01.09), 14 (02.09), 22 (03.09 до 05:50). Ошибки идут парами: массив, где [0] — функция filter, [1] — функция map. Стек: Array.toJSON → stringify → Timeout._onTimeout, то есть при перерисовке по живому событию (роутер.refresh из _части/живое.tsx и студия-холст.tsx). Маршрут в логе не пишется. Экраны-кандидаты: b/[brand]/dashboard, works, works/[work], studio/[kind], rols — все с <Живое>. Проверка: открыть их под демо-доступом (npx tsx tools/demo.ts) при идущей сборке и поймать в логе.
✓ Пойман 02.10: «Сети» бренда и завода. Next запоминал в серверном действии ключи.map / аккаунты.filter (функции). «Сохранить» ломал страницу 500 — проверено руками. Починено (список в функцию модуля), проверка сборки tools/akcii-zamykaniya.check.ts в гейте: 67 действий, функций нет. После выката обход не дал ни одной такой ошибки.
bug
↑ Тревога об обращении в поддержку приходит без ссылки, владелец ищет его на старом сайте
21.09 09:33 Лия принесла «Обращение в поддержку (телеграм), от Сергей Грищенко, тема: Скриншот без текста». На 24contentzavod.ru/admin/support пусто: обращение лежало в очереди ядра newtest.24contentzavod.ru/admin/support (нитка e8b323d9). Файл — селфи мужчины в машине, не клиент: написал боту публикаций @Contetn_zavod_MyVision_pub_bot, который в ядре принимает поддержку (SUPPORT_TELEGRAM_TOKEN). Развилка про бота — OPEN-QUESTIONS В-24.
✓ сказатьНам получил параметр «ссылка», хук /api/hooks/support/[channel] и форма кабинета передают <адрес запроса>/admin/support?thread=<id> (адрес из свойАдрес(), не из CZ_SITE_URL, который до дня переключения смотрит на старый прод). Выкат tools/deploy.sh 21.09: гейт зелёный, 86 страниц, с бедой 0. Проверка живым хуком: нитка 2b92e036 создана, тревога ушла без предупреждений в журнале, нитка закрыта.
bug
↑ План доводки и переключения 02.10 (docs/ПЛАН-ДОВОДКИ-2026-10.md): ждёт «ок» и ответов Р-1…Р-10
Пять этапов владельца: 1 хвосты кода (7 пунктов), 3 интерфейс (129 находок Полигона), 4 платная приёмка (~1 860 ₽ потолок, по «трачу?»), 5 переключение по книге дня. Решения Р-1…Р-10 с рекомендацией и дефолтом — в документе. Срок до переключения 6–7 рабочих дней при ответах на Р-1…Р-4 в начале.
✓ «Ок» владельца получен 02.10. Ход этапа 1 и этап 1б — в docs/ПЛАН-ДОВОДКИ-2026-10.md.
decision
🔴 Автозапуск планов подключён (был написан, но никем не вызывался)
✓ Коммит 7c246a6: крон tools/plan-run.ts раз в 15 мин, только в бою (CZ_CONTOUR=бой) и при «план:запуск», замок в базе, автор = утвердивший план. plan-run.check 8/8, plans.check 20/20. На стенде признак выключен явно.
fix
🔴 Режим «авто»: готовое из плана само согласуется и встаёт в расписание
✓ Коммит 3c53bea: src/autopublish.ts — часы завода в зоне бренда, не ночью, 30 мин между постами в аккаунт, фильтр сети пункта; пропущенный слот по В-12 (пропуск + тревога). autopublish.check 17/17 (А2–А6), schedule.check 26/26.
feature
🔴 FAIL-209: ключ API уволенного продолжал работать
✓ Коммит 16fde81: отзыв доступа отзывает ключи; ключ живёт, пока выписавший в организации. klyuchi-uchastnika.check 4/4.
bug
🔴 FAIL-198: в просмотре глазами клиента можно было публиковать и утверждать план
✓ Коммит cf0c060: в просмотре только показ, держит требуетсяПраво. prosmotr-prava.check 30/30, vitest 219/219.
bug
↑ FAIL-157/160–162: оборванная отправка висела и задваивалась
✓ Коммит cf0c060: зависшее и неясное (таймаут) → «ошибка» с просьбой проверить ленту и тревогой, без повтора; ясный отказ соединения повторяется. publish-obryv.check 8/8, publish.check 23/23.
bug
Себестоимость по вебхуку писалась ценой клиента
✓ Коммит cf0c060: пишется расход у провайдера. webhooks.check 18/18 (проверка требовала цену клиента — приведена).
bug
↑ Подключить приёмку качества (acceptance.ts): латиница на картинке, речь против задания
FAIL-078/079/099, ACCEPTANCE С6. Модуль написан и проверен, линии его не вызывают. Подключение стоит денег в работе (Scribe, зрячий критик) — со сметой и «трачу?».
feature
FAIL-125: возврат платежа банком не разбирается
Обработчик кассы понимает только succeeded. core.refund_payment снимает всю сумму — частичный возврат требует миграции. Старый сервис тоже не разбирает: после переключения.
bug
↑ Тесты на механизмы без проверки: первые 10 (деньги и гонки)
Из docs/failures/РАЗМЕТКА-2026-10.md: FAIL-111, 021, 120, 027, 132, 007, 010, 046, 040, 013. 02.10: 5 из 10 закреплены — tools/dengi-gonki.check.ts в гейте (FAIL-111, 120, 027, 021, 132), коммит 7c54cb0. Все пять механизмов держались. Осталось: FAIL-007, 010, 046, 040, 013.
✓ 10.10.2026: первая десятка закрыта целиком. FAIL-111, 120, 027, 021, 132 — tools/dengi-gonki.check.ts; FAIL-040, 013, 046 — tools/dengi-posle-konca.check.ts (02.10); FAIL-007 (обрыв между заказом и очередью) и FAIL-010 (обрыв базы при расчёте, pg_terminate_backend) — новый tools/dengi-obryvy.check.ts, 18 проверок, в гейте. Проверка мутацией: убранная защита шагГотов роняет проверку. Заодно проверки выката больше не сбрасывают предохранители Kie/ElevenLabs и не стирают чужие события вебхуков (после переезда гейт идёт по боевой базе). Ветка dovodka-1010.
task
готовS13 · С8а · Заводы4/4

Цель: Конвейеры производства возвращаются как первоклассная сущность. Итог: то, чем сегодня пользуются через /factory/*, работает на новом ядре.

Завод как сущность
тематика, форматы, расписание, режим, план; у бренда несколько заводов
✓ src/factories.ts: завод с тематикой, форматами, расписанием (дни недели, сколько в день, чем заполнять) и тремя режимами. Создаётся ВСЕГДА в ручном, что бы ни просили. Перевод в «сам» требует причины и попадает в аудит, без расписания не разрешается, остановленный завод не переводится. Снятие остановки сбрасывает счётчик провалов, иначе следующая же неудача снова остановит. Расписание проверяется: девятого дня недели не бывает, пятьдесят выпусков в день — авария со счётом.
feature
Планировщик заводов поверх заказов
в ручном режиме не производит ничего, в авто — строго по плану
✓ собратьПлан раскладывает выпуски по дням расписания и считает смету, ничего не производя: план создаётся неутверждённым, между «предложили» и «списали» всегда человек. Темы не выдумываются — это отдельная работа помощника и отдельные деньги. НАЙДЕНО И ПОЧИНЕНО: планировщик не смотрел на режим завода. Завод «только по кнопке» с утверждённым планом запускал работы сам — восемь работ там, где должно быть ноль. Теперь план едет только в режиме «сам».
feature
Интерфейс завода
настройка, план, лента произведённого
✓ Раздел «Заводы» в кабинете: список с режимом и остановкой, карточка завода с настройкой (название, форматы, дни, сколько в день, чем заполнять), сменой режима с причиной, сборкой и утверждением плана со сметой, лентой произведённого. НАЙДЕНО: кириллица в шаблонных именах полей (`формат-${ф}`) — форма не доносила ни одного отмеченного формата. Проверка «на проводе только латиница» такие имена пропускала: видела подстановку и шла дальше. Теперь смотрит на литеральную часть шаблона, у форматов появились латинские ключи для провода (КЛЮЧ_ФОРМАТА). Проверено 131 имя.
feature
Сборка завода помощником
основа для «Лия собирает кастомные заводы»
✓ src/factory-assistant.ts: помощник читает бриф бренда и предлагает завод (название, зачем, форматы, дни, сколько в день) со сметой в месяц. Один вызов модели, не пять «уточняющих». Предложение НИЧЕГО не создаёт: ждёт согласия в черновике, завод появляется по кнопке и всегда в ручном режиме. Промах модели (заполнять форматом, которого нет у завода) чинится молча, мусор в ответе — отказ, а не завод из пустоты. tools/factory-assistant.check.ts — 17 проверок на подставном отправителе, без трат. Живой прогон ждёт согласия владельца: он стоит денег (около рубля).
feature
В РАБОТЕS14 · С8б · Студия5/6

Цель: Разовые обращения к моделям поверх общего кошелька и очереди; семь мест списания схлопываются в одно

Единый вход вместо семи мест списания
студия работает через заказы и кошелёк ядра
✓ Разовая генерация — такой же заказ, как всё остальное: тот же кошелёк, та же очередь, та же приёмка, тот же номер для поддержки. Формат работы «студия», линия из двух шагов (сделать и проверить). Цена берётся из каталога вариантов по реестру провайдеров, а не задаётся отдельным числом — иначе цена в студии и цена вызова разъедутся молча. Каталог закрытый: «любая модель по выбору» это способ заплатить за результат, который некуда деть.
feature
Общая библиотека результатов
сделанное в Студии можно взять в пост или ролик
✓ Библиотека показывает всё готовое бренда в одном месте: и разовое из студии, и то, что вышло из работ. Разделять незачем — человек ищет «ту картинку с диваном», а не «результат шага отрисовки заказа №41». Пока файлов нет, показывается пусто, а не список заказов.
feature
История, повтор, пресеты
повторить с теми же настройками
✓ История студии с кнопкой «Повторить»: прошлый запрос подставляется в поле, ничего не отправляется само. Настройки (пропорции, разрешение, длительность) стоят прямо у промта и уезжают провайдеру как есть. ПЕРЕДЕЛАНО по замечанию владельца: студия — прямой прокид в Kie, а не мини-линия. Убран шаг приёмки, который я добавил из-за общего юнит-теста: судить «то ли получилось» здесь некому, человек просил ровно это. Тест теперь знает про исключение и объясняет почему. Помощь с описанием — единственное место, где участвует модель кроме генерации. Только по кнопке, с ценой на ней (1 ₽), переписывает текст человека подробнее и ничего не выдумывает сверх сказанного. Пресеты не делал.
feature
Долгие генерации переживают уход со страницы
через общую очередь
✓ Разовая генерация идёт через очередь как обычный заказ, поэтому уход со страницы её не трогает: воркер доведёт, результат появится в истории и библиотеке. На экране это сказано прямо — «можно закрыть страницу». Дополнительного кода не потребовалось: свойство досталось от того, что студия не заводит своего пути в обход ядра.
feature
Улучшения по ответам на В-18
ждёт решения владельца
feature
↑ Выбор движка съёмки: завод, СММ-Центр, Студия
Решение владельца: показывать людям 5–8 движков и давать выбрать самим, на чём работает завод и чем снимать конкретный ролик. Veo 3.1 основой не ставим — владелец сравнивал, wan 2.7 лучше.
✓ Выбор стоит в трёх местах: настройки завода (чем снимает поток), композер СММ-Центра (чем снять этот ролик), Студия. В настройках завода — одна пилюля со списком, по согласованному варианту. Слияние настроек проверено: правка движка не затирает перенесённые «секунд» и «уровень».
feature
готовS15 · С8в · Вирус и Лия5/5

Цель: Оба перестают быть островами: работают через ядро, Лия получает СММ-Центр

Перенос Вируса
встраивание по решению В-19: источник идей для плана плюс кнопка в создании
✓ УЖЕ БЫЛО в ядре, задача устарела. Проверка на вирусность живёт в src/virality.ts (224 строки) с проверками в virality.test.ts и подключена инструментом «Проверка на вирусность» в студии: разбор готового ролика. Цена считается по длительности: базовая за первые 30 секунд, дальше посекундно, потолок 180 секунд.
feature
Лия через ядро
сегодня дёргает генерацию напрямую мимо всех проверок
✓ СДЕЛАНО. Лия — помощник, который читает состояние дел и предлагает следующий шаг. В ядре её не было вовсе. Сделано не копией прода (там своя схема на Supabase и 1700 строк), а на нашей механике: Лия получает обычный ключ публичного API с правом «предлагать» и ходит по тем же ручкам. Второго служебного API заводить не стал — он разошёлся бы с основным за месяц. Ручки: POST /api/v1/proposals (предложить), GET /api/v1/proposals (свои предложения и что с ними решили). Одобрять через API НЕЛЬЗЯ — согласие даёт человек в кабинете, глазами увидев цену. Экран «Предложения» в кабинете: видно, что предложено, во сколько обойдётся и с чем именно соглашаешься. Цена стоит на кнопке, а не в мелком тексте. ПРОВЕРЕНО СКВОЗНЯКОМ: ключ ассистента попробовал заказать работу напрямую — 403. Тогда предложил, цену посчитали мы сами (20 руб), предложение появилось в кабинете, человек согласился, работа пошла в очередь.
feature
Лия в СММ-Центре
помогает и управляет, а не только заводами
✓ СДЕЛАНО: src/lia.ts, кнопка «Спросить Лию» на обзоре бренда — экране, куда человек попадает первым и где у него возникает вопрос «что мне теперь делать». Раньше она умела помогать только с заводом; сервис знал, что у клиента пункты плана без темы и работы не опубликованы, но молчал. COMPILED-AI: поводы находит КОД запросами к базе (пункты плана без темы, готовое без публикации, незаполненные описание товара и аудитория). Модель зовётся ровно за одним — придумать тему выпуска, с оглядкой на то, что уже было в плане, чтобы не повторяться. ГЛАВНОЕ РЕШЕНИЕ — ПРЕДЛАГАТЬ ТОЛЬКО ИСПОЛНИМОЕ. Согласие на предложение вида «публикация» и «описание компании» сегодня упирается в «применяется руками»: механика для них ещё не написана. Предложить такое значит дать человеку кнопку, которая на нажатие отвечает «не вышло». Поэтому эти находки уходят наблюдениями со ссылкой на нужный экран, а предложениями становятся только темы — их согласие исполняет. ЗОВЁТСЯ ПО КНОПКЕ, не по расписанию: ассистент, заводящий предложения каждый час, через неделю превращается в ленту, которую перестают читать. Плюс предел частоты на бренд, потому что просьба к модели стоит денег. ПРОВЕРЕНО живьём на стенде: нашла 36 собранных работ без публикации. Четыре проверки с заглушкой вместо модели (src/lia.test.ts) стерегут честность кнопки: что неисполнимое не попадает в предложения и что форма предложения совпадает с тем, что читает применение согласия (день, формат, тема) — иначе ошибку видно только в момент, когда человек уже нажал. Агент прописан в бэкофисе как cz-lia под главой производства.
feature
Границы полномочий Лии
готовит и предлагает, человек подтверждает траты и публикации; ждёт В-20
✓ СДЕЛАНО — и это главное во всей задаче. Граница проведена в схеме, а не в уговоре: у предложения есть состояние, и «применено» наступает только после согласия человека. ТРИ ПРАВИЛА: 1. Предложение ничего не тратит — оно только записывает намерение. 2. Цена считается НАШИМ прайсом, а не берётся из предложения: ассистент мог ошибиться или устареть, а человек читает именно эту цифру. 3. Применяет человек СВОИМИ правами, и права разные по видам: на работу нужно «тратить», на идею «управлять_планом», на публикацию «публиковать». Наблюдатель не может одобрить трату, даже если ассистенту выдали ключ пошире. Право «предлагать» заведено отдельным: это единственное, что безопасно доверить машине. Ключ с ним не тратит и не публикует. СЕМЬ ПРОВЕРОК ровно на границу: работа не появляется до согласия, цена не берётся у ассистента, без права тратить согласиться нельзя и предложение при этом остаётся ждать, отклонённое больше не применить, на идею нужно другое право.
feature
Согласие на публикацию и правку описания — исполнять, а не отсылать руками
Сейчас применить(): для видов «публикация» и «описание_компании» бросает «применяется руками, откройте экран и перенесите». Из-за этого Лия не может предлагать эти два вида — предложение с неисполнимым согласием это кнопка, которая отвечает «не вышло». ЧТО СДЕЛАТЬ: · публикация — поставить готовую работу в расписание публикации по согласию (механика в src/approval.ts уже есть); · описание_компании — применять точечно и только по известным полям. Принять произвольный объект от ассистента значит однажды затереть настройки целиком; это и было причиной, по которой ветку оставили незакрытой. ПОСЛЕ ЭТОГО: снять ограничение в src/lia.ts — там оба повода уже находятся кодом и ждут только исполнимого согласия.
✓ Сделано 02.10 (69e3672): публикация ставит согласованную работу в аккаунты завода не раньше +15 мин, повтор упирается в индекс; описание — только 4 известных поля, профиль читается строго. Лия предлагает публикацию согласованного; описание не предлагает (фактов нет). proposals-apply.test 8/8, lia.test 6/6.
feature
готовS16 · С8г · Публичный API5/5

Цель: Полноценный API для заводов, СММ-Центра и Студии: ключи, квоты, версия, единый формат ошибок

Ключи и квоты
выдача, отзыв, лимиты по тарифу
✓ СДЕЛАНО. Право «управлять_ключами» было в ролях с самого начала, а управлять было нечем: ни ключей, ни ручек. Ключ принадлежит организации, не человеку: сотрудник уходит, интеграция остаётся. В базе только отпечаток — показать секрет второй раз нельзя даже нам, поэтому утечка дампа не превращается в доступ. Первые знаки храним отдельно, чтобы человек с тремя ключами понимал, какой из них где стоит. Права нельзя выдать шире своих: иначе наблюдатель выписал бы себе ключ, которым тратит деньги. Свой предел запросов у каждого ключа, по умолчанию 60 в минуту. Экран в кабинете: раздел «Ключи API» — выписать, посмотреть, отозвать. Отозванный ключ остаётся в списке: по нему разбирают, что им делали. ОДИННАДЦАТЬ ПРОВЕРОК на то, что должно быть верно всегда: секрета нет в базе и в списке, права шире своих не выдаются, отозванный ключ перестаёт работать сразу, предел частоты срабатывает на своём числе.
feature
Покрытие заводов, СММ-Центра, Студии
то же ядро, те же правила и потолки
✓ СДЕЛАНО частично и осознанно. Ручки v1: /me (проверить ключ и увидеть остаток), /brands (номера брендов), POST /works (заказать пост, карусель, ролик, историю), GET /works/{id} (состояние и ссылки на готовое), GET /works (список). Заказ идёт ТЕМ ЖЕ путём, что и из кабинета: та же постановка, те же проверки денег и прав, тот же ключ отправки от двойного заказа. Отдельной «упрощённой» дороги для машин нет намеренно — она разошлась бы с основной за месяц. Студия через API не заказывается: это инструмент кабинета со своим потоком, и притворяться, что он такой же, значило бы обещать несуществующее. ПРОВЕРЕНО ЖИВЬЁМ: выписал ключ, заказал пост, работа собралась за 220 секунд, списалось 20 руб, файл вернулся подписанной ссылкой. Повтор с тем же ключОтправки вернул ту же работу с пометкой повтор:true — двойной оплаты нет. Отказы отвечают верно: без ключа 401, с выдуманным 401, без нужного права 403 с объяснением, что делать.
feature
Идемпотентность и единый формат ошибок
заголовок ключа повтора, коды ошибок
✓ Идемпотентность готова; единый формат ошибок — вместе с публичным API в С8г
feature
Вебхуки наружу
события о готовности и публикации
✓ СДЕЛАНО. Клиенту незачем опрашивать нас каждые пять секунд: когда работа готова, мы сами приходим на его адрес. Очередь в базе, а не в памяти: перезапуск не должен терять события про чужие деньги. Повторы с растущим отступом — минута, пять, полчаса, два часа, двенадцать: чужой сервер лежит чаще нашего, одна попытка теряет событие, а повторы без отступа это стук в дверь раз в секунду. Тело подписано HMAC-SHA256 секретом ключа, иначе на адрес клиента напишет кто угодно. НАЙДЕНА ПОЛОМКА НА ЖИВОЙ ПРОВЕРКЕ: имя события уезжало в заголовок X-CZ-Event по-русски, а заголовки HTTP держат только латиницу — доставка падала целиком с «Cannot convert argument to a ByteString». Статически это не видно: имя приходит из базы. События получили латинские коды (work.ready, work.failed), в отправке стоит защита на случай старых строк в очереди. ПРОВЕРЕНО СКВОЗНЯКОМ: поднял приёмник, заказал работу через API, дождался готовности — сообщение пришло с событием, работой, заказом и списанной суммой. Подпись пересчитана получателем и СОВПАЛА.
feature
Документация из кода
не отдельный текст, который устареет
✓ СДЕЛАНО. Описание ручек лежит одним объектом в src/api-public.ts, и из него собираются обе справки: машинная по адресу /api/v1 и человеческая на /docs/api. Источник общий, поэтому разойтись им негде — отдельно написанная документация устаревает на первой же правке. Страница открыта без входа: чтобы понять, что умеет сервис, не должно быть нужно сначала завести учётную запись. На ней правила, которые обычно узнают об ошибку: ключ только в заголовке, свой номер у каждого заказа, предел запросов, ссылки на файлы временные.
feature
готовS17 · Уроки с навесов6/7

Цель: Перенести в Контент Завод то, что проверено живыми генерациями на проекте навесов.

🔴 Ударения в озвучке по словарю
Движок сам угадывает русское ударение и на профессиональных словах ошибается: сказал «арО́чный» вместо «А́рочный». Лечится знаком U+0301 сразу после ударной гласной, ставится словарём перед синтезом. Признак, что движок принял: дорожка заметно длиннее. Реализация: lib/stress.ts проекта навесов.
✓ Применено: src/lines/video/pronunciation.ts — слой произношения перед озвучкой, словарь на бренде, длинные термины заменяются раньше коротких. Есть проверка, что знак ударения не попадает на экран (tts-timings.test.ts). Кэша намеренно нет: правка ударения слышна сразу.
backend
🔴 Озвучка только прямым ElevenLabs через прокси
Через Kie их TTS падает с internal error на любых параметрах: проверено четырьмя вариантами (с языком и без, свой голос и стоковый). Прямой вызов api.elevenlabs.io отдаёт mp3 сразу, но с российского адреса приходит 302, поэтому только через прокси.
✓ Применено: src/lines/video/tts.ts ходит на api.elevenlabs.io напрямую и через прокси (postJsonДвоично из src/proxy-post.ts) — с российского адреса приходит заглушка Cloudflare. Через Kie озвучка не идёт вовсе.
backend
🔴 Поле образца у gpt-image-2 это input_urls
С image_urls модель молча игнорирует образец и рисует другого человека: в кадр вместо аватара попала посторонняя женщина. Слаги: gpt-image-2-text-to-image и gpt-image-2-image-to-image.
✓ Применено: src/providers.ts, у gpt-image-2 в описании вызова стоит полеКартинок: input_urls (до 14 образцов). Поле берётся из реестра моделей, а не угадывается по имени, и у каждой модели своё: у nano-banana image_urls, у wan 3.0 reference_image_urls.
backend
🔴 Формула вживления человека в настоящее фото
Промпт по-русски, фото объекта ПЕРВЫМ, лицо вторым, прямой запрет «первое фото не меняй, только добавь человека» и требование естественности по свету и цвету. Английский промпт с теми же требованиями даёт похожий, но чужой объект.
✓ Заведён инструмент студии «Человек с товаром» (раздел «Картинка»). Формула лежит в предисловии инструмента, а не в подсказке: подсказку человек перепишет, предисловие уедет модели всегда. Промт по-русски (по-английски тот же смысл давал похожий, но чужой предмет), товар первой картинкой, лицо второй, прямой запрет менять первую и требование подогнать свет, тени, цвет и зернистость. Порядок образцов в пульте виден номерами на плитках. Платную проверку не гонял: по порядку владельца тесты картинок — этап 4.
backend
↑ fail у Kie не значит, что денег не взяли
Задача отчиталась fail с пустым результатом, а в кабинете видео есть и кредиты списаны. Сверять по балансу и кабинету, а не по статусу. Повтор отправки разрешать только когда баланс не сдвинулся.
✓ Сделано во всех пяти платных вызовах Kie (картинка, ролик, проба Veo, аватар, удаление фона). Перед постановкой задачи снимаем остаток кредитов (GET /api/v1/chat/credit, проверено — отдаёт число), после отказа читаем ещё раз. Остаток просел — пишем в расход настоящую себестоимость вместо нуля и будим владельца: результат мог остаться в кабинете Kie. Ноль в расходе был опасен не сам по себе: по нему потолок доведения (RT-09) не видел потраченного и разрешал повтор за те же деньги. Точность снимка ограничена: сборщик держит три места, соседняя задача тоже двигает остаток. Поэтому вывод «списали» — повод разбудить человека, а не основание для тихого автоматического действия.
backend
↑ Стоп-темы: проверка после генерации
Запрет в промпте дырявый, нужна проверка готового текста. Осторожно: \w в JS не покрывает кириллицу, окончания писать через \p{L}.
✓ Проверка готового текста в ядре уже была и стоит во всех линиях (нарушенияЗапретов в видео, карусели, ролсе, аватаре, посте). Закрыта настоящая дыра в ней: искала подстроку, из-за чего стоп-слово «дом» срабатывало внутри «бездомный», «акция» внутри «реакция». Каждое такое срабатывание заворачивало готовый текст и оплачивало новый прогон впустую. Теперь ищем основу в НАЧАЛЕ слова, границы через \p{L} с флагом u — ровно та ловушка, о которой предупреждал урок: \w и  в JS знают только латиницу. Шесть проверок в src/stop-words.test.ts стерегут оба края: и что запретное ловится во всех формах, и что лишнее не ловится.
backend
Veo Omni и Kling Omni: обсудить с владельцем
Владелец 01.09: «отдельно надо реализовать возможно вео омни и клинг омни, там где можно очень детально управлять роликом (но это надо с тобой обсудить делать или нет)». В работу НЕ брать до разговора. Что уже известно из документации Kie: семейство kling/v3-omni-* — это image-to-video, reference-to-video, text-to-video и transformation, плюс kling/motion-control-v3. У Клинга 3.0 есть многокадровый режим: несколько сцен, у каждой свой промпт и своя длительность (1–12 секунд на сцену, до 15 всего), и ссылки на элементы через @имя. Это ровно то «детальное управление роликом», про которое речь, и оно ложится на режиссёрский лист из С12.
✓ Дубль: то же самое заведено в С13 как «Veo Omni и Kling Omni: детальное управление роликом», там же собран разбор возможностей. Обе завёл я в одну сессию.
feature
В РАБОТЕS18 · С12 · Режиссёр роликов14/21

Цель: ЧЕРНОВИК НА ПОДУМАТЬ, этапы не утверждены. Ролик собирается не склейкой двух клипов, а режиссёрским листом: эпизоды 2-5 сек с типами (лицо / вставка / сплит / моушен-карточка), слои, титры, пословные субтитры. Упор владельца: моушен-графика кодом (экраны телефона, UI-мокапы, терминал, барабаны выбора, счётчики), видео - лишь часть. Цель по себестоимости 300-500 руб за ролик. Движок Remotion (лицензия бесплатна: 1 сотрудник) + ffmpeg на подхвате. Прописка не решена: внутрь ядра, если успеем, иначе отдельный сервис и подключение по API.

↑ Развилка: прописка Режиссёра
Решение владельца 12.08: если ядро cz-core успеваем доделать - Режиссёр встраивается внутрь; если нет - делаем отдельный сервис и подключаем по API. Решать в момент готовности ядра, формат листа и рендерер от этого не зависят.
✓ РАЗВИЛКА ЗАКРЫТА 02.09 по правилу самого владельца от 12.08: «если ядро cz-core успеваем доделать — Режиссёр встраивается внутрь; если нет — отдельный сервис по API». Владелец 02.09 решил, что С12 идёт ДО переключения, то есть ядро успеваем. Значит — внутрь. РАЗДЕЛЕНИЕ ПО ЧАСТЯМ (дополнение владельца про бэкофис учтено): · РЕЖИССЁР-ИИ (сценарий → лист) — обычный агент ядра. Живёт в cz-core рядом с остальными агентами линии и докладывает о работе в бэкофис — механика уже сделана 02.09 (src/backoffice.ts, 16 сотрудников прописаны в реестре). Отдельного сервиса ему не нужно: он думает, а не считает, и стоит внутри производства. · РЕНДЕРЕР (лист → mp4) — ОТДЕЛЬНАЯ служба на pm2, по образцу rols-render (/opt/rols-render, 127.0.0.1:3100). Это не агент и не решение, а тяжёлый счёт: рендер тридцати секунд 1080x1920 занимает минуты полной загрузки процессора. Внутри веб-приложения он заблокировал бы кабинет всем клиентам разом. В бэкофисе ему тоже не место — там смотрят за теми, кто думает. ФОРМАТ ЛИСТА от развилки не зависел и уже написан (src/director/list.ts), критик листа кодом тоже (src/director/critic.ts).
idea
↑ Развилка: лицо в кадре
В эталонных разборах (Nick Saraev, drcintas) говорящая голова занимает 10-39% хронометража и держит доверие. У завода лица нет. Варианты: аватар OmniHuman (13,5 руб/сек - это +200-400 руб на ролик, съедает весь бюджет) либо форматы без лица (только моушен + b-roll + субтитры). От ответа зависит набор шаблонов форматов. Решает владелец.
✓ РЕШЕНО ВЛАДЕЛЬЦЕМ 31.08: делать не один шаблон с переключателем, а ТРИ РАЗНЫХ. Аватары отличаются не ценой, а тем, что умеют телом: ПРЕМИУМ — OmniHuman: «ии аватару можно в руки дать товар, он там может ходить, садится на диван». Настоящий актёр, вокруг него можно строить сцену. СРЕДНИЙ — Kling Avatar: «говорящая голова с небольшим движением тела». ДЕШЁВЫЙ — InfiniteTalk: «подозреваю чисто липсинк без оживления тела» — по описанию в каталоге так и есть, до 15 секунд. Сделано: src/director/levels.ts. От уровня зависит не только ведущий, но и вся раскладка: где премиум показывает действие актёра, экономный закрывает время моушен-графикой. Один шаблон с переключателем эту разницу не выразил бы. СЕБЕСТОИМОСТЬ на 30 секунд: премиум 394 руб (актёр 141 + Seedance 2.5 253), средний 198 руб, экономный 55 руб. На 15 секундах: 197 / 99 / 28. ПРОВЕРКА ПОЙМАЛА НАСТОЯЩУЮ ОШИБКУ в раскладке: на экономном уровне при 15 секундах призыв выходил нулевой длины — хук и тело съедали всё время, а призыв получал остаток, которого не оставалось. Ролик кончался ничем. Переписал: хук и призыв берут своё время первыми, середина делит остаток. Четырнадцать проверок, включая прогон раскладки через критика листа.
idea
↑ Формат режиссёрского листа (JSON-контракт)
Основа: timeline -> tracks -> clips (проверенная схема Shotstack), сверху наш слой смысла: роль эпизода (хук / доказательство / оффер / CTA), тип кадра (лицо / вставка / сплит / моушен-карточка), привязка к строке сценария и к пословным субтитрам. Схема валидируется, версионируется, лист неизменяем (правило ядра: артефакт неизменяем, пересборка добавляет версию). Эталоны для обратной проверки - два разбора рилсов от владельца в scratchpad.
✓ СДЕЛАНО. Лист — контракт между тремя частями, которые иначе не договорятся: режиссёр-ИИ пишет лист, добытчик подставляет источники, рендерер собирает mp4. Пока контракта нет, каждый понимает «сцену» по-своему. ОСНОВА как в плане: лента → дорожки → клипы. Сверху наш слой смысла, которого в монтажных схемах нет: роль эпизода (хук, проблема, доказательство, решение, оффер, призыв) и тип кадра (лицо, вставка, сплит, моушен, товар). Без него лист — набор прямоугольников, и проверить, есть ли в ролике хук, невозможно. ЧЕТЫРЕ РЕШЕНИЯ, каждое из грабель: 1. Время целыми миллисекундами: дробные секунды в JSON дают 2.9999999 и разъехавшиеся стыки. 2. Лист неизменяем, у него отпечаток — видно, из какого именно листа собран лежащий рядом mp4. 3. Клип знает свою СТРОКУ сценария номером, а не текстом: текст меняется при правке, привязка должна пережить правку. 4. Источники перечислены по цене: моушен кодом (0) → файл клиента (0) → сток (0) → генерация (15-60 руб). Добытчик обязан идти сверху вниз. ПРОВЕРКА СМЫСЛА, а не только формы: клип за краем ролика, дыра на дорожке видео (рендерер отдал бы там чёрный кадр и не пожаловался), ссылка на несуществующую строку сценария, клип короче 0.2 секунды. КРИТИК ЛИСТА — КОДОМ, как и задумано (compiled-ai): хук в первые 2 секунды, длина эпизодов 0.8–8 секунд, доля говорящей головы не больше 60% («персонаж, который просто стоит и объясняет мораль» — слова владельца), одинаковые блоки подряд, наличие призыва, реплики без своего кадра. Замечания делятся на «стоп» (собирать нельзя) и «слабо» (соберём, но скажем). ПЯТНАДЦАТЬ ПРОВЕРОК на всё перечисленное.
feature
↑ Рендерер director-render: лист -> mp4
Сервис по образцу rols-render (/opt/rols-render, pm2, 127.0.0.1:3100). Remotion как основа + ffmpeg на нарезке исходников и финальном кодеке. Обязательно: очередь и лимит параллелизма - на cfru 8 ядер, 23 ГБ, load уже ~2.3, там же cz-core и rols-render; рендер 30 сек 1080x1920 это минуты полной загрузки CPU. Лицензия Remotion: бесплатна, у компании 1 сотрудник (порог - 4).
✓ СЛУЖБА РАБОТАЕТ. /opt/director-render, pm2 «director-render», 127.0.0.1:3110, запись сохранена. Remotion + встроенный кодек, композиция одна — размер, частота кадров и длительность берутся из самого листа, а не зашиты. ОЧЕРЕДЬ И ПАРАЛЛЕЛИЗМ: рендерим по одному (DIRECTOR_RENDER_PARALLEL), внутри рендера берём половину ядер. На cfru восемь ядер и на них же кабинеты клиентов — два одновременных рендера кладут машину. ОТВЕТ НЕ ЖДЁТ РЕНДЕРА: POST /render кладёт в очередь и отдаёт номер, GET /job/:id — состояние, GET /job/:id/file — готовый ролик. Синхронный ответ на многоминутную работу рвал бы соединение и приводил ко второму рендеру уже оплаченного. СО СТОРОНЫ ЯДРА: src/director/render.ts — проверяет лист, подписывает ссылки на наши файлы (рендерер про хранилище и права не знает и знать не должен), ждёт опросом, отдаёт байты. Проверено сквозняком: 9 секунд 1080x1920 за 15 с, файл настоящий mp4. НАЙДЕНО ГЛАЗАМИ НА ПЕРВОМ ЖЕ РЕНДЕРЕ И ПОЧИНЕНО: блоки рисовали себе непрозрачный фон и блок на дорожке графики закрашивал видео под собой — чек-лист исчезал целиком. Фон принадлежит слою, а не блоку. Плюс субтитры уезжали за край кадра. ГРАБЛИ (в память проекта): кириллица не живёт за границей кода — «:номер» в маршруте Express даёт 404, id композиции «Лист» Remotion не принимает.
feature
↑ Библиотека блоков: пилот 10 анимаций + каталог с оценкой
Моушен-графика кодом по категориям: типографика (караоке-субтитры, маркер, счётчик, печать), UI-мокапы (телефон, браузер, терминал, чат, загрузка файла, барабан выбора), схемы (стрелки, лучи-связи, воронка, до/после, чек-лист), переходы (whip pan, zoom punch, глитч, маска, шторка), акценты (обводка, указатель, шейк, вспышка), раскладки (сплит 50/50, лицо+экран, сетка, зум на деталь). База: Magic UI - 150+ компонентов под MIT (мокапы iPhone/Safari/Android, macOS-терминал, animated-beam, number-ticker, typing-animation), переносятся в Remotion почти как есть. Каждый блок параметризуем (цвет, шрифт, текст, длительность, направление) + превью + теги для режиссёра. Каталог со страницей оценки ок/не ок. СДЕЛАНО 03.09: восемь блоков нарисованы и работают в рендерере (/opt/director-render/src/blocks.tsx) — заставка, караоке, счётчик, схема до/после, чек-лист, мокап телефона, воронка, стрелки. Ровно те ключи, которые умеет выдавать добытчик материала (src/director/resolver.ts): ключи здесь и там обязаны совпадать, иначе блок либо никогда не будет выбран, либо приедет в рендер и нарисуется заглушкой с надписью «нет блока». ОСТАЛОСЬ: оценить глазами на настоящем ролике и решить, каких блоков не хватает. Расширять до сотни без вашей оценки пилота запрещено вашим же решением от 12.08.
✓ 12.08 две партии на живом каталоге https://myvisionagency.ru/lab/rezhisser/. ИТОГО ПРИНЯТО 15 БЛОКОВ. Партия 1 (7 из 12): барабан выбора, счётчик падения, терминал, экран телефона, загрузка файла, субтитры по словам, связь лучом. Партия 2 (8 из 8, без единого отказа): метрика растёт, заявка в переписке, заявки стопкой, календарь публикаций, поиск с подсказками, таймлайн монтажа, карточка товара, гео и охват. ВЫЧЕРКНУТО НАВСЕГДА по решению владельца: маркер с меткой, было и стало, чек-лист, переход рывком, переход ударом - сами приёмы не нужны плюс дёшево выглядели. КРИТЕРИЙ ОТБОРА выведен: берём интерфейс и механику процесса, не берём оформленные слайды с текстом и переходы как самостоятельный эффект. ПЛАНКА КАРТИНКИ: многослойные тени и глубина, стекло, свет под объектами, пружины вместо линейных кривых, детализированные интерфейсы, дыхание кадра. МЕХАНИКА: цикл 4,5-5,5 сек, петля замкнута, мёртвого хвоста нет. Записано в память feedback-motion-blocks-taste. ВАЖНО на следующий шаг: превью сделаны CSS-анимациями для браузера, в Remotion их надо переписать как функцию от номера кадра (useCurrentFrame, interpolate, spring) - CSS-анимации при покадровом рендере не воспроизводятся; хореография и тайминги переносятся один в один.
feature
Каталог шрифтов и выбор под бренд
Проверено 12.08: в Google Fonts 1942 семейства, из них 294 с кириллицей (130 sans, 67 serif, 63 display, 19 mono, 15 рукописных), все OFL, коммерция разрешена. @remotion/google-fonts уже стоит в rols-render. Разметить теги (характер, читаемость в мелком кегле, пара заголовок+текст) и выбирать КОДОМ по бренд-киту, не запросом к ИИ (правило: не платить ИИ за статику).
✓ СДЕЛАНО. Каталог собирается скриптом tools/build-fonts.py из метаданных Google Fonts, а не пишется руками. Цифры сошлись с разведкой от 12.08: из 1946 семейств кириллицу держат 294, условиям отбора отвечают 276, в каталог взяты 160 самых известных. Отбор ПО ДАННЫМ: только кириллица (иначе на экране квадраты), только открытая лицензия, не Noto и не символьные. Характер берётся из полей API (category, stroke, classifications), а не выдумывается: премиальный 33, деловой 28, дерзкий 35, техно 13, дружелюбный 11, нейтральный 40. Выбор под бренд идёт КОДОМ, как и записано в плане: соответствие «манера — характер» устоялось и не требует рассуждения, а запрос к модели давал бы каждый раз чуть разный ответ, и лента бренда перестала бы выглядеть одной лентой. Выбор человека в настройках главнее подбора. ДВЕ ПРАВКИ ПО ХОДУ. Сначала я потребовал не меньше четырёх начертаний — и выкинул Russo One и другие заголовочные, ради которых каталог и нужен: заголовку хватает одного хорошего веса, шесть начертаний нужны тексту. И каждому шрифту, которым нельзя набирать мелкое, назначена пара для текста: иначе подпись под постом окажется нечитаемой, и заметят это не на макете, а на телефоне в ленте. ПРОВЕРЕНО существующей tools/fonts.check.ts: все 160 имён есть в пакете @remotion/google-fonts у рендерера ролсов и держат кириллицу, пары указывают на существующие шрифты, подбор устойчив (один бренд — одна пара). ОШИБКА, О КОТОРОЙ СТОИТ ЗНАТЬ: я перезаписал уже существовавший src/director/fonts.ts, не проверив, что файл есть. Восстановил интерфейс по проверке, которая его переживает, — но правильнее было посмотреть до того, как писать.
feature
Пословные субтитры (караоке)
В эталонах субтитры идут группами по 2-4 слова крупным кеглем с подсветкой текущего слова. Тайминги брать из ElevenLabs with-timestamps (память content-factory-subtitles-whisper-own-tts: Whisper по собственной озвучке врёт). Для чужого видео (разбор трендов) - WhisperX, точность <100 мс.
✓ СДЕЛАНО. Тайминги берутся из САМОЙ ОЗВУЧКИ, а не из распознавания — как и записано в плане (память content-factory-subtitles-whisper-own-tts: Whisper по собственной озвучке врёт). Раньше субтитры к своей же речи собирались распознаванием: мы произносили известный текст, потом слушали запись и записывали услышанное. Это единственное место, где мы сами создавали себе ошибку — на числах, именах и названиях брендов распознавание промахивается. Теперь ElevenLabs отдаёт посимвольные тайминги вместе со звуком (эндпоинт with-timestamps). Текст остаётся наш, точный, от озвучки берутся только моменты времени. ЖИВАЯ ПРОВЕРКА ВСКРЫЛА ТОНКОСТЬ, которой не было в плане: озвучка произносит не то, что написано. «49 900» звучит как «сорок девять девятьсот» (три слова вместо двух), а словарь ударений превращает «Диван» в «Дива́н». Показать зрителю «сорок девять девятьсот рублей» значит сделать субтитры хуже текста; показать «Дива́н» — вывести служебный знак, которого в русском письме нет. Поэтому добавлено выравнивание: каждое написанное слово прогоняется через ту же подготовку, что уходит в озвучку, считается, во сколько слов оно превратилось, и забирается столько же таймингов. На экране «49 900», подсветка идёт от начала «сорок» до конца «девятьсот». Если разошлось сильно — доверяем произнесённому: непривычное написание лучше, чем субтитры не в такт. ПРОВЕРЕНО ЖИВЬЁМ на фразе «Диван «Осло» за 49 900 рублей. Каркас выдерживает 120 килограммов»: 12 произнесённых слов свелись к 10 написанным, совпало слово в слово. Плюс девять проверок на подставной разметке: сборка слов из символов, знаки препинания при слове, число одним субтитром, снятие ударения, поведение при сильном расхождении. Наложение субтитров теперь принимает готовые тайминги и не зовёт распознавание, когда они есть. Для чужого видео (ролик Veo, разбор тренда) распознавание остаётся: там текста у нас нет. ВОРОТА ПОЙМАЛИ МОЮ ОШИБКУ: я дописал функции прямо в tts.ts, который собирается переносчиком — правка жила бы до первой пересборки. Перенёс в сам переносчик, сверка снова зелёная.
feature
↑ Режиссёр-ИИ v1: сценарий -> лист
ИИ пишет ТОЛЬКО лист, ничего не рисует и не считает (compiled-ai). Вход: сценарий, реально доступный материал, бренд-кит, шаблон формата. Три формата на старт: гайд, новость, оффер. Плюс критик листа КОДОМ: длина эпизода, доля говорящей головы, есть ли хук в первые 2 сек, не повторяется ли анимация подряд.
✓ ГОТОВ: src/director/ai.ts, собратьЛист(). Три формата на старт — гайд, новость, оффер; у каждого свои указания про хук, середину и призыв, потому что ролик с чужим хуком не смотрят. COMPILED-AI СОБЛЮДЁН. Модель делает ровно одно: раскладывает строки сценария по эпизодам и пишет надписи на экран. Кодом считается всё остальное — сколько эпизодов и какой длины (levels.ts, раскладка), чем показан каждый, откуда взять материал и во сколько он обойдётся (resolver.ts, добытчик идёт по цене снизу вверх и первым делом пробует нарисовать блоком). КРИТИК КОДОМ ПОСЛЕ СБОРКИ (critic.ts): хук в первые две секунды, длина эпизода, доля говорящей головы, повтор блока подряд, наличие призыва. При стоп-замечании — ОДИН повтор с этим замечанием в задании. Не три: повтор стоит денег, а модель на второй заход обычно возвращает то же самое. ШЕСТЬ ПРОВЕРОК с заглушкой вместо модели (src/director/ai.test.ts) стерегут главное обещание: что бы модель ни ответила, структура остаётся нашей. Вернула два эпизода вместо пяти — длина ролика прежняя и дыр между эпизодами нет; вернула двадцать — лишние отброшены; сослалась на несуществующую строку сценария — эпизод остаётся без реплики, а лист собирается. ЭТА ЖЕ ПРОВЕРКА НАШЛА НАСТОЯЩУЮ ОШИБКУ в моём коде: текст я подставлял безопасно, а выдуманный номер строки клал в лист как есть — разбор листа его не пропускал и ронял сборку целиком. То есть выдумка модели стоила бы всей работы. Агент прописан в бэкофисе как cz-director под главой производства.
feature
↑ Добытчик материала (resolver вставок)
По каждому эпизоду решает источник в порядке цены: моушен-графика кодом (0) -> материалы клиента (0) -> сток Pexels/Pixabay (0, без атрибуции, но кэшировать у себя и не хотлинкать) -> генерация Kie (15-60 руб). Ставка владельца: основную красоту даёт код, видео - лишь часть.
✓ СДЕЛАНО. Порядок строгий и идёт по цене, как и записано в плане: моушен кодом (0) → материалы клиента (0) → сток (0) → генерация (15-60 руб). Останавливаемся на первом подходящем. Смысл не в экономии ради экономии: ролик на 30 секунд из одной генерации стоит около 400 руб, а из графики и своих материалов — полсотни. При ежедневной ленте это разница между «дорого, раз в неделю» и «дёшево, каждый день». Подбор блока идёт КОДОМ: соответствие «в тексте число — покажем счётчик», «есть сравнение — схема до и после», «перечисление — чек-лист» не требует рассуждения, и платить за него каждый раз значит сделать дешёвый уровень дорогим. Лицо и товар графикой не подменяются: там нужен человек или сам предмет. Добытчик НЕ РИСУЕТ и НЕ ТРАТИТ — только решает, откуда взять, и говорит это листом вместе с ценой. Без разрешения тратить генерации не будет вовсе: эпизод закроется текстовой карточкой. ДЕВЯТЬ ПРОВЕРОК поймали две настоящие ошибки: запрос к несуществующей таблице core.artifact_files (файлы лежат внутри артефакта полем files — на живом заказе добытчик вместо бесплатного материала клиента уходил бы в платную генерацию) и слишком узкое правило счётчика: «выдерживает 120 килограммов» не ловилось, потому что искались только рубли и проценты. Сток пока не подключён намеренно: нужен свой кэш, иначе ролик рассыплется через месяц, когда чужая ссылка умрёт.
feature
Фабрика трендов: пилот 10 разборов
Разбор чужого рилса в такой же лист: PySceneDetect (границы склеек) + WhisperX (пословные тайминги) + Vision-модель (описание кадра и тип: лицо/вставка/сплит). Источник данных: Apify, Instagram ~1,5$ за 1000 результатов, TikTok ~1,7$ - 50 трендов дешевле доллара. Итог: лист-эталон + страница оценки ок/не ок. Своего источника нет: файлы владельца взяты из чужого ТГ-канала как образец цели.
✓ Дубль: тот же пилот заведён как «Пилот фабрики трендов: 10 разборов чужих роликов», там же учтено, что он платный и отложен владельцем. Технику разбора (PySceneDetect, пословные тайминги, тип кадра моделью) перенёс туда.
research
Расширение до 100 блоков и 50 трендов
Только после оценки пилота владельцем. Гнать объём до подтверждения направления запрещено - решение 12.08.
feature
Режиссёр длинных роликов: докрутить форматы
ИДЕЯ ВЛАДЕЛЬЦА, в работу не брать без его отмашки. Часть уже начата — надо докрутить. Суть: красивого видео в 2026 мало. Зритель должен понимать, о чём ролик и зачем ему досматривать. Персонаж, который просто стоит, красиво говорит и объясняет мораль, — это не контент и не сериал. Что делать: доработать форматы режиссёра под ДЛИННЫЕ ролики — не набор красивых кадров, а история с завязкой и переменой. Владелец ссылается на разбор из поста и на «скилл сценариста рилс», который он давал: перед работой поднять этот материал и сверить наши форматы с ним. Не начинать, пока владелец не покажет исходник поста и скилл: пересказ идеи здесь неполный, в оригинале была видеочасть.
feature
Проверка драматургии до отрисовки: три вопроса
ИДЕЯ ВЛАДЕЛЬЦА, в работу не брать без его отмашки. Перед тем как рисовать ролик, сценарий обязан ответить на три вопроса: 1. Что происходит с героем прямо сейчас? 2. Что изменится к концу? 3. Если убрать ИИ-эффект, останется ли здесь хоть какая-то история? Третий — главный: он отсекает ролики, которые держатся только на красивой генерации. Ставить это ДО отрисовки, там же, где сейчас работает критик: круг правок текстом стоит рубли, а перерисовка сцен — сотни. Ответы на три вопроса имеет смысл показывать человеку в сценарии: он тогда видит, что правит, а не абзац красивых слов. Формулировки владельца сохранены дословно — при доработке сверяться с ними, а не с пересказом.
✓ СДЕЛАНО по идее владельца, его формулировки сохранены дословно. Три вопроса встали в шаг сценария — рядом с проверкой запретов бренда, где уже написано «переснять сцену стоит 120 руб, переписать текст — рубль». Драматургия работает по той же логике, только про смысл. КАК УСТРОЕНО. Редактор отвечает на три вопроса: что происходит с героем сейчас, что изменится к концу, останется ли история без ИИ-эффектов. Третий — главный: он отсекает ролики, которые держатся только на красивой генерации. Не прошло — переписываем сценарий с КОНКРЕТНЫМ замечанием (чего не хватает, а не «сделай лучше»: расплывчатое замечание даёт расплывчатую правку). Второй круг лучше — берём его. Не лучше — идём дальше со старым, но ответы остаются в работе. РАБОТУ НЕ РОНЯЕМ намеренно: человек заказал ролик и заплатил, отказать ему из-за того, что редактор не увидел перемены, — это решать за него. Наше дело показать. Сценарий, который человек написал сам в плане, не переписываем вовсе: он его согласовал. ВИДНО ЧЕЛОВЕКУ: в работе появился блок «На чём держится ролик» с тремя ответами. Если истории мало — прямо сказано, чего не хватает, и что дело, скорее всего, в этом. Проверка, о которой никто не знает, не работает. Модель дешёвая (gemini-flash) намеренно: это редакторская работа, а не сочинительство. Круг проверки должен стоить копейки, иначе от него откажутся в первый тяжёлый день. ПРОВЕРЕНО НА ЖИВЫХ СЦЕНАРИЯХ. Пустой («девушка в интерьере проводит рукой по дивану, голос перечисляет преимущества, появляется логотип») — НЕ ГОДИТСЯ: «ничего не меняется, кроме появления логотипа», «остаётся только перечисление характеристик». Со событием (мужчина не может занести диван в проём, разбирает, собирает, сидит) — ГОДИТСЯ: «разбирает, заносит, собирает, успешно решив проблему». Плюс четыре проверки на подставной модели: разбор ответа, обрезка пробелов, конкретность замечания.
feature
Критик листа кодом (часть режиссёра-ИИ)
✓ СДЕЛАНО заранее, вместе с форматом листа: критик не зависит от того, кто лист написал. Всё проверяется арифметикой, без модели: хук в первые 2 секунды, длина эпизода, доля говорящей головы, повтор блока подряд, наличие призыва, реплики без кадра. Спрашивать об этом модель — платить за приблизительный и каждый раз разный ответ. Модель нужна там, где надо судить о смысле: это делает проверка драматургии по трём вопросам владельца, сделанная в этой же сессии.
feature
Новые модели Kie: что доступно, а что только в прайсе
✓ Владелец: «на кие аи выкатили клинг новый, ван 3.0 и сиденс 2.5, ван 3.0 720p на 30 секунд около 240 рублей». ПЕРЕКАЧАЛ КАТАЛОГ: 467 записей против 447 три дня назад, видео 271 против 251. ГЛАВНОЕ: у Wan 3.0 и Kling 3.0 turbo ССЫЛКИ НА МОДЕЛЬ ПУСТЫЕ — ключа API нет, в прайсе они есть, вызвать нельзя. Таких свежих анонсов сейчас 51 штука. Заводить по догадке нельзя: из восьми написанных по памяти ключей верным однажды оказался один. ПО ЦЕНЕ РАСХОЖДЕНИЕ: Wan 3.0 720p стоит 15.2 руб/сек, то есть 456 руб за 30 секунд, а не 240. За 242 руб в тридцати секундах идёт Seedance 2.5 в 480p — но только «с видео-входом», когда мы даём исходный ролик. Генерация с нуля в том же 480p стоит 399 руб, в 720p — 898. ЗАВЕДЕНО: seedance-2-5 (ключ есть, сверен с каталогом) как верх линейки, 24 руб/сек клиенту при себестоимости 13.3. Wan 3.0 и Kling 3.0 turbo — ждут ключа.
feature
Разборщик роликов: готовый ролик → лист
✓ СДЕЛАНО как первый шаг фабрики трендов: чужой ролик, который зашёл, превращается в разбор, и по нему видно, как он устроен. Тот же разборщик отвечает на вопрос про свои ролики — почему один досмотрели, а другой нет. PySceneDetect не понадобился: ffmpeg умеет искать смену сцены сам, лишний пакет на сервере не нужен. Речь берётся распознаванием и включается отдельно — у своего ролика текст уже есть, платить второй раз незачем. Роли эпизодов угадываются КОДОМ по месту в ролике и виду фразы: первый — хук, последний — призыв, упоминание цены — оффер, жалоба — проблема. Это названо догадкой прямо в коде: человек, разбирающий тренд, поправит. НАЙДЕНО ОГРАНИЧЕНИЕ, записано в код: ffmpeg сравнивает кадры ПО ЯРКОСТИ. Переход красный→зелёный он не видит вовсе — яркости близки, — а зелёный→синий видит сразу. Значит смена плана внутри одной тональности может слипнуться в один эпизод, и темп монтажа в разборе окажется заниженным. На настоящих роликах это редкость: в нашем ролсе нашлось ровно 6 эпизодов, из скольких он и собран. ПРОВЕРЕНО на своих роликах: ролс 25 секунд — 6 эпизодов, темп 4.2 секунды на кадр, хук 3 секунды; ролик Veo — один кадр на 8 секунд. Отсюда сразу видно, куда расти: в эталонных рилсах кадр меняется вдвое чаще. Восемь проверок на собранном ролике с известными границами — чужой файл превратил бы проверку в гадание «а сколько там на самом деле склеек». ОСТАЛОСЬ ДЛЯ ФАБРИКИ ТРЕНДОВ: выкачка чужих роликов через Apify (ключ есть, около доллара за 50 штук) и определение типа кадра моделью — лицо в кадре или показ товара по звуку не понять. Обе части тратят деньги, поэтому жду отмашки.
feature
Пилот фабрики трендов: 10 разборов чужих роликов
ОЧЕРЕДЬ: этап 4 (контент в разных сценариях), не раньше — порядок владельца. ОТЛОЖЕНО решением владельца 01.09: «про траты давай позже». Разборщик роликов готов и бесплатен: склейки, темп монтажа, длина хука, роли эпизодов. Не хватает двух платных кусков: 1. ВЫКАЧКА чужих роликов через Apify. Ключ уже есть в .env ядра (взят со стейджа). Instagram около 1.5 долл за 1000 результатов, TikTok 1.7 — пятьдесят трендов дешевле доллара. 2. ТИП КАДРА моделью: лицо в кадре, показ товара или графика. По звуку и склейкам это не определить, нужно смотреть кадр. На дешёвой модели выходит рублей тридцать на пятьдесят роликов. ИТОГ ПИЛОТА: десять разобранных трендов в виде листов + страница оценки «ок / не ок», чтобы владелец сказал, туда ли мы копаем. Гнать объём до его подтверждения запрещено — решение от 12.08. ТЕХНИКА РАЗБОРА (из закрытого дубля): границы склеек — PySceneDetect, пословные тайминги — WhisperX, описание кадра и его тип (лицо / вставка / сплит) — vision-модель. Итог: лист-эталон плюс страница оценки «ок / не ок».
feature
Развилка: где живёт Режиссёр — учесть бэкофис
ДОПОЛНЕНИЕ к развилке от владельца 01.09: «в какой-то момент я старался всех ИИ-агентов в сервисах делать через бэкофис, чтобы иметь возможность в одном окне смотреть за их работой, тратой денег и т.д.». Это меняет постановку. Режиссёр состоит из двух разных частей: · РЕЖИССЁР-ИИ (сценарий → лист) — это агент: думает, стоит денег, может ошибаться. Ему место там, где за агентами наблюдают, то есть в бэкофисе. · РЕНДЕРЕР (лист → mp4) — не агент, а конвейер: ffmpeg и Remotion, никакой модели. Ему место рядом с rols-render, отдельным сервисом на своём порту, с очередью и пределом параллелизма (на cfru 8 ядер, рендер 30 секунд 1080x1920 это минуты полной загрузки). Предложение к обсуждению: лист и критик остаются в ядре (они уже там и бесплатны), режиссёр-ИИ ходит через бэкофис, рендерер живёт отдельным сервисом. Решает владелец.
✓ РАЗВИЛКА ЗАКРЫТА 02.09 по правилу самого владельца от 12.08: «если ядро cz-core успеваем доделать — Режиссёр встраивается внутрь; если нет — отдельный сервис по API». Владелец 02.09 решил, что С12 идёт ДО переключения, то есть ядро успеваем. Значит — внутрь. РАЗДЕЛЕНИЕ ПО ЧАСТЯМ (дополнение владельца про бэкофис учтено): · РЕЖИССЁР-ИИ (сценарий → лист) — обычный агент ядра. Живёт в cz-core рядом с остальными агентами линии и докладывает о работе в бэкофис — механика уже сделана 02.09 (src/backoffice.ts, 16 сотрудников прописаны в реестре). Отдельного сервиса ему не нужно: он думает, а не считает, и стоит внутри производства. · РЕНДЕРЕР (лист → mp4) — ОТДЕЛЬНАЯ служба на pm2, по образцу rols-render (/opt/rols-render, 127.0.0.1:3100). Это не агент и не решение, а тяжёлый счёт: рендер тридцати секунд 1080x1920 занимает минуты полной загрузки процессора. Внутри веб-приложения он заблокировал бы кабинет всем клиентам разом. В бэкофисе ему тоже не место — там смотрят за теми, кто думает. ФОРМАТ ЛИСТА от развилки не зависел и уже написан (src/director/list.ts), критик листа кодом тоже (src/director/critic.ts).
feature
↑ ИИ-агенты сервисов — через бэкофис: одно окно на работу
Владелец: «в какой-то момент я старался всех ии агентов в сервисах делать через бэкофис, чтобы иметь возможность в одном окне смотреть за их работой, тратой денег и так далее». Это правило прописки для всего, что решает моделью: Режиссёр, Лия, критик качества, фабрика трендов. Решать не по каждому агенту отдельно, а один раз и на все. Смежная развилка уже заведена — «Развилка: прописка Режиссёра». Что решить: где живёт агент, как его работу видно снаружи сервиса, и что именно бэкофис показывает в одном окне.
✓ СДЕЛАНО. Агенты остаются в ядре — они стоят внутри производства, в транзакции и под лестницей повторов; наружу уходит отчёт о работе, а не сама работа. Механика: 1) в ядре src/backoffice.ts докладывает о каждом вызове модели, ничего не ждёт и не роняет работу; 2) точка доклада одна — там же, где пишется трата, поэтому забытая точка вызова невозможна; 3) имя агента берётся из поля «агент», а если его нет — выводится из шага заказа (сценарий→сценарист, приёмка→приёмщик); 4) в бэкофисе появился приём POST /backoffice/api/runs (раньше в ленту писали только свои инструменты, прямо в файл); 5) 16 сотрудников Контент Завода прописаны в реестре бэкофиса (backend external, project «Контент Завод», общий руководитель cz-pm под CEO) — без этого их запуски в окне не видны, список строится по реестру. Проверено сквозняком без трат: запуск доехал на cz-scriptwriter, цена считается ($0.003 за 777+42 токенов). В прайс бэкофиса добавлены модели ядра — иначе окно показывало бы работу ценой в ноль.
feature
↓ Траты ИИ-агентов в одном окне бэкофиса
ОТЛОЖЕНО владельцем: «про траты давай позже, поставь галочку потом сделать». ПЕРВАЯ ПОЛОВИНА УЖЕ ЕСТЬ: работа агентов ядра видна в окне бэкофиса, и цена запуска считается по токенам (прайс моделей ядра добавлен в apps/web/lib/runs.ts). ЧТО ОСТАЛОСЬ ДЛЯ ПОЛНОЙ КАРТИНЫ ТРАТ: 1. Картинки и ролики. Они идут не через модель текста, а через Kie (src/kie.ts), и в ленту бэкофиса не попадают. А это и есть основная трата: ролик стоит нам сотни рублей против копеек за текст. Нужен такой же доклад из вызватьПровайдера. 2. Рубли рядом с долларами: бэкофис считает в долларах, а владелец смотрит бюджеты в рублях. 3. Сверка с нашим собственным учётом: у ядра есть своя запись трат по заказам (записатьТрату), и две цифры обязаны сходиться, иначе им перестанут верить.
feature
Включить рендерер роликов после усиления сервера
ОТЛОЖЕНО ВЛАДЕЛЬЦЕМ 03.09: «большую нагрузку даёт на сервер, я не хотел пока поднимать мощности сервера. Надо его пока выключить, чтобы был, потом усилим сервер — включим». СЛУЖБА ГОТОВА И ПРОВЕРЕНА, просто остановлена: /opt/director-render, pm2 «director-render» в состоянии stopped, запись сохранена (переживёт перезагрузку остановленной). Проверено сквозняком до выключения: 9 секунд 1080x1920 за 15 с. В ЯДРЕ выключено признаком «рендер:режиссёр» с причиной. Это важно: молча остановленная служба выглядит как поломка, и следующий человек пойдёт чинить сеть и pm2. Сейчас код отвечает «Сборка роликов по режиссёрскому листу: временно выключено» и печатает причину. КАК ВКЛЮЧИТЬ (два действия, оба обязательны): 1) ssh cfru "pm2 start director-render && pm2 save" 2) включить признак «рендер:режиссёр» в ядре (tools/vykl-render.ts — образец вызова переключить(), причина обязательна). ЧТО МЕРИТЬ ПОСЛЕ ВКЛЮЧЕНИЯ: load average до и во время рендера. Сейчас на cfru восемь ядер и load около 3; рендерер берёт половину ядер и держит их минутами. Рядом живут кабинеты клиентов, rols-render и бэкофис.
task
В РАБОТЕS19 · С13 · Вид сервиса66/69

Цель: Разводная страница и три продукта на равных, вид уровня higgsfield и mage.space. Задача целиком: docs/ЮАЙ-ЗАДАЧА.md

↑ Изучить образцы глазами: higgsfield.ai и mage.space
Нужен браузер, текстом уже смотрели и этого мало. Смотреть: лендинг, разделы, кабинет, экран генерации, как показаны примеры работ, как показаны цены и лимиты, наведения и анимации. Снять: палитру, шрифты, сетку, радиусы, приёмы карточек. Результат: разбор в docs/ЮАЙ-ЗАДАЧА.md, а не в чате.
✓ Оба образца сняты глазами, в том числе кабинеты под входом владельца. Разбор в docs/ЮАЙ-ЗАДАЧА.md: палитра, шрифты, радиусы и кегли замерены кодом с живых страниц, 10 приёмов к переносу, что не берём. 15 снимков в docs/obraztsy/. Главное: цена на кнопке до нажатия (higgsfield), кредиты переведены в понятные штуки, меню группами (mage), инструменты лентой а не пунктами, закрытое тарифом с замком.
task
↑ План внешнего вида и обсуждение с владельцем
Пять решений, которые нельзя принимать по ходу (перечислены в docs/ЮАЙ-ЗАДАЧА.md): что видит человек после входа, как уживаются три продукта, что делать с пунктами под тарифом, одна тема или две, публичные страницы свои или на рабочем домене. Код начинать только после «ок».
✓ Макеты согласованы владельцем 18.08.2026 словами «огонь, супер, мне очень нравится». Лендинг myvisionagency.ru/lab/cz-core-lending/, кабинет myvisionagency.ru/lab/cz-core-vid/. Закрыты все пять развилок плюс стилистика: палитра из логотипа (чёрный, лайм c7f52b, белый, техно-капс, гранж-мазки через feTurbulence). Три захода: первый макет забракован по лендингу и мельтешению, второй дал четыре варианта лендинга и выбор «живой соцсети», третий свёл всё в фирменный стиль. Отдельно поймано правило: лайм не бывает полупрозрачным, под альфой на чёрном он даёт болото.
task
↑ Разводная страница личного кабинета
Сейчас человек после входа падает сразу в СММ-Центр, и сервис выглядит как один СММ-Центр. В рабочем сервисе это /dashboard: плитки цифр, три карточки продуктов (Контент Заводы, Студия, СММ-Центр), отдельный призыв на тест генерации. ЗАВОДЫ ОТДЕЛЬНЫЙ ПРОДУКТ, не раздел СММ-Центра.
✓ Сделано и выложено на стенд, проверено глазами в двух темах и на телефоне. Данные собирает обзорБренда() в src/screens.ts одним походом в базу. Числа с разрезом: заводов работает из настроенных, в производстве и сколько застряло, готово за неделю с «+N к прошлой», потрачено за месяц со средней ценой штуки (при нуле работ показываем остаток, а не «0 ₽ за штуку»). Карточки продуктов разные: у Заводов очередь выпусков с неподвижными обложками и состоянием словами дела («Собирается», «Ждёт вас», «Опубликовано»), у Студии форматы из каталога с числом инструментов, у СММ-Центра подключённые площадки с индикатором живости и три настоящих числа (четвёртое из макета, «новые отклики», не показываем: «Входящие» не перенесены). Зовущий блок на Студию. Живое видео только в ленте последних работ и только у видимых плиток. Пустые состояния объясняют, чего не хватает. Проверки на обзорБренда добавлены в tools/screens.check.ts.
task
↑ Шапка и боковое меню по новой структуре
Шапка: Главная, Тарифы, Инструкции, Блог, Обновления, Кабинет, кнопка «Создать завод», баланс, вход в ЛК. Убрать: Партнёрка, Все ИИ агенты, Наши боты. Оставить: Настройки, ИИ-ассистент, Поддержка, Лия-архитектор. Грабли прода: баланс и бейдж тарифа наезжали на пункт «Кабинет». Тариф держать в теле дашборда.
✓ Сделано и выложено на стенд, вид проверен глазами в двух темах и на телефоне. Меню группами по продуктам (Обзор, Контент Заводы, Студия, СММ-Центр, Компания); раскрыта группа текущего продукта, а на разводной первая — иначе меню было пятью заголовками над пустотой. Мёртвых пунктов не осталось: «Собрать завод» ведёт в список заводов, где и живёт форма, «Конкуренты» и «Поддержка» видны с пометкой «скоро», но не нажимаются (раньше все три давали «не найдено»). Добавлены «Настройки компании» и «Права и доступ» — страницы работают, а входа в них не было. /b/{бренд} больше не 404, уводит на разводную. ЗАМКИ ПЕРЕДЕЛАНЫ: считаются в src/tarif.ts по настоящей таблице тарифов, а не написаны буквами. С «ИИ-аватара» и «Конкурентов» замок снят — в биллинге за них не берут денег; остались «Тренды» и «Аналитика». Называется самый дешёвый ПЛАТНЫЙ тариф: у «Корпоративного» цена ноль, и он вылезал первым. Без подписки не закрыто ничего — её нет у 718 организаций из 722. Проверка tools/tarif.check.ts (13 проверок) вписана в гейт. Меню телефона переписано под ту же структуру: внизу Обзор, Заводы, Студия, Публикации, остальное за «Ещё» теми же группами.
task
↑ Создать завод: отдельный поток
В ядре есть только настройка готового завода. В рабочем сервисе это /factory/create и главная кнопка шапки. Пересекается с задачей «Мастера создания заводов» в С11.
✓ Сделано и выложено, проверено глазами в обеих темах и заведением настоящего завода. Своя страница /b/{бренд}/factories/new вместо формы внизу списка; на неё ведут кнопка «Создать завод» в шапке, пункт меню «Собрать завод» и призыв с разводной, когда заводов ещё нет. Два входа: собрать самому и помощник по брифу (помощник переехал сюда же со списка). ГЛАВНОЕ — СМЕТА ДО НАЖАТИЯ: на форме видно цену каждого формата за штуку, а внизу живая сумма «столько-то ₽ в месяц за N шт., в среднем столько-то за штуку» и честное «на счету хватит примерно на N мес.» либо «на месяц не хватает, пополните до запуска». Формула сметы вынесена в src/prices.ts (сметаКонвейера) и теперь одна на страницу сборки и на помощника — раньше помощник считал у себя, и две копии разошлись бы молча. Расписание задаётся сразу при заведении: раньше завод создавался без единого дня, человек уходил уверенным, что всё готово, а завод молчал. Завод по-прежнему появляется в режиме «только по кнопке» и ничего не тратит. Список заводов очищен от формы: там теперь список и кнопка. Побочно починена подсветка меню: «Мои заводы» и «Собрать завод» живут в одном разделе, и меню врало, где человек находится — Каркас принимает имя текущего пункта. Смоук вырос до 54 проверок.
task
Тест генерации
РЕШЕНО ВЛАДЕЛЬЦЕМ 19.08: проба платится из подарочных 100 ₽, генерация видео на Veo 3.1 Fast, цена клиенту 60 ₽. Проверено: у Kie это отдельный эндпоинт /api/v1/veo/generate (не общий jobs/createTask), модель veo3_fast, ролик 8 секунд, опрос через /api/v1/veo/record-info, себестоимость $0.30 = ~28 ₽ по курсу 95. Маржа 2,1x. В рабочем сервисе та же модель стоит клиенту 90 ₽ — проба дешевле нарочно. В реестре моделей ядра Veo пока НЕТ, добавляется этой задачей.
✓ ГОТОВО И ПРОВЕРЕНО ЖИВЫМ РОЛИКОМ. Экран /b/<бренд>/test-video: одно поле, три готовых замысла, выбор кадра, кнопка «Сделать за 60 ₽». Модель veo3_fast в реестре, вызов своей веткой в src/kie.ts (у Veo отдельный адрес /api/v1/veo/generate и своё состояние успеха числом). Ролик 8 секунд 1080×1920 со звуком лежит у нас, открывается и скачивается. ДВА ЖИВЫХ ПРОГОНА НАШЛИ ДВЕ НАСТОЯЩИЕ ПОЛОМКИ, обе денежные. (1) РАЗБОР ОТВЕТА: Veo кладёт ссылку в data.response.resultUrls, а общий createTask — строкой в resultJson. Первый ролик сняли, деньги провайдеру ушли, ссылку мы прочитали как пустую, шаг посчитали неудавшимся, второй заход остановил потолок трат — работа кончилась провалом при готовом оплаченном ролике. Разбор теперь понимает обе формы, закреплено юнит-тестом на настоящих ответах (src/kie.test.ts, 5 проверок). (2) ЦЕНА ШАГОВ (важнее): резервировали 60 ₽, а списывали 17 ₽ — цена заказа бралась из каталога студии, а цены шагов из линии, где стоит опорная. При себестоимости 28,5 ₽ мы дарили клиенту работу и уходили в минус на КАЖДОЙ студийной генерации, молча: все проверки смотрели на цену заказа, а она была правильной. Теперь цены шагов приводятся к цене заказа (остаток от округления — на последний платный шаг). Это общее правило, оно закрывает и второй случай: пункт плана с ценой, зафиксированной при утверждении. Записано как INV-084, держится тремя проверками в tools/orders.check.ts. ГЕЙТ ЗЕЛЁНЫЙ ЦЕЛИКОМ. Заодно научил проверку имён моделей различать модели со своим адресом: у Veo имя вызова veo3_fast, а в каталоге Kie она лежит как veo-3-1.
task
Поиск конкурентов
В рабочем сервисе /dashboard/competitors. В ядре нет совсем. Отдельный раздел, не внутрь СММ-Центра.
✓ СДЕЛАН. Отдельный раздел «Конкуренты» в СММ-Центре (пункт меню больше не «скоро»), миграция 0070, src/competitors.ts, экран app/b/[brand]/competitors. СМЫСЛ РАЗДЕЛА — ПЕРЕХОД, а не витрина: увидел чужой ролик, который зашёл, нажал «Сделать своё» — и он лёг идеей в свой контент-план на сегодня. Без этой кнопки раздел был бы выставкой чужих успехов, от которой в работе клиента ничего не меняется. Проверено живьём: идея дошла до плана со статусом «одобрена». ЧТО УМЕЕТ: добавить канал по ссылке (пять способов написания ссылки), цифры канала и вовлечённость по роликам, список роликов, отсортированный по просмотрам (нас интересует что СРАБОТАЛО, а не что вышло вчера), обновление по кнопке, удаление. ТОЛЬКО YOUTUBE, и это сказано на экране прямо: у него открытая статистика по чужим каналам, у ВК и Телеграма её нет вовсе, у Instagram закрыта. Обещать их в интерфейсе значит обещать несуществующее. Столбец network в таблице есть — добавить сеть потом дешевле, чем переносить таблицу. КВОТА: цифры храним у себя и обновляем по кнопке. Перечитывать каналы при каждом открытии страницы значит выбрать суточную квоту YouTube к обеду. Кончилась квота — человек читает «на сегодня разбор исчерпан», а не «ошибка 403». ЧТО ПОЙМАЛ ГЕЙТ (и это его работа): кириллица в имени параметра адреса «?открыт=» — имя параметра не кодируется и ломает заголовок переадресации; и redirect() внутри try, который поймал бы мой же catch и показал ошибку вместо перехода. Обе правки сделаны. ЧТО ПОЧИНИЛ ГЛАЗАМИ: «Обновить цифры» обновляла только ролики, не трогая подписчиков и просмотры — кнопка обещала больше, чем делала. И скрытые подписчики (YouTube даёт hiddenSubscriberCount) показывались нулём, то есть «у конкурента ноль подписчиков» вместо «канал их не показывает».
task
Студия: сверить инструменты с рабочим сервисом
В рабочем сервисе 14 инструментов: Обзор, Проверка на вирусность, Фотосессия, Видео, Изображения, Музыка, Озвучка, Аватар, Нарезка рилсов, Анимация, Удаление фона, Брендирование, Банк идей, Библиотека. В ядре /studio, /studio/[вид], /studio/library. Сверить поштучно и решить, что переносим, а что закрываем.
✓ СВЕРКА ЗАКРЫТА решением владельца 19.08: «Студию переноси» — переносим все шесть инструментов, которых в ядре нет. Сверка показала, что платного спроса за 8 месяцев они почти не дали (одно списание на 40 ₽), но решение владельца важнее статистики: витрина должна выглядеть не беднее рабочего сервиса. Работа разбита на шесть отдельных задач.
task
Поддержка и ИИ-ассистент со стороны клиента
В ядре есть только сторона сотрудника. Поддержка в рабочем сервисе это всплывашка в меню.
✓ СДЕЛАНО обе стороны, проверено живым сценарием на стенде: клиент написал — сотрудник увидел в очереди — ответил — у клиента загорелась точка. КЛИЕНТ: раздел /b/<бренд>/support, слева свои обращения, справа переписка или форма нового с четырьмя готовыми поводами (пустое поле «тема» отсеивает часть людей). Показываем ВСЕ его обращения, а не по текущему бренду: человек пишет один раз, а брендов у него может быть пять. СОТРУДНИК: /admin/support, очередь «сначала те, где ждут нас», ответ, закрытие ответом или без ответа, ссылка на карточку человека. МЕТКИ: точка «вам ответили» гаснет при открытии, метка сотрудника — тоже при открытии, а не при ответе (посмотреть и отложить бывает, а «новое» после прочтения — вранье очереди). Закрытие без ответа точку НЕ ставит: молча закрытая переписка не должна выглядеть как ответ. Писем не шлём — у клиента остаётся ровно три повода получить от нас письмо. Миграция 0060 (unread_client + три индекса), src/support.ts, tools/support.check.ts — 21 проверка в гейте, включая «чужое не открывается по прямой ссылке» и «дописать в чужую нельзя». ГЛАЗАМИ ПОЙМАНО ТРИ: точка была не видна в свёрнутой группе меню (сигнал, которого не видно, — это не сигнал; теперь точка и на заголовке группы); форма ответа восстанавливала текст после отправки и сотрудник мог отправить то же дважды; список обращений читался ДО снятия метки, и точка горела рядом с перепиской, которую человек в этот момент читает. ИИ-ассистента в этой задаче нет — он ждёт решения по В-20.
task
Публичные страницы: решить и сделать
Главная, Тарифы, Инструкции, Блог, Обновления. Сейчас шапка ядра уводит на 24contentzavod.ru намеренно. Развилка: свои страницы в ядре или развод по адресам в nginx. Связано с задачей С11 «Развести адреса на nginx».
✓ РЕШЕНО И СДЕЛАНО. Развилка «свои страницы в ядре или развод по адресам» закрыта в пользу развода: витрина (главная, тарифы, инструкции, блог, обновления, оферта, политика, условия, контакты, документация) остаётся на рабочем домене. Она не пишет ни строки клиентских данных, и переносить её в ядро — это одиннадцать страниц работы ради нуля пользы. Решение уже стояло в docs/ЧТО-ОСТАЛОСЬ.md, сверился и держусь его. ЧТО СДЕЛАНО В ЯДРЕ: найдена дыра — ссылок на оферту и политику не было НИГДЕ. Человек заводил кабинет и платил деньги, ни разу не увидев, по каким условиям. Теперь ссылки стоят в двух местах: у кнопки «Завести кабинет» и под кнопкой «Пополнить». Согласие у кнопки, а не галкой выше: галку ставят не читая, а сюда взгляд падает в момент решения. Адреса собраны в одну константу ПРАВОВЫЕ (app/_части/каркас.tsx) — когда витрина переедет, править одно место. Держит tools/pravovye.check.ts в гейте: проверяет и что ссылки в исходниках на месте (иначе их снесут при правке вёрстки и никто не заметит), и что страницы ещё отвечают 200 — витрина живёт отдельно от нас, её могут переставить. ЧТО ОСТАЛОСЬ НА ДЕНЬ ПЕРЕКЛЮЧЕНИЯ: витрина ссылается на СТАРЫЙ кабинет в пяти местах (/auth/login, /auth/register, /dashboard, /settings, /factory/create в Header.tsx и Footer.tsx). Раньше дня переключения их трогать нельзя — прод живой. Список с файлами и признаком готовности дописан в docs/ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md отдельным шагом 4.1.
task
↑ Перенести подарок за регистрацию
На проде работает: триггер handle_new_user (supabase/migrations/20260805_company_on_signup.sql) берёт features->>balance_bonus тарифа free, кладёт на баланс и пишет транзакцию «Подарок за регистрацию». Живое значение в БД = 100 ₽ (в миграции 055 лежит 300, база главнее). В ядре этого нет: есть только промокоды с бонусом. Начислять надо через кошелёк на двойной записи, а не правкой баланса — иначе повторится баг админской правки счёта, где начисление писалось без заказа и поддержка не могла вернуть деньги. Нужна защита от многоразовой регистрации на новые почты. Лендинг давит на «100 ₽ на счёт после регистрации», так что без этого переноса лендинг обещает несуществующее.
✓ Сделано и проверено на живой регистрации: новый кабинет видит 100 ₽ и «хватит на работы» вместо красной плашки «Счёт пуст». Сумма в core.tariffs.gift_rub (миграция 0056), не в коде. Начисление через кошелёк на двойной записи, идемпотентность держит база (провайдер + организация), сбой начисления не мешает войти. Путь клиента дополнен проверкой подарка.
feature
↑ Лендинг по согласованному макету
Макет согласован: myvisionagency.ru/lab/cz-core-lending/, исходник C:\tmp\cz-core-lending.html. Перенести в код все девять блоков рабочего лендинга с текстами прода: герой со стеной контента и реакциями, три сегмента (ведут на /for/sellers, /for/services, /for/creators), четыре шага, девять возможностей, студия с форматами и моделями, витрина работ вкладками видео-карусели-посты, тарифы живыми ценами из базы, шесть вопросов гармошкой, финал, подвал. Стилистика: чёрный, лайм #c7f52b, белый, техно-капс, гранж-мазки через feTurbulence. ЛАЙМ НЕ БЫВАЕТ ПОЛУПРОЗРАЧНЫМ: под альфой на чёрном даёт болото, подложки нейтральные. Цены за штуку на лендинге НЕ показывать, упор на регистрацию и подарок 100 ₽. Подарок зависит от задачи «Перенести подарок за регистрацию» — без неё лендинг обещает несуществующее. Контент: 15 роликов /landing-v2/videos/proof/ и 12 картинок /landing-v2/images/proof/ с прода, стоп-кадры вынуты ffmpeg. Перед публикацией пересчитать на живой базе цифры 733 работы и 170 заводов.
✓ СДЕЛАН И ОТКРЫВАЕТСЯ на /lp. Все девять блоков согласованного макета в коде: герой со стеной контента и всплывающими реакциями, три сегмента, четыре шага, девять возможностей, студия с форматами и моделями, витрина работ вкладками, тарифы, вопросы гармошкой, финал, подвал. Классы оставлены именами макета — так проще сверять «то же самое или нет», открыв макет рядом. ЧИСЛА ЖИВЫЕ, КАК И ТРЕБОВАЛА ЗАМЕТКА К МАКЕТУ («перед публикацией пересчитать на живой базе»): 745 работ, 170 заводов, 5 сетей, 18 моделей, подарок 100 ₽ — всё считается при открытии страницы. Цены тарифов из core.tariffs. И ГЛАВНОЕ — УБРАНЫ ОБЕЩАНИЯ, КОТОРЫХ ЯДРО НЕ ДЕРЖИТ. В макете стояли тексты рабочего сервиса: «15+ моделей видео» (в ядре 4), «10+ моделей картинок» (3), «10 соцсетей напрямую» (5), «ещё восемь площадок», Kling/Hailuo/Runway/Midjourney/Flux в списке моделей (их нет), «клонирование голоса» в тарифе Бизнес (не сделано). Всё это заменено на живые числа из реестра и на подписи тарифов, собранные из limits в базе: поменяли тариф — поменялась витрина. Обещание, которого нет внутри, человек обнаруживает после оплаты. Кадры роликов скопированы к нам (public/lp/kadr), чтобы витрина не зависела от лабораторной папки на myvisionagency. Стили макета перенесены целиком и заперты под .лендинг: у витрины свой чёрный фон и свои размеры, пускать это в кабинет нельзя.
feature
↑ Догнать перенос: прод ушёл вперёд
Сверка переноса 19.08 не сходится и блокирует штатную выкладку: в ядре на 100 ₽ больше, расходится у одного человека. Причина не в коде - рабочий сервис живёт, перенос делали 16.08, накопилось 39 людей, 3 готовых работы, 58 денежных операций. Лечится прогоном переносов (у каждого есть режим показа без записи и флаг --делать). Пока не догнали, выкладка идёт с SKIP_GATE=1, и это плохо: гейт данных выключен целиком, а не только сверка. Команда: cd /var/www/cz-core && set -a && . ./.env && set +a && npx tsx tools/sverka-perenosa.ts
✓ РАЗОБРАНО И ЗАКРЫТО решением владельца: финальный перенос людей и балансов делается перед релизом на прод, а не сейчас — расхождения появляются потому, что прод живой и люди в нём работают, пока мы переносим. Разбор дал картину крупнее одного человека: на проде БАЛАНС ЭТО САМОСТОЯТЕЛЬНОЕ ЧИСЛО, а не сумма операций — у 326 человек из 538 он не сходится с их же журналом (у 310 больше, у 16 меньше). Там его правили напрямую. Перенос берёт именно число баланса и подгоняет ядро записью выравнивания; пересчитывать по журналу нельзя — половина клиентов увидит не ту сумму. Сухой прогон подтвердил: финальный перенос сам запишет −100 ₽ и остатки сойдутся, ключ идемпотентности включает посчитанную сумму. СВЕРКА ПЕРЕДЕЛАНА: проверки разделены на «беда» (потеряно или задвоено — переключаться нельзя) и «отстало ⟳» (прод ушёл вперёд, догоняется переносом, выкладку не останавливает). Заодно исправлена проверка заводов: она сравнивала общее число в ядре и в проде и краснела от любого нашего же завода — показательного, пробного, из проверки; то есть ровно тогда, когда стендом пользовались. Теперь сверка зелёная, 28 проверок, и штатный гейт больше не стоит красным из-за живого прода. В ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md дописано: признак готовности — ни одной строки с ⟳.
⛔ Сверка переноса блокирует штатную выкладку: в ядре на 100 ₽ больше, расходится у одного человека. РАЗОБРАНО 19.08, прогоном переносов НЕ лечится. Это roman-kotov@bk.ru: в проде 710 ₽, в ядре 810 ₽. 16.08 перенос выровнял ему остаток записью «выравнивание остатка при переносе». После этого баланс в проде уменьшился на 100 ₽ БЕЗ ЕДИНОЙ ОПЕРАЦИИ В ЖУРНАЛЕ: последняя запись в public.transactions у него от 09.08, неперенесённых операций нет вообще. То есть на проде число правили мимо журнала — ровно то, против чего в ядре сделан кошелёк на двойной записи. РЕШЕНИЕ ЗА ВЛАДЕЛЬЦЕМ: считать правильным число прода (тогда в ядро нужна запись выравнивания с причиной) или число ядра (тогда искать, кто и когда правил баланс руками на проде). Пока не решено, выкладка идёт мимо этого шага гейта — остальные 58 проверок гоняются отдельно.
bug
Вход и регистрация: привести к фирменному виду
Страница входа осталась от прежней вёрстки: форма прижата в левый верхний угол, много пустоты, фирменного оформления нет. Это первый экран клиента. Палитра уже фирменная, нужна композиция: карточка по центру, логотип, обещание подарка за регистрацию рядом с кнопкой.
✓ ДОДЕЛАНО: страница входа по приглашению приведена к тому же виду, что вход по паролю. Была замечена при живой проверке — осталась от прежней вёрстки, и человек, пришедший по приглашению, видел сервис, собранный из двух разных вёрсток. Тот же разворот: слева знак, надпись «вас позвали в кабинет», заголовок и три строки про совместную работу (своё имя в журнале, права от владельца, общий кабинет), справа карточка с формой.
feature
↑ Настройки компании: разделы, каталог товаров, айдентика
Владелец разобрал прежнюю страницу настроек: блоков много, плохо читаемы, одно поле на логотип и фото товара, цвета кодом, и вопрос «откуда взялось обращение на вы». Разобрано по коду: часть полей работала, обращение слушала одна линия из пяти, водяной знак не читал никто.
✓ Сделано и выложено, проверено глазами и заведением настоящего товара. ШЕСТЬ РАЗДЕЛОВ со своим боковым списком: Компания, Товары, Аудитория, Голос бренда, Айдентика, Площадки; у каждого написано, на что он влияет в производстве. КАТАЛОГ ТОВАРОВ (миграция 0057): принадлежит бренду, завод выбирает из него; артикул, цена, ссылка на маркетплейс, плюсы, минусы, фото, наличие. Работа помнит свой товар (works.product_id), профильДляРаботы подставляет его описание и фото вместо общего — подмена в одном месте, линии про каталог не знают. Минусы товара уходят в запреты. Завод раскладывает товары по кругу вместе с форматами, пункт плана помнит товар (миграция 0058). ГОЛОС доехал до всех линий: голосСтрокой = тон + обращение, обращение проверяется на приёмке от двух совпадений. ВОДЯНОЙ ЗНАК теперь настоящий: ffmpeg накладывает логотип на готовую картинку. ЛОГОТИПОВ НЕСКОЛЬКО с формой и назначением, логотипДля() выбирает нужный. ШРИФТ уходит дизайнеру карусели. ЦВЕТА выбираются пипеткой, а не вписываются кодом. Проверки: products.check.ts (24) и обращение в brand-profile.check.ts, обе в гейте.
feature
🔴 Пять экранов не сохраняли ничего: действие замыкало функцию
Найдено при проверке сохранения товаров у завода. Внутри страницы жила вспомогательная функция назад(...), и все действия звали её в redirect(назад(...)). Серверное действие замыкает на себя всё, что использует, Next пытается это сериализовать и падает с «Functions cannot be passed directly to Client Components». Страница при этом ОТКРЫВАЕТСЯ: ошибка только при отправке формы.
✓ Починено на пяти экранах: карточка завода (настройка, режим, сборка и утверждение плана), контент-план, публикации, права и доступ, страница вида в Студии. То есть не работали утверждение плана, публикация, выдача прав и заказ разовой работы. Ни типы, ни смоук этого не ловили: типы довольны, страницы отвечают 200. Заведена проверка tools/server-actions.check.ts — читает исходники и ищет ровно этот случай, вписана в гейт.
bug
↑ Разбор владельца: убрать ерунду из настроек, добавить рубрики и призыв
Владелец прошёл по свежим настройкам: цена в разделе компания, «чем вы лучше других», непонятные галки про сайт и телефон, нечитаемая кнопка, «что беспокоит покупателя» для монтажной пены, шрифт непонятно зачем. Просил подумать с позиции СММщика крупного бренда и селлера.
✓ Проверено по коду, а не по памяти, и исправлено. УБРАНО: цена из раздела компания (она у товара); шрифт из айдентики — применить его сегодня некуда, модель его не соблюдает, свой рендер есть только у Ролса и шрифта не принимает, субтитров ядро не делает вовсе. ПЕРЕФОРМУЛИРОВАНО: «чем вы лучше других» → «почему выбирают вас»; «что беспокоит» → «что мешает купить» (сомнения и возражения); «кто покупатель» → «кто покупает и зачем». ДОБАВЛЕНО «КУДА ВЕДЁМ»: никуда / сайт / карточка товара / личные сообщения, ссылка подставляется настоящая, призыв уходит копирайтеру. Заменяет две противоречивые галки: ведём на сайт — сайт в текстах разрешён сам. Первым комментарием не умеем, и это написано на экране: Upload-Post не поддерживает. ДОБАВЛЕНЫ РУБРИКИ с долями (раздел «О чём говорим»): планировщик раскладывает их по весам на месяц вперёд и чередует, пункт плана помнит рубрику (миграция 0059), копирайтер видит её вместе с темой. Есть готовые наборы для товара и для услуг. ПОЧИНЕНО: main a.кнопка наследовала цвет от родителя — белый текст на лайме на всех кнопках-ссылках кабинета. Проверено вживую: товар заведён, выбран заводом, план собран — доли соблюдены (обзор 6, польза 4, отзывы 2), товар в каждом пункте. Проверки: каталог и рубрики 28, профиль 23, смоук 54.
feature
↑ Слой завода в интерфейсе: позиционирование с наследованием от бренда
Владелец: «ты придумываешь настройки с нуля, а не берёшь наработки текущего завода». Разобран прод: 120 колонок в factories, компания и ограничения дублируются в каждом заводе. В ядре трёхслойная модель с наследованием была с самого начала (src/factory-profile.ts), но в интерфейс не выводилась.
✓ Экран factories/{завод}/about: тема конвейера, аудитория, тон и обращение, свои запреты, «о чём не пишем», свои рубрики, предмет для не-товарных заводов. У каждого поля подписано, что стоит у бренда — пустое поле значит «как у бренда». Внизу «что в итоге получит производство»: сведённый профиль. Проверено вживую — тон и аудитория перекрыты заводом, предмет взят от бренда, запретов стало 3 (1 брендовый + 2 заводских, сложились), рубрики заменены заводскими. Рубрики добавлены в СхемаПозиционирования: заводские заменяют брендовые целиком, а не дополняют.
feature
Субтитры на завод: шрифт, размер, цвет, положение, пресеты
Перенос с прода (subtitles_font, size, color, outline, position, short_video, avatar) плюс пресеты Жёлтый/Неон/Мятный/Чистый. Требует правки службы рендера rols-render: она принимает субтитры {text,startSec,endSec}, но стиль в ней зашит намертво. Ядро сейчас шлёт пустой список. Сюда же вернётся фирменный шрифт — он про субтитры конвейера, а не про марку.
✓ Сделано и проверено настоящей сборкой ролика. СЛУЖБА РЕНДЕРА научилась стилю субтитров: шрифт, кегль, цвет, цвет и толщина обводки, положение, капс — раньше вид был зашит в компонент. Обводка штрихом поверх заливки, а не четырьмя тенями: на крупном кегле тени дают рваные углы. ЯДРО собирает субтитры из реплик озвучки — тот же текст, что слышит человек, и те же тайминги; шаг озвучки стал держать текст и длительность рядом со звуком. НАСТРОЙКА НА ЗАВОДЕ, как на проде: экран factories/{завод}/video, шесть готовых стилей с прод-названиями и живой предпросмотр на макете кадра. ПОЙМАНО ПРИ ПЕРЕНОСЕ: у Poppins нет кириллицы, а на проде он в пресете «Чистый» — русские субтитры вышли бы квадратами; заменён на Inter, в выборе оставлены только шрифты с кириллицей. И субтитр наезжал на подпись сцены — увидел на первой пробной сборке, теперь подпись поднимается. Машинные проверки этого не видят: рендер отрабатывает успешно. ОГРАНИЧЕНИЕ, написанное на экране: субтитры только на Ролс — у готового ролика от модели нет ни таймингов, ни текста реплик.
feature
Плашка-хук на завод
hook_overlay_enabled и hook_overlay_style с прода: шрифт, размер, цвет, обводка, положение, длительность.
✓ Сделано и проверено настоящей сборкой ролика. Крупная надпись поверх первых секунд с притемнением кадра, уходит плавно за 0,4 с — резкая смена читается как склейка. Текст берётся из подписи первой сцены, своего сервис не выдумывает: лишний вызов модели ради заголовка, который уже есть. Настройка на заводе рядом с субтитрами, с живым предпросмотром и длительностью 1–6 с.
feature
Стиль контента и персонажи на завод
content_style (продающий, обучающий, развлекательный, разговорный, кинематограф) и characters/characters_mode с прода.
✓ Сделано. Стиль подачи (продающий, обучающий, развлекательный, разговорный, истории, кинематограф) и описание того, кто в кадре. Задаётся на заводе, перекрывает бренд, читает режиссёр роликов и общий контекст модели.
feature
Свои промты завода — только сотрудникам
scriptwriter_prompt, smm_prompt, designer_prompt с прода. Решение владельца: клиенту не показывать, сломает себе генерацию. Нужны нам, чтобы подкручивать конкретный завод руками.
✓ Сделано по решению владельца: блок виден только сотрудникам, и промты принимаются только от них — поле на клиентском экране не рисуется, но форму можно отправить и мимо экрана. Добавка идёт ПОСЛЕ наших указаний и не отменяет их: формат, запреты и «не выдумывать числа» остаются. Сейчас читается добавка сценаристу (линия Ролса); дизайнерская и копирайтерская сохраняются и подключатся со своими линиями — написано прямо на экране.
feature
Логотипы и цвета: бренд хранит, завод выбирает
На проде post_logo_url/post_brand_colors и carousel_logo_url/carousel_brand_colors раздельно. Сейчас в ядре назначения заданы на бренде; надо дать заводу выбирать из брендовых.
✓ Сделано. Логотипы и палитра принадлежат бренду, завод выбирает: какой логотип на картинки, какой в ролики, какими цветами работать. Не выбрал — берётся брендовое и едет за брендом при правке. Выбор переносит назначение на выбранный логотип и снимает с остальных, поэтому логотипДля возвращает выбранное заводом, а линиям про заводы знать не нужно. Чужой путь не подставляется: поле показывает только брендовые логотипы, но форму можно отправить мимо экрана, поэтому выбор проверяется на сервере. Экран завода переименован в «Как выглядит» — там логотип с цветами, субтитры и плашка-крючок. Проверки в factory-profile.check.ts: 22 (было 17).
feature
↓ Реквизиты и юридические ограничения на уровне организации
ОТЛОЖЕНО РЕШЕНИЕМ ВЛАДЕЛЬЦА 19.08: не делаем сейчас. «У нас такого не было, таких вопросов не было, столько ещё делать — готовить новую фишку прям щас не хочется, но точно надо оставить под неё место в будущем». Вернуться ПОСЛЕ переключения на прод, как отдельную фичу. МЕСТО В АРХИТЕКТУРЕ УЖЕ ОСТАВЛЕНО: уровень организации (core.orgs) свободен — сейчас на нём нет ни одной настройки, всё живёт на бренде и заводе. Когда понадобится, сюда лягут: реквизиты, дисклеймеры, маркировка рекламы (ЕРИР), кто согласует публикацию, возрастные ограничения. Наследование то же, что у бренда и завода: организация задаёт, бренд уточняет. Ничего переделывать не придётся — только добавить слой.
feature
Студия: проверка на вирусность
ИИ разбирает ролик и говорит, что мешает ему разойтись: хук в первые секунды, темп, субтитры, звук. В рабочем сервисе 40 ₽ за разбор, результат нигде не хранится — в ядре хранить как работу, иначе человек платит второй раз за то, что уже смотрел.
✓ СДЕЛАНО И ПРОВЕРЕНО ЖИВЫМ РАЗБОРОМ. Расход 0,24 ₽, с клиента 40 ₽ (как в рабочем сервисе: 40 ₽ до 30 секунд, дальше рубль за секунду, считается по НАСТОЯЩЕЙ длине файла). РАЗДЕЛЕНИЕ ТРУДА: длина, размер кадра, вертикальность, наличие звука и средняя громкость меряются КОДОМ через ffprobe и уезжают модели готовыми числами с прямым запретом их пересчитывать. Модель судит только то, что измерить нельзя: цепляет ли хук, ясен ли посыл, на какой секунде свайпнут, аккуратно ли вплетён товар. Без этого модель пишет «ролик секунд двадцать» там, где восемь, и это число живёт в отчёте как правда. Проверка на живом ролике показала, что модель действительно СМОТРИТ видео: описала пылинки в лучах солнца и медленный наезд на подушку — это в точности наш Veo-ролик. Поставила 2/10, «свайпнут на первой секунде», три конкретных правки. Строго и по делу. В ядро добавлена возможность показать модели видео (src/ask.ts): раньше туда уходил только текст. У Gemini выключены рассуждения — они съедают предел токенов, эти грабли уже ловили. Разбор показывается на экране работы человеческим видом: балл, вердикт, что поправить в первую очередь, где бросят смотреть, разбор по частям. JSON-файл тоже сохраняется — его можно забрать.
feature
Студия: фотосессия товара
Комплект кадров товара в едином стиле, в рабочем сервисе 100 ₽ за комплект. В ядре уже есть каталог товаров с фото и брендстиль — фотосессия берёт исходник и стиль оттуда, а не спрашивает заново.
✓ ГОТОВО И ПРОВЕРЕНО ЖИВЫМ КОМПЛЕКТОМ. Четыре кадра с одной фотографии товара: каталожный на чистом фоне, в обстановке, крупный план фактуры, живая сцена с человеком. Диван на всех четырёх узнаваемо ТОТ ЖЕ — форма, цвет и фактура ткани сохранены, а это главное: похожий диван в карточке товара замечает не клиент, а его покупатель. Расход 19 ₽ (4 × 4,75), с клиента 100 ₽ — списание сошлось. ДВА ЖИВЫХ ПРОГОНА ДО ЭТОГО ПРОВАЛИЛИСЬ И НАШЛИ ДВЕ ПОЛОМКИ. (1) Провайдеру уходил ПУТЬ в нашем хранилище вместо адреса — Kie отвечал «Your media file is unavailable», четыре кадра подряд в отказ. Денег это не стоило (задача не создаётся), но искалось бы долго: теперь `нарисовать` сама проверяет образцы и называет ошибку прямо, закреплено юнит-тестом. Ссылку берём заново на каждый кадр: подпись живёт 15 минут, а комплект рисуется минут восемь — одной ссылки хватало бы впритык. (2) СЕРЬЁЗНЕЕ: у всех четырёх кадров кириллические роли («каталог», «обстановка», «фактура», «в деле») схлопывались в ОДНО имя файла — очистка до латиницы не оставляла ничего, и все получали общее «file». Второй кадр падал с «resource already exists», два оплаченных кадра пропадали. Починено в двух местах: ролям фотосессии заданы явные имена, а для любой будущей русской роли без перевода вместо общего «file» берётся короткий отпечаток самой роли — новая роль больше не уронит работу молча. Пять юнит-проверок в гейте.
feature
Студия: нарезка рилсов
Длинное видео на короткие вертикальные куски. Резать по смыслу, а не по секундам: PySceneDetect на границы склеек уже разбирался в задачах Режиссёра.
✓ ОТМЕНЕНО решением владельца: он перестал оплачивать сторонний сервис, на котором держалась нарезка, и тот сейчас не работает. Просил убрать — говорил об этом дважды, и оба раза это терялось у меня между сессиями. Кода нарезки в ядре не было вовсе. Убрано обещание из ленты обновлений (v1.1.1, блок «Нарезка видео»): человек читал его и шёл искать функцию, которой нет. Записано в память, чтобы не всплывало снова.
feature
Студия: удаление фона
Прозрачный фон у картинки товара. Дешёвая утилита, но без неё фотосессия и брендирование упираются в чужой фон.
✓ ГОТОВО И ПРОВЕРЕНО. Recraft через Kie: расход 0,48 ₽, клиенту 5 ₽. Результат — PNG 1080×1350 с настоящей прозрачностью (pix_fmt rgba), проверил подложив цветной фон: края чистые. Имя модели выяснено бесплатно: в каталоге Kie она «recraft-remove-background», а API принимает «recraft/remove-background» и требует поле image. Ошибки имени и полей у Kie не создают задачу и не стоят денег — разведка обошлась в ноль. ВАЖНАЯ ДЕТАЛЬ: файл кладётся БЕЗ приведения к канону. Приведение переводит картинку в jpg, а jpg не умеет прозрачность — то есть ровно та работа, ради которой инструмент нужен, потерялась бы на последнем шаге, и заметил бы это клиент. Локальный rembg (как в рабочем сервисе) не ставил: это питон плюс модель на 180 МБ на общий сервер, где и так живут cz-core, rols-render и заводы, ради операции за полтинник копеек.
feature
Студия: брендирование
Логотип и палитра на готовую картинку. В ядре это УЖЕ работает внутри производства (src/watermark.ts кладёт логотип бренда на работу) — задача в том, чтобы дать это отдельной кнопкой для своих файлов, переиспользуя тот же код, а не написав второй.
✓ СДЕЛАНО и проверено картинкой. Инструмент «Логотип на картинку» в разделе Изображения: выбираешь свою картинку, получаешь её же с логотипом бренда в правом нижнем углу. Переиспользует src/watermark.ts — тот самый код, что работает внутри производства, второго не писал. ПО ДОРОГЕ СДЕЛАН ВЫБОР КАРТИНКИ-ОСНОВЫ, которого не было вовсе: варианты с исходником («Ролик из картинки») висели в списке с заглушкой «выбор появится следующим шагом» и выключенной кнопкой. Теперь галерея своих работ прямо в форме — заодно ожил и ролик из кадра. НАЙДЕНА И ИСПРАВЛЕНА НАСТОЯЩАЯ ПОЛОМКА ВОДЯНОГО ЗНАКА: в фильтре ffmpeg был перепутан порядок входов scale2ref, и вместо логотипа ужималась сама работа — кадр 1080×1350 превращался в 52×65 пикселей. ffmpeg отрабатывал успешно, файл получался валидный, ошибок ноль. Код не выполнялся с самого переноса: галка «водяной знак» выключена у всех брендов, то есть поломка ждала первого, кто её включит. Теперь наложение проверяет, что размер кадра не изменился, а в гейте стоит tools/watermark.check.ts — четыре проверки на настоящих картинках: размер, знак в правом нижнем углу, нетронутый остальной кадр, ширина знака в восьмую часть кадра. ЦЕНА 3 ₽ — на подтверждение: генерации здесь нет, наш расход ноль, но бесплатный заказ ядро не пропускает (резерв требует сумму больше нуля), а трогать денежное ядро ради одной кнопки я не стал.
feature
Студия: банк идей
Сохранённые промпты. Осторожно: в рабочем сервисе под этим именем лежит НАШ внутренний каталог на 144 записи с оценками, а клиентских записей три штуки с 15 февраля. Решить, что именно показываем клиенту — свои сохранённые или общую библиотеку удачных.
✓ СДЕЛАНО. Развилка «свои сохранённые или общая библиотека» решена так: и то, и другое в одном списке, свои сверху. Основание — данные рабочего сервиса: под именем «Банк идей» там лежал НАШ каталог на 144 описания (126 живых, все с русским текстом, категории, счётчик применений — сверху то, что брали по 90 раз), а клиентских записей было три штуки с 15 февраля. Ценность именно в общей библиотеке, её и перенёс; возможность сохранить своё добавил, иначе «банк» это просто чужие тексты. Перенесено 126 описаний вместе со счётчиком применений (tools/perenos-idey.ts, идемпотентный). Отключённые (18 штук) не брал — их выключали осознанно. Экран /studio/ideas: свои описания сверху, ниже общие с фильтром по категориям (Кино, Товар, От первого лица, Превращение, Клип, АСМР…). Длинные описания свёрнуты до трёх строк — в каталоге они по три-четыре абзаца, сотня таких карточек нечитаема. «Взять в работу» кладёт текст в черновик студии и отправляет на нужный экран: ничего не заказывает и не тратит денег, человек правит под себя и запускает сам. На экране генерации добавлена кнопка «Сохранить в банк идей» — без неё банк не наполняется. Миграция 0061: core.prompt_bank, brand_id NULL = общая. RLS: общие видят все, свои — только владелец бренда, вставлять можно только своё. Счётчик применений растёт ТОЛЬКО у общих — иначе порядок в общем списке накручивается своими записями. Проверка tools/ideas.check.ts в гейте, 12 проверок на границу между общим и своим. Гейт зелёный. ВИД ГЛАЗАМИ НЕ ПРОВЕРЕН: браузер отвалился посреди работы, страницы отвечают 307 на гостя (то есть не падают), но скриншот сделаю при первой возможности.
feature
Поддержка: один поток из Телеграма, МАКСа и кабинета
РЕШЕНО ВЛАДЕЛЬЦЕМ 19.08 (ответ на В-23): «надо чтобы тг работал и макс и чат поддержки как сейчас. пишут, мне приходит уведомление в админку и в чате Лия». То есть: три входа — Телеграм, МАКС, кабинет — сходятся в ОДНУ очередь обращений ядра. Ответ сотрудника из админки уходит обратно тем же каналом, которым пришёл вопрос. Уведомление о новом обращении: счётчик в админке плюс сообщение в рабочий чат, где Лия. Клиенту писем по-прежнему не шлём — у него ровно три повода получить от нас письмо. Сегодня в ядре готова кабинетная часть (раздел у клиента, очередь у нас, 21 проверка). Осталось: вебхук бота ТГ на ядро, вход из МАКСа, доставка ответа наружу, уведомление в чат. Основание: все 8 живых обращений рабочего сервиса — гостевые из Телеграма, у людей нет кабинета; это основной вход, а не запасной.
✓ СДЕЛАНО. Кабинетная поддержка работала и раньше, не хватало входа из мессенджеров: человек, у которого что-то не работает, пишет туда, где ему привычно, и такое сообщение оседало в чужой личке — в очереди его нет, ответить некому, клиент решает, что его игнорируют. Теперь один поток. Обращение из Телеграма или МАКСа заводит нитку в той же очереди, что и кабинетное; ответ поддержки уходит обратно в мессенджер; на первое сообщение человеку приходит подтверждение, что он попал куда надо. НАЙДЕНА ПОЛОМКА ПО ДОРОГЕ: очередь соединялась с людьми внутренним JOIN, а у обращения из мессенджера человека может не быть вовсе — он ещё не регистрировался. Такое обращение было бы принято, записано и невидимо: худший вид поломки, когда никто не узнаёт, что клиента потеряли. Соединение стало левым, в очереди видно канал и как человек подписан в мессенджере. Механика канально-независимая: миграция 0066 добавила нитке канал, обратный адрес и имя из мессенджера, сообщению — номер для защиты от повторной доставки (мессенджеры шлют повтор, если не получили подтверждения). Вебхук один на оба канала, отвечает 200 всегда: на любой другой ответ мессенджер шлёт обновление по кругу, а через сутки отключает доставку. Секрет в заголовке или параметре — иначе ручку найдут перебором. ПРОВЕРЕНО: вебхук принял сообщение, нитка завелась с каналом, именем и темой из первых слов. Семь проверок в vitest на то, что ломается молча: обращение видно в очереди, второе сообщение продолжает ту же переписку, повтор не задваивает, после закрытия заводится новая нитка. НУЖНО ОТ ВЛАДЕЛЬЦА, чтобы включить: токен бота поддержки в SUPPORT_TELEGRAM_TOKEN (или разрешение использовать публикационный Contetn_zavod_MyVision_pub_bot — смешивать роли я не стал), чат для оповещений в SUPPORT_ALERT_CHAT, секрет в SUPPORT_HOOK_SECRET. Пока переменных нет, канал спит, а кабинетная поддержка работает как прежде: обещать клиенту канал, которого нет, нельзя.
feature
Витрина: правки лендинга по замечаниям владельца
✓ СДЕЛАНО. Убраны счётчики работ и заводов — числа низкие и играли против нас. Шапка выровнена по центру (логотип у левого края и кнопки у правого расползались на широком экране). Мазок в финале стал белым: тёмный на чёрном пропадал, и фраза читалась как «Соцсети сами себя». Плашка «Скоро добавим больше» убрана. В витрине работ теперь все 15 роликов вместо шести. ЧИСЛА МОДЕЛЕЙ ИСПРАВЛЕНЫ ПО ФАКТУ КАТАЛОГА: было «4 модели видео» из реестра ядра, стало 18 видео и 19 картинок — из живого каталога студии, где 49 активных моделей. Заголовок студии переписан под оффер: «49 моделей ИИ в одном окне». УБРАНА ЛИШНЯЯ МЕХАНИКА: «Озвучка ElevenLabs» → просто «Живая озвучка», имя провайдера клиенту не показываем. У ИИ-аватаров убрано «запишете тридцать секунд» — у нас другая механика, аватар делается из одной фотографии.
feature
Витрина: страницы под нишу (селлеры, услуги, эксперты)
✓ СДЕЛАНО, /for/sellers, /for/services, /for/creators. Тексты перенесены из рабочего сервиса как есть — они писались под живые запросы этих людей и уже работают в поиске; придумывать заново значит терять проверенное. На каждой: свой герой, боль тремя строками, шесть форматов ниши, примеры работ именно этой ниши, четыре шага, подсказка какой тариф под задачу, вопросы ниши. Цены НЕ переносились: в самом файле прода стояло предупреждение «ЦЕНУ СЮДА НЕ ПИСАТЬ», появившееся после того, как лендинги показывали устаревшие тарифы. Берём из базы. Для поиска: свои title и description, один H1, canonical, микроразметка вопросов — по ней поисковик показывает ответы прямо в выдаче.
feature
Витрина: блог с переносом статей
✓ СДЕЛАНО: /blog, /blog/<статья>, /blog/category/<раздел>. Все 26 опубликованных статей читаются из той же базы — они уже собирают поиск, терять их нельзя. НАЙДЕНО ПРИ ПЕРЕНОСЕ: все 26 обложек БИТЫЕ. Ссылки ведут на tempfile.aiquickdraw.com — временное хранилище Kie, которое картинки удалило (проверено: 404 на каждой). То есть блог прода прямо сейчас показывает статьи с пустыми картинками. Обложки заменены типографскими карточками: всегда на месте, единообразны, ничего не стоят. ЕЩЁ: категории заведены (5 штук), но НИ ОДНОЙ статье не проставлены — блок разделов пуст. Раскидать 26 статей по разделам стоит: это структура сайта для поиска. SEO на статье: canonical, open graph с датой публикации, разметка Article и хлебных крошек, время чтения, счётчик просмотров продолжает счёт прода, ссылки на соседние статьи.
feature
Витрина: тарифы, обновления, карта сайта
✓ СДЕЛАНО. /tariffs — цены и содержимое из базы, разметка Product с предложениями, пять вопросов про оплату с разметкой FAQ. Отдельно объяснено то, что путают: подписка открывает разделы, работы списываются по факту. /updates — журнал обновлений перенесён целиком (10 версий), с разделением «стало удобнее» и «починили»: это разные новости, и человеку важно видеть, что поломки чинят. /sitemap.xml — собирается из живых данных: 33 адреса, статьи подтягиваются сами. /robots.txt — кабинет, админка и вход закрыты от обхода, витрина открыта. КОРЕНЬ ТЕПЕРЬ ПОКАЗЫВАЕТ ВИТРИНУ, а не переадресацию на вход: по переадресации робот индексировал форму входа. Вошедшего по-прежнему уводим в его бренд.
feature
Витрина: сквозная шапка, значок сайта, картинка ссылки
✓ СДЕЛАНО. Шапка и подвал вынесены в оболочку витрины: раньше на главной работала одна навигация, на блоге другая — это были два разных сайта под одним доменом. Теперь одна на всех страницах. ЗНАЧОК: был только SVG, и часть браузеров его не берёт. Добавлены favicon.ico (16/32/48), значок для телефона 180, значки 192 и 512 и манифест — сайт, сохранённый на домашний экран, получает имя и иконку. КАРТИНКА ССЫЛКИ: на рабочем сервисе в мете указана /og-image.png, а файла НЕТ — 404. То есть все репосты сервиса в Телеграм и ВК идут без картинки, с января. Здесь картинка нарисована и отдаётся; у статьи блога вместо неё встаёт её обложка.
feature
Витрина: правовые страницы, контакты, переадресации старых адресов
✓ СДЕЛАНО. /privacy, /terms, /offer сняты с ЖИВОГО рабочего сервиса дословно (tools/snyat-pravovye.py) — это документы, на которые ссылается договор, переписывать их нельзя. Оформление наше, текст их. /contacts — реквизиты ИП, ИНН, банк, адрес плюс разметка организации с ИНН и адресом: по ней ответные машины понимают, что за сайтом живая компания. ПЕРЕАДРЕСАЦИИ на день переключения: /pricing → /tariffs, /auth/login → /login, /auth/signup и /auth/register → регистрация, /contact-sales → /contacts. Именно 301, а не привычный для Next 308: 308 не описан в документации Яндекса, а он основной источник. Без этого всё, что накопил поиск по этим адресам, ушло бы в 404. НЕ ПЕРЕНОСИЛ: инструкции (/docs). Их экраны сняты со старого кабинета, а кабинет переделан — подсказка с чужой картинкой хуже, чем ссылка наружу. Записано в книгу переключения отдельным пунктом.
feature
ГЕО: сайт под поиск через ИИ
✓ СДЕЛАНО. Владелец уточнил: ГЕО — это не города, а поиск через ИИ. /llms.txt — выжимка для ответных машин: что за сервис, цены, тарифы, разделы, до 40 статей со ссылками. Собирается из живых данных, потому что выжимка, устаревшая на полгода, вреднее её отсутствия: машина процитирует её как факт. robots.txt переписан вручную: явно пускаем GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, Google-Extended, YandexAdditional и ещё восемь — разрешение не должно зависеть от того, как очередной робот трактует звёздочку. Там же строка со ссылкой на llms.txt. РАЗМЕТКА: Organization и SoftwareApplication на всех страницах витрины, у статьи добавлены дата правки, раздел, ключевые слова, число слов и время чтения, картинка. Карта сайта: 41 адрес против 9 на рабочем сервисе — у него в карте нет НИ ОДНОЙ статьи блога.
feature
Метрика: счётчик перенесён на витрину и вход
✓ СДЕЛАНО. Счётчика в ядре не было вовсе — на переключении статистика оборвалась бы: визиты, источники, поисковые запросы и цели начались бы с нуля. Взят тот же номер 106102698, история продолжится. СТОИТ НЕ ВЕЗДЕ: витрина и вход — там реклама и поиск. В кабинет намеренно не ставил: вебвизор пишет запись экрана, а на экране кабинета чужие материалы, брифы и переписка с поддержкой; отправлять это наружу ради цифры посещаемости не нужно. Номер берётся из переменной окружения; на стенде её нет, поэтому тестовые заходы не мешаются с боевой статистикой. Проверено с временно включённой переменной: скрипт грузится, window.ym — функция, на /admin счётчика нет. В книгу переключения добавлен пункт.
feature
Блог: пять новых статей и живые картинки
✓ СДЕЛАНО, лежат черновиками. Владелец решил, что тексты пишу сам, без модели — так и сделано. Пять разборов: цена ведения соцсетей по позициям (9 600 знаков), выбор модели для видео (7 900), почему ролики не досматривают (7 400), контент-план на месяц (8 400), ИИ-аватар (6 800). Жанр разбора, без «мы предлагаем», цены и лимиты — из живого каталога и тарифов, не выдуманные. КАРТИНКИ: 11 штук нарисовано (5 обложек, 5 в текст, одна перерисована), около 33 ₽. Лежат у НАС в public/img/blog — именно потому, что 26 старых обложек умерли во временном хранилище провайдера. Обложки старых 26 статей по решению владельца остаются типографскими. СМОТРЕТЬ И СОГЛАСОВАТЬ: /blog/proba — разводная страница, клик открывает статью в настоящем оформлении сайта. Публикация отдельным запуском после «да».
feature
Вид: починен мазок, знак в гармошке, обложки в карточках
✓ СДЕЛАНО. МАЗОК: владелец дважды писал, что фраза «не наполнят» не видна. Первый раз я покрасил подложку в белый — причина была не в цвете. Подложка рисуется псевдоэлементом с z-index:-1 и уезжала ПОД фон блока, на экране оставался тёмный текст на тёмном. Дал мазку свой слой — теперь читается везде. Заодно отступ, чтобы подложка не наезжала на предыдущее слово. ГАРМОШКА: у раскрытого вопроса стоял плюс, у закрытых крестик — значок читался как «закрыть» там, где закрывать нечего. Перевёрнуто. ОБЛОЖКИ: карточки блога и шапка статьи показывают картинку, если она живая; мёртвые ссылки по-прежнему отсекаются. Ворота: 58 из 58. Проверку корня обновил — она требовала переадресации на вход, а корень теперь показывает витрину.
feature
Логотип: один знак на весь сервис
✓ СДЕЛАНО. Владелец: «шапки везде разные, логотипа где-то вообще нет, где-то просто цифра 24». Так и было: витрина рисовала знак словами, кабинет и студия лаймовым квадратом с цифрой, админка тоже цифрой, вход цифрой и словом врозь. Логотип у бренда при этом есть и лежит на рабочем сервисе. ВЗЯЛ ИСХОДНИКИ С ПРОДА и собрал два лок-апа: строкой для шапок (в строке высотой 26 пикселей слово в две строки нечитаемо) и блоком для подвала, входа и картинки ссылки. Плюс вариант с тёмными чернилами: в светлой теме кабинета от логотипа оставались только лаймовые буквы, «24 контент» пропадал. Теперь знак живёт в одной части `знак.tsx` и правится в одном месте. ЗНАЧОК САЙТА тоже из фирменного знака, а не набранная шрифтом цифра: ico 16/32/48, иконка телефона 180, 192 и 512. Картинка ссылки перерисована с логотипом.
feature
Шапка закреплена и работает на телефоне
✓ СДЕЛАНО. Шапка была на месте и уезжала со страницей: на длинной статье человек оставался без навигации. Теперь закреплена сверху, с подложкой и размытием. НА ТЕЛЕФОНЕ РАНЬШЕ МЕНЮ ПРОСТО ПРЯТАЛОСЬ правилом display:none, и попасть на страницы можно было только через подвал. Сделал кнопку и выпадающий список из того же набора разделов. ПОЙМАЛ ПРИ ПРОВЕРКЕ: правило не срабатывало, потому что старый селектор с элементом (.shapka nav) весит больше, чем селектор по классу. На телефоне меню оставалось строкой и уезжало за край экрана. Видно только глазами, кодом не поймать. Якорям добавлен отступ: переход на /lp#chto ставил заголовок раздела ровно под шапку.
feature
Статьи: убрано то, что выдаёт машинный текст
✓ СДЕЛАНО. Владелец: «убери палево типа количества знаков, длинные тире и прочее». Со страницы согласования убран счётчик знаков. Все пять статей переписаны целиком: было 151 длинное тире на пять текстов, стало ноль. Заодно убраны машинные приёмы: одинаковые зачины разделов «Что происходит / Что делать» в каждом пункте, слово «Итого», ровные тройки перечислений. Объём сохранён: от 6,7 до 9,5 тысяч знаков. Тире убрано и из текстов витрины, которые писал я. В перенесённых с рабочего сервиса (страницы под нишу, журнал обновлений, правовые документы) не трогал ничего: это чужой готовый текст. ЦЕНЫ ПОПРАВЛЕНЫ по словам владельца: Veo 3.1 Лайт 50 ₽, Veo 3.1 Фаст 60 ₽.
feature
Почта info@24contentzavod.ru везде
✓ СДЕЛАНО. В правовых документах, снятых с рабочего сервиса, стояла почта другого домена (Info@myvision.su) в десяти местах. Заменена. Правка внесена и в снимающий скрипт, иначе следующее снятие вернуло бы старый адрес обратно. Почта добавлена в подвал отдельной строкой вместе с годом и Телеграмом: бухгалтерия клиента ищет реквизиты именно там, и если их нет, следующий вопрос приходит в поддержку.
feature
Приёмка глазами: что нашлось и починено
✓ ПРОВЕРЕНО по всем страницам витрины и кабинета, на широком экране и на телефоне. НАЙДЕНО И ПОЧИНЕНО: (1) меню на телефоне уезжало за экран, см. задачу про шапку; (2) в светлой теме кабинета логотип наполовину пропадал; (3) на витрине показывались оба варианта знака сразу — общее правило для картинок весило больше; (4) в студии в «последних работах» висела сломанная картинка: карточка рисовала <img> на любой файл, а у проверки на вирусность результат это отчёт report.json — теперь плитка с подписью; (5) плашка «черновик» пряталась под закреплённой шапкой, переехала вниз экрана; (6) страницы ниш и статьи блога потеряли картинку ссылки: свой блок openGraph перебивает корневой целиком, картинку пришлось указать явно. Битых ссылок нет: обошёл 68 адресов со всех страниц витрины, все отвечают. Ворота 58 из 58.
feature
Блог: пять статей согласованы и опубликованы
✓ СДЕЛАНО. Владелец посмотрел на /blog/proba и сказал «статьи ок». Опубликованы все пять: цена ведения соцсетей, выбор модели для видео, почему не досматривают, контент-план, ИИ-аватар. Стоят первыми в блоге, с обложками. Разделы пересчитались: SMM 13, Видеоконтент 11, AI и автоматизация 4, Кейсы 3. В карте сайта стало 46 адресов вместо 41, все пять попали и в карту, и в выжимку для ИИ-поиска. У статьи с обложкой картинка ссылки теперь её собственная, а не общая заставка.
feature
Корень: главная открывается и тому, кто вошёл
✓ ПОЧИНЕНО. Владелец: «не могу перейти на главную, кидает в лк сразу». Корень уводил вошедшего в его бренд, и получалось, что из кабинета нельзя посмотреть собственный сайт: клик по логотипу или по пункту «Главная» возвращал обратно. Теперь витрина показывается всем. Вошедшего узнаём в оболочке витрины и вместо кнопок «Войти» и «Начать» показываем «В кабинет»: один бренд ведёт прямо в него, несколько на разводную. Ошибку при опросе базы глушим намеренно, иначе сайт падал бы целиком из-за кнопки в углу. В смоук добавлена проверка: вошедший получает с корня 200 и видит «В кабинет». Раньше такой проверки не было вовсе, поэтому поломку нашёл владелец, а не ворота. Ворота: 58 из 58.
feature
Обход всех страниц: инструмент и находки
✓ СДЕЛАНО. Написан tools/obhod.ts: заводит свою организацию с данными, проходит 68 адресов (витрина, кабинет, студия, админка) и смотрит три вещи — код ответа и время, следы поломки в вёрстке (Application error, NaN ₽, Invalid Date), и экраны без единой ссылки и кнопки. Последнее важно для новичка: технически исправная страница, с которой некуда идти, это тупик. Прогон чистый: 500-х нет, самая медленная страница 300 мс.
feature
ПОЛОМКА: вход по приглашению не работал при вставке ссылки целиком
✓ ПОЧИНЕНО. Под полем написано «можно вставить весь адрес, лишнее уберём сами». Разбор делал split(«/»).pop() и потом split(«?»)[0], то есть от ссылки /login/invite?token=ABC оставалось слово «invite». Вход не проходил ровно у того, кто делал как написано; работал только случай с голым ключом. Разбор вынесен в функцию ключИзСсылки и покрыт проверкой на девять видов вставки (полный адрес, ключ в пути, голый ключ, хвост с меткой рассылки). Плюс ключ из адреса теперь подставляется сам: человек пришёл по ссылке, и просить вставить её снова незачем.
feature
ПОЛОМКА: посты с фото товара не собирались вообще
✓ ПОЧИНЕНО. Выпуск из плана вставал на шаге «картинка» и молча повторял попытку раз в пятнадцать минут. Причина: линии поста, карусели и аватара кладут в образцы путь в нашем хранилище (фото товара, логотип), а провайдер скачивает картинку сам и путь открыть не может. Стояла проверка, которая называла это ошибкой вызывающего. Исправлено в одном месте: подпись пути теперь делает сам модуль рисования, а не каждая линия по-своему. Проверено живьём — выпуск собрался за 162 секунды, картинка с русским текстом и логотипом на месте. Тест kie.test.ts переписан под новое поведение.
feature
ПОЛОМКА: три кнопки плана отвечали «NEXT_REDIRECT»
✓ ПОЧИНЕНО. «Расширить план», «Добавить контент» и «Придумать идеи» показывали ошибку, хотя работа делалась: redirect() в Next бросает своё исключение, а он стоял внутри try и попадал в catch. Переадресация вынесена наружу во всех трёх. Чтобы не повторилось, проверка tools/server-actions.check.ts теперь ловит и этот случай наравне с замкнутой функцией: обе ошибки одинаково коварны — страница открывается, типы довольны, ломается только отправка формы.
feature
Надёжность: после перезапуска воркера работы больше не ждут полчаса
✓ СДЕЛАНО. Заказ, застигнутый перезапуском воркера, оставался заблокированным в очереди: замки снимались только у задач старше тридцати минут. При каждом выкате это стоило клиенту получаса ожидания с удержанными деньгами. Теперь при СТАРТЕ порог минута (новый процесс ещё не взял ни одной задачи, значит любой замок чужой и мёртвый), а по кругу работает прежний порог. Плюс каждая неудачная попытка шага теперь пишет в лог причину и решение лестницы: именно из-за молчания поломку с образцами искали двадцать минут вместо одной.
feature
Студия: холст и пульт вместо анкеты
✓ СДЕЛАНО по образцу, который показал владелец. Наверху холст: пока идёт сборка — шаги и спокойная волна, готово — картинка или ролик крупно, с кнопками «Скачать» и «Открыть работу». Внизу по центру пульт: виды работ, поле описания, настройки (кадр, разрешение, длительность, версия, основа) выпадают вверх, справа кнопка с ценой. Помощники «Улучшить описание» и «В банк идей» переехали в пульт: раньше это были отдельные карточки посреди страницы, и человек не связывал их с полем ввода. Заодно починена кириллица в адресе студии: переадресация после отправки уезжает заголовком ответа, и русское «/studio/картинка» роняло страницу с ERR_INVALID_CHAR.
feature
Интерфейс глазами новичка: девять правок
✓ СДЕЛАНО. (1) Лента работ показывала два чёрных прямоугольника в пол-экрана: ролики грузились, но браузер не рисует кадр до воспроизведения. Просим кадр на 0.1 секунде, плитки уменьшены. (2) «Готово за неделю: 0, −2 к прошлой неделе» читалось как «минус две работы» — теперь «неделей раньше было 2». (3) Раздел «Входящие» объяснял клиенту про перенос на новое ядро и «настоящий объём работы» — наша кухня на клиентском экране. (4) Календарь назывался «План», ровно как соседний пункт меню. (5) Завод без расписания молча ничего не выпускал: теперь об этом сказано и на карточке, и на самом заводе, с кнопкой. (6) «Файл не открылся» на готовой проверке вирусности: это не поломка, а разбор вместо картинки. (7) В списке людей первым шёл «без почты · без почты». (8) Заголовком работы стояло задание сценаристу на три строки вместо короткого названия. (9) Кнопки тарифов вели вошедшего клиента на регистрацию.
feature
Формы: свои флажки и списки вместо системных
✓ СДЕЛАНО. Системные квадратики Windows посреди тёмного кабинета выглядели как чужая вставка. Покрашены флажки, переключатели, выпадающие списки и кнопка выбора файла — в общем оформлении, значит сразу во всех формах сервиса. Механику не трогали: это по-прежнему обычные input и select, работают с клавиатурой и без скриптов. Плюс подсказка «где взять ключ» на подключении соцсети: раньше экран просил ключ доступа и не говорил, где его берут, — для человека без опыта с ботами это тупик.
feature
Телефон: горизонтальная прокрутка убрана
✓ СДЕЛАНО. На ширине 390 px дашборд и студия разъезжались вбок: длинная тема работы растягивала карточку продукта, а строка истории студии не переносилась. Проверены восемнадцать экранов кабинета и студии — прокрутки вбок не осталось нигде. Баланс в шапке перестал ломаться на две строки.
feature
ПОЛОМКА: холст студии не показывал готовую картинку
✓ ПОЧИНЕНО. Библиотека работ отрезала список ДО фильтра по виду: SQL брал последнюю работу любого вида, а «только картинки» применялось уже в коде, поверх этой одной строки. Если последней оказалась озвучка или разбор, ответ приходил пустой. Холст просил одну последнюю картинку и получал пустоту при десяти готовых картинках. Тем же страдал выбор основы для ролика: список молча терял картинки. Теперь при заданном виде берём из базы с запасом и обрезаем после фильтра.
feature
Светлая тема действовала только в кабинете
✓ ПОЧИНЕНО. Человек включал светлую тему, кабинет светлел, а студия, админка и вход оставались чёрными: тему применял сам переключатель, а живёт он в боковом меню кабинета. Нет меню — некому поставить признак. Теперь выбор применяется на уровне всего документа и до первой отрисовки, иначе светлая страница успевает моргнуть чёрным. Заодно студия и админка переведены с жёстких цветов (#0a0a0f, #8b93a7) на общие переменные темы: без этого они не посветлели бы всё равно.
feature
Обходчик страниц доведён до чистого прогона
✓ СДЕЛАНО. tools/obhod.ts проходит 73 адреса и заканчивается «С бедой: 0». Убраны ложные срабатывания: виды студии названы по-русски и адрес надо кодировать, служебная страница здоровья живёт без ссылок по назначению, а «страницы нет» в ответ на админку — правильное поведение, существование служебного раздела чужому не подтверждаем. Инструментом можно пользоваться после каждого крупного изменения: он показывает коды, время, следы поломки в вёрстке и экраны, с которых некуда идти.
feature
План: к идее привязывается фото товара
✓ СДЕЛАНО по образцу рабочего сервиса: под темой идеи стоит строка «фото не привязано · выбрать», выбор идёт из товаров бренда с фотографиями, без привязки сервис решает сам. Поле в базе было заведено миграцией 0062, теперь появился экран. Номер товара из формы не принимаем на веру: сервер ищет его среди товаров бренда. Проверено сквозняком на живых деньгах: привязал «Диван-кровать Осло», запустил выпуск, работа собралась за 173 секунды — на картинке этот диван, с русским текстом и логотипом бренда.
feature
Несколько картинок за раз: студия, СММ-Центр, линии
✓ СДЕЛАНО по задаче владельца: «кидаю 2-3 картинки и говорю — первая человек, со второй возьми одежду, с третьей улицу». НАЙДЕНО: вызов провайдера это умел (поле принимает список), но по всей цепочке был протянут ровно один образец. Студия хранила исходник строкой, линия оборачивала его в массив из одного. СДЕЛАНО: заказ и задание держат список, порядок сохраняется — человек описывает картинки номерами, и перестановка ломает задание. В пульте студии появилась кнопка «+» для своих файлов (до восьми) и множественный выбор из готовых работ с номерами на плитках; под полем ввода строка «1. … 2. … 3. …», чтобы было видно, на что ссылаться. В СММ-Центре то же самое для поста и карусели: свои картинки уходят тем же отправлением и встают первыми, перед образцом стиля и фото товара, чтобы не сдвигать нумерацию. Предел образцов теперь свой у каждой модели: gpt-image-2 держит 14, нанобанана 8. Раньше резали по 16 для всех, лишние модель отбрасывает молча. ПРОВЕРЕНО ЖИВЬЁМ: три картинки из библиотеки + описание «диван с первой, комнату со второй, растение с третьей» — провайдеру ушли все три (в записи запроса «образцов: 3»), кадр собран из трёх источников. Первый прогон показал 1 образец: воркер работал по старому коду, после перезапуска стало 3.
feature
Сценарий у идеи: пишет сервис, правит человек
✓ СДЕЛАНО по развилке владельца. Ручная работа и работа с ИИ уже исходили из того, что написал человек. Третий случай — автоматика завода — не работал: сценарий рождался внутри работы, человек видел его только в готовом ролике, и «докрутить сценарий» существовало на словах. Миграция 0063: у идеи появились сценарий и отметка, кто его писал. В плане: кнопка «Написать сценарий сервисом» (по теме, крючку и призыву, меньше рубля) и поле для правки руками. При запуске выпуска сценарий уезжает в задание вместе с крючком, призывом и привязанным фото, а линия видео берёт готовое вместо того, чтобы писать своё поверх согласованного. Пустой сценарий по-прежнему значит «придумай сам». Две правки по ходу: модель возвращала JSON внутри строки (человек видел фигурные скобки вместо сценария) и обрывала ответ на полуслове из-за лимита токенов. Первое разворачиваем в текст, второе вылечено лимитом и просьбой уложиться в тысячу знаков. ПРОВЕРЕНО ЖИВЬЁМ: сценарий написан («В кадре: … Текст: …»), доехал до задания работы, работа собралась.
feature
Veo 3.1 Быстрый: цена в ядре уже 60 ₽
✓ ПРОВЕРЕНО, менять не пришлось. В реестре ядра стоит 60 ₽ при себестоимости около 28 ₽ за ролик (0.30 доллара за восемь секунд 720p). Владелец назвал те же 60 ₽. Расхождение остаётся с каталогом рабочего сервиса: там та же модель стоит 90 ₽. Боевую таблицу не трогал — это цена для живых клиентов прода, и менять её надо отдельным решением, а не по ходу работы над ядром.
feature
🔴 Kie: два имени моделей были неверными, проверка переписана
Владелец спросил: «ты апи брал или додумывал, потом пол студии не встанет?». Проверил каждое имя пробой к Kie.
✓ Найдено три поломки. 1) «kling-3-0» и «seedance-2-0-mini» — это адреса страниц каталога, а не имена моделей: настоящие kling-3.0/video и bytedance/seedance-2-mini. Обе стояли в выборе движков Студии. 2) Проверка имён сверяла ключ с адресом страницы, поэтому пропускала такое по построению — переписана на пробу createTask с пустым вводом (ошибка имени и ошибка полей у Kie бесплатны). 3) Съёмка слала в поле model ключ реестра и всегда клала кадры в first_frame_url: выбор движка был декоративным, упал бы любой, кроме Wan 2.7. Теперь имя, поля кадров и предел длительности берутся из реестра. В гейте: «Имена 36 моделей Kie проверены пробой».
fix
↑ Цена ролика зависит от движка и длительности
Съёмка стоила фиксированные 240 ₽ при любом движке. При свободном выборе это дыра в марже: Seedance 2.5 обходится нам вшестеро дороже мини. Владелец: «в зависимости от модели меняется ценник, в чём проблема» — как в рабочем сервисе.
✓ Цена = шаги линии кроме съёмки + движок × секунды. Один расчёт на всё: то, что на кнопке, то и резервируется с кошелька. Шаги заказа получают точные цены, а не пропорцию (пропорция удешевляла озвучку из-за выбора дешёвого движка). Проверено живьём: Wan 2.7 16 сек — 224 ₽, Seedance 2.5 16 сек — 416 ₽, 30 сек — 752 ₽. Умолчание видео 272 → 224 ₽. Проверки переписаны на правило, а не на число.
feature
↑ Экран «Создать контент» переведён на пульт
Владелец: «юай везде приходит к единому стандарту — окно работы, внизу по центру чат, в нём спрятаны доп функции вроде выбора моделей». Экран был анкетой сверху вниз. Варианты согласованы до кода: выбран пульт как в Студии.
✓ Холст сверху показывает, из чего соберётся ролик (куски и их длина), композер снизу: строка задачи, пилюли (файлы, подробнее, товар, образец, движок), ползунок секунд, цена на кнопке пуска. Всё пересчитывается на месте. Цена в браузере не считается: сервер присылает готовую таблицу «движок × секунды». Починены два бага по замечанию владельца: пульт наезжал на боковое меню (центрировался по окну, а содержимое кабинета сдвинуто на ширину меню) и системная полоса прокрутки в списке моделей.
feature
Veo Omni и Kling Omni: детальное управление роликом
ИДЕЯ ВЛАДЕЛЬЦА, в работу не брать без разговора: «отдельно надо реализовать возможно вео омни и клинг омни, там где можно очень детально управлять роликом, но это надо с тобой обсудить». ЧТО УЖЕ ИЗВЕСТНО. У Kling 3.0 есть многокадровый режим: несколько сцен, у каждой свой промпт и своя длительность (1-12 сек), плюс ссылки на элементы через @имя — картинки, видео, аудио. Это ровно то детальное управление, о котором речь, и оно ложится на режиссёрский лист из С12: лист уже описывает эпизоды с ролями и таймингами. Семейство в документации: kling/v3-omni-image-to-video, v3-omni-text-to-video, v3-omni-reference-to-video, v3-omni-transformation, плюс kling/motion-control-v3. ЧТО РЕШИТЬ: делаем ли отдельным инструментом Студии или это движок для Режиссёра.
idea
В журнале cz-core после выката: «Functions cannot be passed directly to Client Components» (2 раза за обход страниц)
~/.pm2/logs/cz-core-error.log 21.09 06:47 UTC, два стека из app-page.runtime (Timeout._onTimeout): массив с функцией [function filter, …] и [..., function map, ...] уходит в клиентский компонент. Страницы при этом отдают 200 (обход 86 страниц без беды), поэтому не видно, какая. Найти: пройти страницы под сотрудником с tail -f журнала. С правкой тревоги поддержки не связано: она не трогает клиентские компоненты.
✓ Та же причина, что в С11 «поймать страницу»: «Сети» бренда и завода. Починено 02.10, коммит 69e3672.
bug
готовS20 · С14 · Заводы: настройки по полкам3/3

Цель: Разложить настройки конвейера по страницам, страницу завода сделать пультом

Настройки завода: шесть отдельных страниц вместо двух длинных
✓ СДЕЛАНО. Было: все настройки конвейера на двух страницах, «о чём завод» шестью карточками подряд, плюс форма расписания посреди страницы завода. Половина настроек экрана не имела вовсе. Стало: раздел settings с шестью страницами и своим меню — расписание и форматы, о чём завод, голос и ограничения, рубрики, как выглядит, соцсети. ГЛАВНОЕ ПРИ РАЗРЕЗАНИИ: добавил частичное сохранение позиционирования. Прежняя функция писала документ целиком, и страница «Голос» сохранила бы тон, обнулив тему, аудиторию и рубрики: их полей в её форме нет. Ошибка тихая, вылезла бы через неделю странными работами. Старые адреса /about и /video ведут на новые места, а не в «не найдено».
feature
Страница завода стала пультом
✓ СДЕЛАНО. Владелец: «сейчас контент-заводы выглядят нерабочими». Причина была в том, что на одной странице лежали и состояние, и настройки: форма расписания занимала два экрана, а режим и план терялись между ними. Теперь на пульте только состояние и действия: сводка «что выпускает» (форматы, дни, сколько в день, товары) с кнопкой «Изменить», режим со сменой под причину, плашка «Настройки завода» с шестью разделами, план с утверждением и лента произведённого.
feature
Соцсети завода: экран к механике, которая уже работала
✓ СДЕЛАНО. Владелец спросил, как трём заводам публиковать в три разных инстаграма. Ответ: механика в ядре была с самого начала (аккаунтыЗавода), уникальность в базе стоит по тройке бренд+сеть+аккаунт, то есть несколько аккаунтов одной сети на бренде допустимы. Не было только экрана. Сделал: страница «Соцсети» у завода со списком подключённых аккаунтов бренда и галочками. Ничего не отмечено — уходит во все подключённые. Отвалившийся аккаунт помечен прямо в строке.
feature
готовS21 · С15 · Контент-план как продукт6/6

Цель: Темы придумывает сервис, состояния видны, выпуск можно запустить кнопкой

Темы для плана придумывает сервис
✓ СДЕЛАНО. На экране плана стояло двенадцать строк с рубриками и подписью «Задайте тему: планировщик раскладывает выпуски по дням, но не придумывает, о чём они». Запустить из такого плана было нечего. Появился модуль plan-ideas: берёт профиль завода той же функцией, что и производство (бренд плюс переопределения конвейера), показывает модели рубрику каждого пункта и уже занятые темы, заполняет до двенадцати пунктов за раз. Модель дешёвая, запрос стоит меньше рубля. ЛОВУШКА, КОТОРУЮ ПОЙМАЛ: первая версия искала пункты по заголовку «Тема не задана», а планировщик кладёт в заголовок название рубрики. Кнопка не появилась бы никогда. Признак пустого пункта теперь по идее, а не по заголовку. Проверка tools/plan-ideas.check.ts: модель подменена заглушкой, гейт не платит за прогоны. 11 проверок, включая «пункт с работой не переписан» и «пропущенный не переписан».
feature
Кнопка «Сделать сейчас» для пункта плана
✓ СДЕЛАНО. До неё режим «только по кнопке» означал «никак»: план есть, деньги есть, а произвести выпуск из плана было нельзя — автозапуск в ручном режиме молчит по определению, и другого пути не существовало. Функция запуститьПункт: день не проверяет (пора решил человек), режим завода не смотрит (нажатие и есть разрешение), но утверждение плана требует — смета согласуется до трат. Пропущенный пункт не запускает. Если заказ не встал (не хватило денег), пункт освобождается, иначе он выглядел бы сделанным. На кнопке написана цена.
feature
План читается как календарь
✓ СДЕЛАНО. Было: список форм, у каждого выпуска поле темы, поле даты и две кнопки. Понять по нему, что происходит с лентой, было нельзя. Стало: пункты сгруппированы по дням («24 августа, пн»), у плана три состояния — Черновик, Утверждён, В работе, у пункта видно формат, цену, состояние работы и метку «Без темы». Сверху счётчик: сколько выпусков, на какую сумму, сколько без темы, сколько уже в работе.
feature
Контент-план пересобран по рабочему сервису, а не заново
✓ ПЕРЕДЕЛАНО ПОСЛЕ ЗАМЕЧАНИЯ. Владелец: «ты изобретаешь многое с нуля, убирая половину функционала, который мы выстрадали, и делаешь сложный юай». Он прав: первая версия свелась к списку полей. Взял прод как эталон и перенёс модель: у идеи своё состояние (предложена, одобрена, отклонена), цель, крючок, призыв, визуальный стиль, важность, фото. Миграция 0062. Перенесены действия: Одобрить, Отклонить, Переделать идею, Редактировать, Пропустить, Удалить, Сделать выпуск. Плюс вкладки Текущий план и Прошлые дни, фильтр по форматам со счётчиками, группировка по дням с пометками Сегодня и Завтра, счётчики Идей/Одобрено/Готово/Смета. Модель теперь придумывает не только тему, но и крючок с призывом и целью: просить их отдельно значит платить дважды за один контекст.
feature
Свои идеи и расширение плана
✓ СДЕЛАНО. «Своя идея»: человек ставит в план своё на конкретный день, с форматом, темой, крючком и призывом. Сразу считается одобренной: спрашивать согласия со своей же мыслью незачем. «Расширить план» повторяет прод и делится надвое: «Добавить дни» продлевает план по расписанию завода, «Добавить контент» уплотняет уже расписанные дни (было три выпуска в день, стало пять). Оба не работают на утверждённом плане: смета согласована, дописывать в неё значит менять сумму после согласия.
feature
Контент-план показывает результат: миниатюра и окно работы
✓ СДЕЛАНО по замечанию владельца: «нельзя посмотреть картинку, контент, подробности; в проде открывается модалка, где можно сразу опубликовать, переделать». Он прав: у идеи с готовой работой карточка показывала слово «Готово» и ссылку, и чтобы увидеть картинку, надо было уйти на другую страницу и вернуться. ПОЯВИЛОСЬ: миниатюра результата прямо в карточке и кнопка «Посмотреть». В окне — картинка или ролик крупно, текст поста целиком, сценарий, состояние и действия: Согласовать, На доработку с замечанием, Скачать, Открыть целиком. Окно нативное (dialog): закрывается по Esc и по клику мимо, без своей библиотеки. Данные собираются одним запросом на всю страницу: файлы, тексты, согласование. По запросу на идею это два десятка походов в базу при открытии плана. ПРОВЕРЕНО ЖИВЬЁМ: окно открывается у готовой и у провалившейся работы, «Согласовать» из окна отработало. У провалившейся вместо выдуманной кнопки повтора написано, что деньги вернулись: механики повтора одной работы в ядре нет, и рисовать кнопку под неё нельзя.
feature
готовS22 · Проверки по сроку1/1

Цель: Отложенные проверки: сработало ли то, что чинили

↑ Воркер ядра: не вернулся ли крах-цикл после починки адреса базы
Смотрим, не вернулся ли бесшумный цикл перезапусков после починки 25.08 (адрес базы заменён на имя контейнера, воркер ждёт базу, /etc/hosts синхронизируется кроном). КОМАНДЫ ПРОВЕРКИ: ssh cfru "pm2 list | grep -E 'cz-core|cz-worker'" — счётчик ↺ должен быть маленьким и не расти между двумя запусками, uptime — днями. ssh cfru "curl -s http://127.0.0.1:3160/api/health" — ожидаем {"живой":true,"база":true}. ssh cfru "getent hosts supabase-db" — имя должно резолвиться в текущий адрес контейнера; сверить с docker inspect supabase-db. ssh cfru "grep -c . /root/.pm2/logs/cz-worker-error.log" — лог не должен снова расти на десятки МБ. Если счётчик опять растёт — смотреть первую строку ошибки: адрес это или что-то новое. Разбор аварии: docs/ИНЦИДЕНТЫ.md, запись от 25.08.2026.
✓ Крах-цикл не вернулся. Приложение и сборщик online, счётчик перезапусков между выкатами не растёт сам по себе, /api/health отвечает {"живой":true,"база":true}, supabase-db резолвится в текущий адрес контейнера (172.19.0.10 и в getent, и в docker inspect). Лог ошибок сборщика 3.9 КБ вместо десятков мегабайт, и в нём только штатные остановки при выкатах, а не падения.
check
В РАБОТЕS23 · С16 · Верность входу35/36

Цель: Что человек приложил, то и легло в контент: фото доходит до модели в студии и во всех линиях, несколько фото учитываются, цена = кнопка, готовое не провал. Плюс сценарии не хуже старого.

↑ Общий узел: фото и кадры доходят до модели (src/kie.ts)
Подпись путей для картинок и роликов (и Veo), пара t2i→i2i и t2v→i2v при фото, отказ ОшибкаВхода у движка без пары до оплаты, «принесли/ушло» в записи задачи, пропорция роликам (ratio у wan 2.7), длительность в пределах модели. Реестр сверен с docs.kie.ai.
✓ Коммит 64057c4 в cz-core. Экзамен src/vhod-kie.test.ts 44/44 зелёный (до правки 40 красных: пустой ключ "" в теле запроса пойман). vitest целиком 263/263.
feature
↑ ВХ-1. Фото у любого картиночного движка студии уходит подписанной ссылкой в документированное поле или заказ отклоняется до отправки
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-2. Движок «только по описанию» с фото переходит на пару того же семейства
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-3. Фото больше предела модели: уходит предел, в записи «принесли N, ушло M»
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-4. Ролик из картинки (10 движков) и Проба Veo: кадр подписан и в своём поле
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-5. Ролик по описанию с фото снимается парой «из кадра»
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-6. Пропорция доходит до движка, по умолчанию 9:16
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ ВХ-7. Длительность в пределах модели, заказанная и снятая записаны
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-kie.test.ts зелёный (коммит 64057c4)
acceptance
↑ Студия: фото доходит, несколько фото учитываются, цена = кнопка, готовое не провал
Аудит 02.10: несколько фото молча урезаются; человек с товаром без двух слотов; фотосессия вытесняет товар; фотосессия/озвучка/разбор = провал с возвратом; цена кнопки ≠ списанной (z-image 3 ₽ → 17 ₽, seedance-2-5 30 с 720 ₽ → 60 ₽, аватар ниже себестоимости); выбор движка сбрасывается; правило внешности только у роликов; музыка и анимация продаются без линии; чужие файлы через библиотеку. Ветка vhod-studio.
✓ Ветка vhod-studio (e8aa36b) слита, + 04f8e6b (ловушка server action наВыбор.find, найдена гейтом), 0f6c87d. Экзамены: vitest 72/72 (до 61 красный), база 86/86 (до 17 красных). Выкачено на стенд 03.10. Независимый критик — в работе.
feature
↑ ВХ-С1. Инструмент принимает ровно столько фото, сколько объявляет; все принятые доходят
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-studio-vhod.test.ts, vhod-studio-liniya.test.ts, tools/vhod-studio.check.ts: девять фото → отказ «много_фото», работа не создана, деньги не тронуты. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С2. Человек с товаром: два места «товар» и «лицо», порядок товар→лицо
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ места «товар»/«лицо», без лица отказ «нужно_место», порядок товар→лицо в запросе. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С3. Фотосессия: выбранный товар первым, свой файл его не вытесняет
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ товар первым в каждом из 4 кадров фотосессии. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С4. Готовые фотосессия, озвучка и разбор засчитываются выполненными
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ каждая из 12 веток линии студии: фотосессия/озвучка/разбор = «успех», возврат 0; ролик завода с одной озвучкой по-прежнему «провал». Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С5. Списано ровно столько, сколько на кнопке; аватар посекундно
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ z-image 3 ₽, seedance 2.5 30 с 720 ₽, Клинг 30 с → 12 с 144 ₽, 1K 30 ₽, аватар посекундно: одинаково на кнопке, в заказе, резерве и списании. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С6. Выбор движка, секунд и разрешения не сбрасывается; 1K/2K доходит
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ движок/секунды/кадр сохраняются после «Улучшить», «В банк», ошибки, «Повторить»; resolution 1K уходит в запросе. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С7. Правило внешности у всех инструментов с людьми, по факту ушедшего фото
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ правило внешности у быстрой картинки, картинки с текстом, человека с товаром, кадра «в деле»; «человек с фото» только когда фото ушло. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С8. Музыка и перенос движений не продаются, пока не сделаны
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ музыка и анимация: отказ «не_продаётся» без резерва; старая ссылка ведёт в студию (0f6c87d). Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-С9. Файл из библиотеки чужого бренда отклоняется
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ чужой бренд, обход «..», внешняя ссылка, чужое наследие → отказ «чужой_файл» до подписи. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ Линии завода: фото доходит до каждой линии, план несёт крючок, сценарий и фото
Аудит 02.10: аватар не читает фото человека и товара, призыв не звучит; Ролс не читает свои фото и сдвигает кадры; карусель объявляет товар на слайдах 2–8, а не передаёт; видео режет фото до 4 молча; «Образец» с экрана никуда не идёт; автозапуск плана теряет крючок/сценарий/фото и запускает пропущенные; товар без фото получает фото компании; пост берёт не тот логотип. Ветка vhod-linii.
✓ Ветка vhod-linii (ef45194) слита, + bbd8c6f (формат без линии закрыт на уровне заказа). Экзамены красные на старом коде → 420/420 vitest, vhod-linii 15/15. Выкачено на стенд 03.10. Независимый критик — в работе.
feature
↑ ВХ-Л0. Картинок в промте объявлено столько, сколько передано, у каждой роль
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-obshchee.test.ts 25/25 (5 линий × 5 наборов): картинок в промте = переданных, у каждой роль. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л1. Аватар: фото человека = лицо ведущего, товар уходит, призыв звучит
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-avatar.test.ts 9/9: фото = лицо, товар образцом 2, призыв озвучен последним, «Кто в кадре» в описании. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л2. Ролс: свои фото в кадрах, выпавший кадр не сдвигает сцены
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-rols.test.ts 4/4: свои фото по кадрам, выпавший кадр не сдвигает сцены и тайминги. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л3. Карусель: товар на слайдах, где объявлен; несколько своих фото по слайдам
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-carousel.test.ts 7/7: товар на слайдах, где объявлен; свои фото по Р-16; водяной знак. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л4. Видео: роли картинок в промте кадра, режиссёр видит товар, «ушло N из M»
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-video.test.ts 9/9: роли картинок в промте кадра, режиссёр видит товар, «ушло N из M». Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л5. Экран создания: образец стиля уходит, предел файлов виден, формат без линии не продаётся
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-ekran.test.ts 10/10 + tools/format-bez-linii.check.ts 5/5: образец стиля уходит, предел виден, «История» не продаётся ни одной дорогой. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л6. Автозапуск плана = кнопка: крючок, сценарий, призыв, фото; пропущенные не идут
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ tools/vhod-linii.check.ts 15/15: автозапуск = кнопка (крючок, сценарий, призыв, фото), пропущенные и отклонённые не запускаются. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л7. Товар без фото не получает фото компании
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-katalog.test.ts: товар без фото не получает фото компании. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ ВХ-Л8. Пост: логотип по назначению; пост и карусель следуют плану
docs/ACCEPTANCE.md, раздел «Верность входу»
✓ src/vhod-linii-post.test.ts: логотип «на картинки» по назначению, крючок и сценарий плана у всех агентов. Красный на старом коде → зелёный; выкачено на стенд 03.10 (master 0f6c87d), гейт зелёный.
acceptance
↑ Старый сервис (прод клиентов): аудит того же класса — фото не доходит
Только чтение. Итог — список дыр, что задевает живых клиентов, и план правок; правки на прод только по «да».
feature
🔴 Сценарии ядра не хуже старого (С6): сейчас 5.30 против 6.10
Платная слепая сверка 02.10 (tools/sravnenie-scenariev.ts, 10 роликов, судья sonnet-4-6, ~18 ₽): ядро лучше в 3, старый в 6, равны 1. Судья: у ядра «режиссёрская заявка без слов, пользы и призыва». Разбор причин идёт.
✓ Ветка vhod-scenarii (5699308, 5951cca): сценарист = промт прода v13 из agent_configs (перенесён в код, гейт сверяет хэш), gpt-4o вместо молчаливого mini, озвучка читает реплики звукорежиссёра, стоп-слова на репликах, перенос позиционирования 10 заводов. Экзамены: src/scenarii-prod.test.ts 10/10 (до 8 красных), tools/pozicionirovanie-zavodov.check.ts 108/108 (до 10 красных). Повторная слепая сверка 10 роликов: ядро 5.60, прод 5.30 (ядро лучше 6, прод 3). ~55 ₽ за обе сверки. Выкат на стенд — после слияния веток.
feature
↑ Профиль бренда приводится к схеме, а не валит заказ
356 из 578 перенесённых брендов не проходили схему (339 пустых, 17 с длинными полями, среди них Dandelion и murph.) — каждый заказ падал на ZodError.
✓ Коммит 0eac691, выкачено на стенд (гейт зелёный). На всех 578 брендах: приведено 356, к пустому 0. tools/profil-privedenie.check.ts 12/12 в гейте.
feature
🔴 ОТК студии: подпись файлов не выходит из пути, края аватара, «Повторить», цены плиток
Критик нашёл: путь с %2e%2e / «\» проходил проверку своей папки, а служебный ключ подписывал чужой файл; «Повторить» другого вида; аватар без слов и с названием инструмента вместо текста; речь длиннее минуты в платное оживление; плитки «от 24 ₽/сек».
✓ Коммит ff05370, выкачено на стенд 03.10, гейт зелёный. Экзамены: src/put-podpis.test.ts 14 красных → 19/19; studio-krai 5/5; studio-avatar-minuta 2/2. vitest 446/446. В базе путей с такими символами 0 из 2056.
feature
↑ ОТК линий: двойной запуск пункта, образец чужого бренда, сценарий плана у видео, крючок, призыв аватара, «История» в плане
Критик: двойной клик «Запустить» = две работы и два списания; «Образец» с экрана подписывается без проверки владельца; сценарий плана JSON-строкой или короче 100 знаков роняет видео в автозапуске; крючок плана может не встать на обложку при записи «встал»; аватар говорит зашитое «Забирай на маркетплейсе»; «История» в плане даёт ложную тревогу о балансе; фото компании объявляются товаром; логотип не по назначению. Ветка vhod-krai.
✓ Ветка vhod-krai (a6d7133) слита (e5d1d7c), выкачено на стенд 03.10, гейт зелёный. Двойной запуск пункта: замок строки + условное закрепление + барьер в заказе — две кнопки разом и кнопка с автозапуском дают 1 работу, 1 заказ, 1 резерв (tools/krai-linii.check.ts 38/38, 5 прогонов подряд). Образец чужого бренда отклоняется; сценарий плана у видео разворачивается, короткий не роняет работу; крючок плана на обложке после всех кругов; призыв аватара дословно, без зашитого маркетплейса; История не в плане, без ложных тревог; фото компании не «точный товар»; логотип по назначению. vitest 496/496. Остатки упавших прогонов проверки (4 проверочные орг.) убраны — они уронили первую попытку выката сверкой переноса.
feature
🔴 Чужой объект по id из формы: граница своего во всех действиях; админка только сотрудникам
Приложение ходит в базу с BYPASSRLS — принадлежность проверял только код, а действия страниц брали id плана/пункта/завода/работы/товара/аккаунта из формы без сверки с брендом. Красный прогон: «Сделать сейчас» по пункту Б списал у Б 20 ₽, проба ролика в кошелёк Б 30 ₽, темы и сценарий звали платную модель за счёт Б, утверждение и правка чужого плана проходили. Админка: «войти под клиентом», «поправить кошелёк», «включить тариф по счёту», «отменить счёт» проверяли только вход.
✓ Коммит fd206d5 (+ c2e71d2 по находкам критика), выкачено на стенд 03.10. src/svoe.ts требуетсяСвоё; tools/chuzhoe-po-id.check.ts 187/296 → 296/296. Критик принял; остаток: FAIL-205 (человек в двух организациях — действия с брендом из адреса), FAIL-211 (счётчик подбора id).
bug
🔴 Админка: каждая страница проверяет сотрудника сама; «под клиентом» — только к клиенту организации
ОТК 03.10: проверка сотрудника стояла только в каркасе app/admin/layout.tsx; при частичной отрисовке Next каркас может не выполняться, 10 страниц админки своей проверки не имели — на стенде список людей с почтами и остатками отдавался без входа (воспроизведено). «Войти под клиентом» не сверял цель: можно было под другим сотрудником или в чужой цели организации.
✓ Коммит c2e71d2, выкачено на стенд 03.10, гейт зелёный. await требуетсяСотрудник() первой строкой каждой страницы админки; tools/svoya-proverka-stranic.check.ts 10/56 красные → 56/56; tools/prosmotr-cel.check.ts 3/4 → 4/4. Оба в гейте.
bug
↑ Решения владельца 03.10: голос аватара, фото пункта товаром, фотосессия по доле
Владелец 03.10 «по всем пунктам да»: Р-23 явный выбор голоса аватара при фото; Р-25 фото пункта плана у всех форматов — фото товара; Р-28 фотосессия с частью кадров — оплата по доле.
✓ e2ebe38 + 79b9782 + 3e45230 + b6d3c01, выкачено на стенд 03.10, гейт зелёный. Экзамены до кода: голос 12/14 и 7/10 красные → зелёные; фото пункта 17/21 → 21/21; фотосессия 3/3 и 6/15 → 3/3 и 15/15 (1 из 4 кадров: списано 25 ₽, возвращено 75 ₽). Три строки старых экзаменов приведены к решениям Р-23/Р-25 с пометкой. vitest 534/534.
feature

Ворклог (места остановки)

11.10.2026, 12:43:30 · С11 · День переключения 11.10.2026 · claude

24contentzavod.ru переключён на ядро в 11:47 UTC (вариант А). Старый сервис заморожен (4 крона), перенос догнан — сверка 28/28 (починены перенос людей: 213 пустых сирот от проверок пропускаются; перенос работ: согласование после «готово»; 6 пустых тестовых кабинетов без владельца помечены удалёнными). Контур «бой», Метрика 106102698, автоматика включена. Найдено и починено: живые процессы ядра жили с проверочными флагами (тревоги владельцу не уходили, уведомления поддержки глохли) — выкат теперь их гасит и сверяет. Журнал ошибок в бою: core.error_log, ловец в браузере, сбор журналов pm2 раз в 5 мин, суточный разбор 09:00 МСК (сводка владельцу + полный отчёт /var/log/cz-razbor), экран /admin/oshibki.

→ Второй круг журнала по отчёту критика (точность сводки) — исполнитель в ветке fix-zhurnal2, выкатить. Владелец: адрес ЮKassa → /api/pay/yookassa, вход своим паролем, сброс пароля, пополнение 100 ₽, переобход Вебмастера. Ежедневно: разбор /var/log/cz-razbor/<дата>.md и правки. Неделю держать откат (старый сервис не удалять).

10.10.2026, 13:10:20 · С11 · Приёмка и переключение — этапы 1–4 доводки · claude

10.10.2026. Этапы 1–4 плана доводки закрыты. Этап 1: деньги при обрывах FAIL-007/010, ручка /_next/image закрыта, конфиг дня переключения получил правило денежных таблиц REST от 03.10. Этап 2: 4 критика по дифу с 02.10 — 46 находок (автоцепочка план→заказы→автопубликация, граница своего, Kie по документации, проверки гейта портили общую базу), 5 исполнителей закрыли все с экзаменами; миграция 0072 (признак — строка на область) с «да» владельца. Этап 3: обход 80 адресов×4 вида — 46 находок, major закрыты; сквозной путь СММщика пройден. Этап 4 на живых деньгах (1 аватар): 5 дефектов найдено и закрыто (прокси резал ответы по символам, длина звука, повтор выкладки, карусель-провал с полным списанием, выдуманный бренд), после правок всё вышло. Стенд newtest на master 15b854e, гейт зелёный, 627 юнит-тестов.

→ Этап 5 — переключение 24contentzavod.ru на ядро: ждёт «да» владельца и даты. До дня: решения Р-29…Р-46 (дефолты применены), SUPPORT_HOOK_SECRET и CZ_CONTOUR=бой в боевом .env, адрес уведомлений ЮKassa, сообщение Dandelion про сети. Обновить снимок на GitHub (ветка github-snapshot). Перемерить экономику поста (Р-45).

03.10.2026, 01:07:26 · С16 · Верность входу · claude

03.10: закрытый репозиторий github.com/Logonchek/cz-core создан (через браузер владельца) и залит чистым снимком 1aca2d0 — дерево master b6d3c01 одним коммитом, без истории (в ранней истории .env.bak с паролем базы). Перед пушем дерево проверено на секреты: только заглушка в .env.example и открытые сертификаты.

→ Обновлять GitHub снимком поверх github-snapshot (commit-tree -p), историю master не пушить. Дальше по ядру: FAIL-205, этап 4 (платная проверка контента по «трачу?»), день переключения.

03.10.2026, 00:52:09 · С16 · Верность входу · claude

03.10 ночь: по «да» владельца внедрены Р-23 (выбор голоса аватара при фото), Р-25 (фото пункта плана — товар у всех форматов), Р-28 (фотосессия по доле вышедших кадров); остальные Р приняты как были. Выкачено на стенд, гейт зелёный, vitest 534/534.

→ GitHub — ждёт пустой репо Logonchek/cz-core (чистый снимок без истории); FAIL-205 (человек в двух организациях); этап 4 — платная проверка контента по «трачу?» (~1 860 ₽); день переключения.

02.10.2026, 21:09:52 · С16 · Верность входу · claude

03.10 вечер: по ОТК границы своего — админка ядра проверяла сотрудника только в каркасе layout, при частичной отрисовке страницы отдавали данные без входа (воспроизведено на стенде). Теперь проверка в теле каждой страницы (c2e71d2), экзамен по исходникам 56/56 в гейте; «войти под клиентом» сверяет цель (не сотрудник, член организации). Выкачено, гейт зелёный.

→ FAIL-205 (человек в двух организациях: действия с брендом из адреса); GitHub — ждёт пустой репо; решения Р-21…Р-28; на старом сервисе — «да» на security-правку и выкат 15e0496e+c497c814.

02.10.2026, 20:52:47 · С16 · Верность входу · claude

02–03.10: спринт «Верность входу» выкачен на стенд newtest (master fd206d5, гейт зелёный). Общий узел kie.ts: фото и кадры доходят до модели (пара t2i→i2i, отказ до оплаты, пропорция, секунды по доке Kie). Студия ВХ-С1…9 и линии ВХ-Л0…8 — фото, места, цена = кнопка, готовое не провал, план = кнопка. Сценарист = промт прода v13 + gpt-4o + реплики звукорежиссёра: слепая сверка ядро 5.60 vs прод 5.30 (~55 ₽). Профили 356 брендов приведены к схеме, позиционирование 10 заводов перенесено. ОТК тремя критиками: подпись чужого файла через %2e%2e (закрыто, src/put.ts), двойной запуск пункта = 2 списания (закрыто), чужой объект по id из формы во всех действиях + админка без проверки сотрудника (закрыто, src/svoe.ts, 296 проверок в гейте). Выкат: откат теперь работает (копия вне .next), гейт ловит ловушку server action и обход страниц.

→ 1) Вердикт критика по границе своего (fd206d5). 2) FAIL-205: человек в двух организациях. 3) GitHub: владелец создаёт пустой закрытый Logonchek/cz-core — пушить чистый снимок без истории (в истории .env.bak с паролем). 4) Решения Р-21…Р-28 (дефолты применены). 5) Этап 4 (платная проверка контента, ~1 860 ₽) — по «трачу?». 6) Перед переключением: закрыть дыру REST старого сервиса (perenos-dengi берёт балансы из public.profiles).

02.10.2026, 06:17:50 · С11 · Приёмка и переключение · claude

02.10 (после «ок» на план): этап 1 доводки. Починено с экзаменами до кода и выкачено на newtest (гейт зелёный): «Сохранить» на «Сетях» ломал страницу; согласие Лии исполняется; старые адреса переадресуются; конфиг nginx дня переключения готов. Разметка каталога отказов (248) вскрыла и закрыто: автозапуск планов не был подключён; шаг «авто: готовое → расписание» не был написан; пропущенный слот против решения В-12; ключ API уволенного; действия в просмотре под клиентом; оборванная отправка; себестоимость вебхуков. Вид на телефоне: шапка кабинета, пульт студии, главная. Коммиты 69e3672…d92519a.

→ Ждёт владельца: Р-1 (репозиторий), Р-2 (бот поддержки), Р-3 (потерянные файлы), Р-11 (вариант переключения А/Б), Р-12 (годовые цены на проде), смета этапа 4 и сверки текстов. Дальше по коду: тесты на 10 денежных механизмов без проверки; приёмка качества — со сметой; повторный обход интерфейса.

02.10.2026, 03:54:17 · С11 · Приёмка и переключение · claude

02.10: перенесён урок Студии рабочего сервиса. src/casting.ts — послесловие о славянской внешности к промту ролика студии (с фото — человек с фото остаётся собой), studio-help.ts — помощник пишет для ролика одну сцену. Тест src/casting.test.ts 6/6. Гейт поймал мою правку сценариста рабочего сервиса (перенос не воспроизводился) — правка там откачена, перенос снова байт в байт. Выкачено на newtest: гейт зелёный, 86 страниц без беды. Коммиты fe82925, ef4996f. Написан docs/ПЛАН-ДОВОДКИ-2026-10.md. В ПО-СИМПТОМАМ добавлены «люди не той внешности» и «загрузка 500 от nginx».

→ Ждём «ок» на план и ответы Р-1…Р-10 (главные: репозиторий, бот поддержки, потерянные файлы, /docs). После «ок» — этап 1: ошибка функции в клиентском компоненте, согласие Лии на публикацию, сверка сетей через Upload-Post, конфиг nginx для разводки, сверка качества текстов с продом. Незакоммиченные правки прошлой сессии (поддержка: hooks/support, support-channels, b/[brand]/support, docs) лежат в дереве — не мои, разобрать.

21.09.2026, 06:49:49 · С11 · claude

Обращение владельца: Лия прислала «скриншот без текста», на сайте поддержки пусто. Разобрано: обращение в очереди ядра (newtest), не старого прода; сам файл — селфи случайного человека, написавшего боту публикаций. Починено: тревога о новом обращении несёт ссылку «Разобрать: …» на нитку. Выкат стенда зелёный. В OPEN-QUESTIONS В-24: бот публикаций не должен принимать поддержку, предложен перенос @contentzavodru_bot на ядро в день переключения.

→ Ждём решения владельца по В-24 (дефолт: как есть, тревога со ссылкой). Спам-нитка Майи Озеровой (02.09) и селфи Сергея висят в очереди newtest/admin/support — закрыть руками. Найти источник ошибки «Functions cannot be passed…» (задача в С13).

16.09.2026, 11:09:40 · · claude

Проверил отчёт тестировщика кода и закрыл два блокера из трёх. СВЕРКА ОТЧЁТА. Блокеры 1 и 2 подтвердились до цифры: 107 файлов +13 689 строк без коммита с 19 августа, секрет вебхука Kie в .env.bak-20260809 совпадает с живым. Блокер 3 описан неточно: ошибка digest есть, но убывает (7.09 — 12, 8.09 — 4, 15.09 — 2), а не «ежедневно 25». Самая массовая ошибка в логах вообще другая и в отчёте не названа: Failed to find Server Action с мусорными id «x», «0», «1» — стенд щупает внешний сканер, кода это не ломает. СЕКРЕТ СМЕНЁН. Kie работает опросом, адрес возврата не передаётся, секрет у провайдера не зарегистрирован — смена безопасна. Health 200, вебхук без подписи 401. Нашёл второй бэкап .env.bak-2026-08-25, который лежал в дереве и уехал бы в историю первым же git add — оба вынесены в /root/env-backups, .gitignore расширен до .env*. ЗАКОММИЧЕНО: 419ef02, 418 файлов, дерево чистое, секретов в индексе нет. ЗАГОЛОВКИ БЕЗОПАСНОСТИ на стенде: HSTS, X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, через always — отдаются и на ошибках. Бэкап конфига, nginx -t, reload без даунтайма, /login 200. CSP намеренно нет: Next со встроенными скриптами без nonce ляжет. ПО ТРЁМ ВОПРОСАМ ВЛАДЕЛЬЦУ дал рекомендации: расхождение ровно на 100 ₽ у двоих — скорее всего подарок за регистрацию, сверить транзакцию до выбора «чьё число верно»; 878 мёртвых ссылок — бросить, но показывать «файл не сохранился у провайдера», 116 картинок строкой перенести в хранилище; 35 соцсетей — плашка «переподключите», остальное не блокировать.

→ Ждёт владельца: создать приватный пустой репозиторий на github.com/Logonchek и назвать имя — push с cfru готов, ключ авторизован. Три решения из отчёта (100 ₽, 878 ссылок, 35 соцсетей) по-прежнему за владельцем, рекомендации даны. Ошибка digest: не блокер, тренд убывающий; мусорные Server Action — шум сканера, при желании отсекать на nginx по заголовку Next-Action с невалидным id. Дальше по порядку владельца: этап 3, тестировщик интерфейса через ИИ Полигон.

16.09.2026, 10:53:35 · S18 · claude

16.09: владелец поставил задачу ИИ-монтажёра живого сырья (нарезка длинного, сборка из дублей с матч-катами, аутро для Пульса цен). Решение: делается отдельным проектом montazher (Atlas) со скиллом Claude Code и локальным конвейером на Windows; лист, критик и блоки Режиссёра переиспользуются, в Завод подключится по API позже. director-render на cfru остаётся выключенным.

→ С12 не трогаем; смотреть проект montazher.

03.09.2026, 07:29:49 · С11 · claude

ОТК-приёмка ядра перед продом (круг 1). Прогнано 20 наборов проверок, все зелёные: типы, юниты, 26 инвариантов базы, карта, окружение, покрытие договора, аудит доступа на настоящих данных (9), нагрузка 50 человек (p95 511 мс), деньги (16), платежи, касса, заказы (22), идемпотентность, сеансы, доступ, согласование (18), публикация, экраны, дым по вебу (69), сверка переноса (28). Безопасность: порт 3160 закрыт файрволом, админка постороннему 404, закрытые страницы редиректят на /login, пробный режим не включается в production, суточный потолок и отсечки баланса работают. Храповик покрытия поднят с 3 до 18 — экзамены усилены, не ослаблены. ВЕРДИКТ ЖЁЛТЫЙ, три блокера заведены задачами: некоммиченные 107 файлов без remote, действующий секрет вебхука Kie в истории git, ежедневная ошибка рендера при живой перерисовке. Отдельно: нагрузка мерена на 789 работах при критерии 5000, заголовков безопасности нет ни на стенде, ни на проде.

→ Владельцу три решения: чьё число верно у 2 человек с расхождением 100 руб; что делать с 878 работами с умершими ссылками и 116 картинками строкой в базе; переподключить 35 отвалившихся сетей. Дальше по порядку владельца: этап 1 закрыть спринты, этап 2 тестировщик кода, этап 3 интерфейс через Полигон, этап 4 платный контент, этап 5 переключение.

03.09.2026, 07:20:44 · · claude

ЭТАП 1 ЗАКРЫТ ПО КОДУ. За сессию: выбор движков роликов в трёх местах с ценой по движку и длительности (и починены три неверных имени моделей Kie, из-за которых выбор был декоративным); экран заказа переведён на пульт вместо анкеты; раздел «Конкуренты» с переходом «чужой ролик → идея в свой план»; Лия смотрит на дела и предлагает следующий шаг; Режиссёр-ИИ «сценарий → лист»; служба рендера листа в mp4 (собрана, проверена и ВЫКЛЮЧЕНА признаком до усиления сервера — решение владельца); прописка 18 агентов ядра в бэкофисе с докладом о работе; номера отказов в проверках (3 → 18); три урока с навесов. ЭТАП 2, ОТК — вердикт жёлтый. Три находки со сценарием отказа, все починены: внешние вызовы без таймаута; служебные надписи, запекавшиеся в оплаченный ролик клиента; петля в разметке действий (неудачная отправка события порождала новую отправку). Не зелёный, потому что тронуты файлы проверок (там были вписаны старые суммы) и не прогонялся масштаб — он на этапе 4. ЭТАП 3, первый заход Полигона: 129 находок на 200 проверок по стенду, гостем и под учёткой. Побочно починен сам Полигон — обрезанная подробность роняла весь прогон на последнем шаге. СВЕРКА ПЕРЕЕЗДА: Метрика в ядре есть и продумана, но висит на переменной METRIKA_ID, которой на стенде нет нарочно; GA в ядре нет вовсе; из девяти проиндексированных адресов прода шесть открываются, два перенаправляются, а /docs отдаёт 404.

→ Владелец идёт вторым агентом сверять спринты и ломать. Дальше по его порядку — этап 3 (тестировщик интерфейса) с разбором находок Полигона: наезд блоков на iPhone, обрезанный и слипшийся текст на входе, мелкие кнопки под палец. ЖДЁТ РЕШЕНИЯ ВЛАДЕЛЬЦА: адреса на nginx, потерянные файлы работ, В-18 в Студии, Veo Omni и Kling Omni, оценка библиотеки блоков, нужен ли Google Analytics, что делать с /docs. По коду в этапе 1 незакрытого не осталось.

02.09.2026, 12:04:50 · · claude

Прописка ИИ-агентов ядра через бэкофис сделана: 16 сотрудников Контент Завода заведены в реестре бэкофиса, ядро докладывает о каждой работе агента в общую ленту, бэкофис научился принимать доклады из чужих сервисов по HTTP. Проверено сквозняком без трат. Раньше в этой сессии: выбор движков роликов, цена по движку и длительности, экран заказа на пульте, три поломки Kie.

→ Осталось из списка владельца: перенос работ с прода в базу стенда, формула вживления человека в настоящее фото, стоп-темы проверкой после генерации, запись pm2 для сборщика ведёт обёртку. Обсудить: Veo Omni и Kling Omni. По бэкофису вторая половина (траты картинок и роликов через Kie) отложена владельцем и описана в задаче.

02.09.2026, 07:40:15 · · claude

Выбор движков роликов сделан целиком: каталог из семи моделей, выбор в заводе, СММ-Центре и Студии, цена следует за моделью и длительностью. По дороге найдены и закрыты три поломки Kie: два выдуманных имени моделей в реестре, проверка имён, пропускавшая такое по построению, и декоративный выбор движка в съёмке. Экран «Создать контент» переведён с анкеты на пульт по согласованному варианту; починены наезд пульта на боковое меню и системная полоса прокрутки. Ранее в сессии: воронка и путь клиента в админке, запись отказов в одном месте, разметка в Студии, сверка остатка кредитов при отказе Kie.

→ По списку владельца осталось: прописка ИИ-агентов через бэкофис (делать), перенос работ с прода в базу стенда, формула вживления человека в настоящее фото, стоп-темы проверкой после генерации, запись pm2 для сборщика ведёт обёртку. Обсудить: Veo Omni и Kling Omni. Не забыть: в работающем приложении стоит CZ_CHECK=1 — флаг проверок выката протёк в живой процесс через pm2 --update-env, и всё, что им глушится, в кабинете молчит.

01.09.2026, 11:46:24 · С9 · Эксплуатация · claude

Аналитика клиента в админке доведена и проверена на живых данных. СДЕЛАНО · Экран «Путь клиента» (/admin/funnel): воронка от регистрации до оплаты, где теряем больше всего, что заполняют в компании, на чём упираются, кто перестал заходить. Всё считается кодом, ни одного числа от модели. · Оплату в воронке ищем через кошелёк: по полю payments.org_id выходило «заплатили 0» при 64 оплаченных платежах, потому что у перенесённых строк поле пустое. Стало 30 платящих. · Заполненность компании переписана честно: «сети» считаются по подключениям, а не по полю профиля (было 0% при живых публикациях), и подаётся не как последовательность — разделы настроек проходят в любом порядке. На экране прямо сказано, что 572 компании из 578 перенесены и их заполненность — след старого сервиса. · Отказы клиента теперь записываются: запись стоит в понятно(), через которую проходит каждое «нет» сервиса. Пишется код отказа, а не текст. · Разметка появилась в Студии — раньше там не писалось ничего, хотя именно там тратят деньги поштучно. НАЙДЕНО ПОПУТНО (важное) Выкат ставит себе CZ_CHECK=1, а pm2 --update-env забирает окружение оболочки целиком — кабинет жил с флагом проверок постоянно. Из-за этого молчали оповещения о новых обращениях в поддержку, и соединение приложения ставило core.check_run=1, по которому триггер помечает каждую заведённую организацию проверочной, а проверочное денежные сводки не считают. Первый настоящий клиент, зарегистрировавшийся на стенде, просто не появился бы в выручке. Ущерб не наступил: помечена одна организация, и она правда проверочная. Починено в двух местах — deploy.sh гасит флаг при каждом перезапуске, и ядро больше не считает проверкой процесс server.js. ПРОВЕРКА ПО СРОКУ Воркер ядра: крах-цикл не вернулся, закрыта.

→ Решения владельца, заведены задачами: ИИ-агенты сервисов вести через бэкофис (одно окно на работу) — решать один раз на всех агентов, не по каждому; траты агентов отложены владельцем на потом, галочка стоит. Ждёт ответа владельца: что такое «работы на проде» — это 1688 незавершённых работ в базе рабочего сервиса, надо решить, добивать их или списать при переключении. Открыто в С12: библиотека блоков, рендерер листа, Режиссёр-ИИ v1, форматы длинных роликов, пилот фабрики трендов. В С11: смешанный прогон нагрузки на живых деньгах (около 190 руб, ждёт «да»), переподключение сетей брендам, разведение адресов на nginx. Мелочь на посмотреть: в логе приложения повторяется «Functions cannot be passed directly to Client Components» — где-то в клиентский компонент уезжает функция. На работу не влияет, но шумит.

01.09.2026, 11:46:07 · · claude

проба формы запроса, будет удалена

→ проба формы запроса, будет удалена

01.09.2026, 11:46:07 · · claude

проба формы запроса, будет удалена

01.09.2026, 10:05:29 · · claude

УЧЁТКИ: убрано 211 проверочных записей из базы КЛИЕНТОВ. Служба входа общая с рабочим сервисом, и каждый прогон ворот добавлял туда нового «клиента». Проверки переведены на две постоянные учётки; три прогона подряд и полный выкат — новых не появляется. ДОБЫТЧИК МАТЕРИАЛА: порядок по цене — моушен кодом, файлы клиента, сток, и только потом генерация. Девять проверок поймали запрос к несуществующей таблице (добытчик уходил бы в платную генерацию вместо бесплатного материала) и узкое правило счётчика. КАТАЛОГ ШРИФТОВ: собран из метаданных Google Fonts — 294 семейства с кириллицей, в каталог взяты 160. Характер берётся из полей API, выбор под бренд кодом. Все имена сверены с пакетом рендерера. Ошибка: перезаписал существовавший файл каталога, не проверив, что он есть; интерфейс восстановлен по проверке. РАЗБОРЩИК РОЛИКОВ (первый шаг фабрики трендов): готовый ролик → склейки, темп монтажа, длина хука, роли эпизодов. Бесплатный, поэтому применим пачками. Записано ограничение: ffmpeg сравнивает кадры по яркости и не видит смену плана внутри одной тональности.

→ ЖДЁТ ОТМАШКИ ВЛАДЕЛЬЦА (тратят деньги): · пилот фабрики трендов: выкачка 50 роликов через Apify (около доллара) и определение типа кадра моделью (рублей тридцать на пятьдесят роликов); · смешанный прогон нагрузки: 10 постов и 2 ролика разом, около 190 руб. ЖДЁТ РЕШЕНИЯ: прописка Режиссёра — внутрь ядра или отдельным сервисом. От неё зависят библиотека блоков и рендерер, последние два больших куска спринта. ОДНО КОРОТКОЕ ДЕЙСТВИЕ: написать боту @Contetn_zavod_MyVision_pub_bot что угодно — проверить приём обращений и файлов сквозняком. Сам не могу: бот не пишет человеку первым.

01.09.2026, 04:10:10 · · claude

ПИСЬМА НА ВЫДУМАННЫЕ АДРЕСА ПРЕКРАЩЕНЫ. Владельцу пригрозили баном почтового ящика: воротные проверки на каждом выкате слали три письма в никуда. Заведена регистрация без письма служебным ключом, кабинет собирается тем же кодом, что у людей. Восстановление пароля проверяется по исходнику, без обращения к службе. 14 проверок проходят, накопленные учётки убраны. Правило записано в память. МОДЕЛИ ЗОВУТСЯ ПО ДОКУМЕНТАЦИИ. Владелец спросил, не встанет ли полстудии, — вопрос попал в точку. Нашлись три поломки: выбор движка молча подменялся на gpt-image-2 (проверено живьём: просили z-image, ушло gpt-image), поле картинок у моделей разное, а мы всем слали input_urls, и наш формат постов 4:5 знает не всякая модель. Источник правды нашёлся: docs.kie.ai/market/<путь>.md с OpenAPI-спекой. Имя вызова, поле картинок и пропорции теперь в реестре по документации, три новые проверки. Wan 3.0 доступен (я ошибочно сказал обратное): wan/3-0-video, до 30 секунд, 114 руб за 30 секунд в 480p. ТРИ УРОВНЯ ШАБЛОНОВ по решению владельца: премиум с OmniHuman (актёр берёт товар, ходит), средний с Kling Avatar, экономный с InfiniteTalk. На 30 секундах 394 / 198 / 55 руб. Проверка поймала ошибку: на экономном призыв выходил нулевой длины. РЕЖИССЁР: формат листа, критик кодом, три вопроса драматургии владельца, пословные субтитры из таймингов озвучки с выравниванием написанного и произнесённого. ПОДДЕРЖКА ПРИНИМАЕТ ФАЙЛЫ. Дыру доказал живой клиент: прислал два скриншота, а у нас осталась пометка. Теперь файл забирается сразу и виден в переписке. ПОЧИНЕН ДАШБОРД ATLAS: стек подняли без файла переменных, Redis встал в цикл, API отдавал 500. Поднял с --env-file, разбор записан в проект Atlas.

→ В РАБОТЕ: добытчик материала для режиссёра — выбирает источник эпизода в порядке цены: моушен кодом (0) → файлы клиента (0) → сток (0) → генерация (15-60 руб). Это то, что делает экономный уровень по-настоящему дешёвым. ДАЛЬШЕ по спринту режиссёра: библиотека блоков и рендерер (упираются в развилку «прописка режиссёра» — внутрь ядра или отдельным сервисом), фабрика трендов. ЖДЁТ ВЛАДЕЛЬЦА: · один короткий тест — написать боту @Contetn_zavod_MyVision_pub_bot, чтобы проверить приём обращений и файлов сквозняком (сам не могу: бот не пишет человеку первым); · решение по развилке «прописка Режиссёра»; · отмашка на смешанный прогон нагрузки (10 постов и 2 ролика разом, около 190 руб). ЗАМЕЧЕНО НА ПРОДЕ, не чинил: клиент упирается в тип завода — цены на товары есть только у типа «Селлеры (маркетплейс)», таких заводов 1 из 176, а сменить тип после создания нельзя. Из-за этого человек через 17 минут после регистрации идёт в поддержку.