Раньше compose монтировал с хоста Caddyfile, init-скрипты базы и генератор
секретов. Из-за этого установка требовала рядом распакованный репозиторий, а
файл, забытый при сборке релиза, Docker молча подменял пустым каталогом — и
стек падал на первом старте у человека. Плюс production-образ backend вообще
не собирался: COPY content ссылался на каталог, которого в репозитории нет,
так что релиза не существовало ни на GitHub, ни на GitLab.
Теперь весь дистрибутив — один compose.yaml со страницы релиза:
curl -fsSL .../compose.yaml -o compose.yaml
docker compose up -d --wait
- Caddyfile переехал в свой образ шлюза (caddy validate — в сборке),
init-скрипты базы — в свой образ postgres, генератор секретов — в
backend-образ. Bind-mount'ов в production-манифесте не осталось.
- Состояние установки — именованные тома вместо каталогов рабочего каталога.
Заодно чинит загрузку файлов на Linux: том наследует владельца из образа
(hub), тогда как bind-mount доставался контейнеру как root:root.
- scripts/pin-release-compose.py закрепляет ссылки на образы по digest и
падает, если хоть один ключ остался подстановкой.
- GitHub-workflow собирает четыре образа и прикладывает к релизу compose.yaml
(основной путь) и release.env (для `chatballs deploy`).
- tests/cli/test_release_compose.py держит свойство: манифест без bind-mount'ов
и полностью закрепляем по digest.
- Из окружения шлюза убраны CHATBALLS_APP_DOMAIN и CHATBALLS_ACME_EMAIL —
Caddyfile их не читает.
Dev-контур не меняется по смыслу: compose.dev.yaml по-прежнему собирает всё
из исходников и держит состояние в ./data.
AUDIT-TODO.md — временный список остального из аудита; удаляется целиком,
когда закрыт последний пункт.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пагинация в порталах и контактах была нарисованной: сервер отдавал весь набор,
браузер резал его на страницы. У сотрудников и агентов не было и этого. Любой
из четырёх списков рос вместе с организацией и целиком уезжал клиенту.
Серверная часть — страницы и фильтры до среза:
- контакты: подзапросы вместо join-агрегатов (фильтр по каналу больше не
урезает счётчики диалогов), поиск, каналы, агенты, «с открытым диалогом»
и порядок — в SQL;
- сотрудники: роль, группа и поиск по имени, почте и должности;
- агенты: группа и поиск;
- порталы: статус и поиск, архивные последними;
- библиотека статей: категория с вложенными, язык, статус и поиск по последней
редакции.
Клиент:
- один подвал со страницами на всё приложение вместо трёх разных
(PortalTableFooter, SalesClientsPagination, TablePagination удалены);
- usePagedResource: страница принадлежит набору фильтров, гонки ответов
отсекаются, сервер решает, какая страница существует;
- useDebounced вынесен в shared — поиск придерживает запрос;
- карточка сотрудника грузится по идентификатору, а передача владения сама
запрашивает кандидатов: список постраничный, и нужного человека может не
быть на открытой странице;
- App больше не тянет всех сотрудников на старте.
Выпадающие выборы (ответственный, передача владения, фильтр по агентам)
работают со справочниками; библиотеке знаний нужен серверный контракт со
счётчиками прикреплений — до него потолок в 100 карточек оставлен явным.
Контракт списков и лент записан в SPEC-CHATBALLS-0031 §8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пять из шести сценариев проверяли допитовный интерфейс и падали с 2026-09-03:
ждали заголовок «Командный центр», секции сайдбара «Продажи»/«Поддержка»,
маршрут /departments/sales/dialogs, роль OPERATOR, отделы и колонку «ДОСТУП».
Всё это снесено ADR-CHATBALLS-0041 и ADR-CHATBALLS-0043. В check.ps1 e2e не
входят, поэтому падения никто не видел.
Что проверяется теперь:
- владелец после входа попадает в чат и видит ровно семь пунктов навигации
(SPEC-CHATBALLS-0031 §4), а снесённых понятий — «Командный центр», «Отделы»,
«Продажи», «Каналы», «Подключения», «Продукты» — в интерфейсе нет; это
регрессионная страховка вместо прежних ожиданий;
- сотрудник попадает в тот же чат, но пунктов администрирования не получает
(дерево диалогов тоже лежит в nav.hub-nav, поэтому проверяются именно
button.hub-nav-item);
- сотрудник на менеджерском маршруте видит 403 — сценарий сохранён;
- организация берётся из адреса: запросы уходят только во вторую организацию,
хотя членство есть в обеих;
- на минимальной ширине 1024px нет горизонтальной прокрутки;
- экран сотрудников: колонки списка, ящик создания, карточка с разделами
«Должность и группы» и «Системная роль» (профилей доступа больше нет),
передача владения в «Опасной зоне» карточки владельца, а не в меню строки.
Фикстуры приведены к текущим контрактам: membership с группами вместо отделов,
права выводятся из роли зеркалом `identity/capabilities.py` (пустой список
скрывал кнопку «Добавить сотрудника»). В списке появился администратор — без
него диалог передачи владения показывал пустое состояние, и проверять в нём
было нечего.
Прогон: 8 passed, 6 skipped, 0 failed (было 3 passed, 5 failed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Требование к open-source публикации: в репозитории не должно остаться
CustoCRM и eDevs Hub. В docs/ это сделано ранее, здесь — код и дизайн.
Дизайн-система (design/design-system):
- заголовки, брендинг и примерные данные переведены на Chatballs;
- переименованы глобали справочника: CUSTOCRM_ICON_SPRITE →
CHATBALLS_ICON_SPRITE, CUSTOCRM_INVENTORY → CHATBALLS_INVENTORY,
data-атрибут спрайта — обе стороны замкнуты внутри справочника;
- в README поправлены пути открытия: справочник живёт в design/,
а не в apps/internal-ui/;
- личный адрес andrey@edevs.tech в примере таблицы заменён на example.com.
Макеты (design/baseline):
- каталоги _ds/edevs-hub-design-system-<uuid>/ переименованы в _ds/design-system/,
ссылки в .dc.html обновлены;
- JS-namespace бандла EdevsHubDesignSystem_e4c9df → ChatballsDesignSystem_e4c9df
(21 вхождение: бандл, манифест, все x-import в макетах);
- readme.md внутри бандлов удалён — это описание прежнего продукта
(hub.edevs.tech, checkout, командный центр), к текущему отношения не имеет;
- assets/custocrm-mark.svg → chatballs-mark.svg;
- удалён каталог «Каналы обработки» целиком: макеты сущности, упразднённой
ADR-CHATBALLS-0041. Восстанавливается из истории git, если понадобится.
Тесты: tests/cli/test_custocrm_cli.py → test_chatballs_cli.py, набор проходит
(9 passed).
Ссылки в макетах и справочнике проверены: из 121 ссылки битых 6, и все шесть —
в «Сотрудники/Сотрудники (новая модель).dc.html», где каталог _ds отсутствовал
и до этих правок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Документация переработана: действующее отделено от истории, отменённые
контуры (продажи, биллинг, managed AI, отделы, сущность Product) убраны из
действующих документов в архив.
Здесь — только кодовая часть: ссылки на документы в комментариях. Ссылки на
действующие документы переименованы ADR/SPEC/ARCH/BUS-HUB-NNNN →
*-CHATBALLS-NNNN (274 ссылки в 173 файлах). Ссылки на документы, ушедшие в
архив, намеренно сохранили прежний идентификатор: он совпадает с именем
архивного файла.
Логика не менялась — правки только в комментариях, докстрингах и одном
описании теста.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Переход на запуск без .env (41a4b0d) не был доведён: в дереве остались файл
`env.example`, чтение instance .env во всём deploy/cli и CI, который этот .env
сам же и создавал из `env.example`. Из-за остатков ломались две вещи.
`chatballs deploy` не работал на установке без .env: _normalize_schema_ownership
брал POSTGRES_USER и POSTGRES_DB из файла и падал с «POSTGRES_USER not set»,
хотя установка исправна. Теперь берёт те же значения по умолчанию, что compose.
`chatballs doctor` выдавал пять ложных ошибок подряд: искал в .env домены,
ACME-почту, POSTGRES_PASSWORD и CHATBALLS_SECRET_KEY. Первое задаёт владелец
в «Настройках», второе генерирует в том с секретами первый старт стека —
снаружи, с хоста, этого не видно, и проверки убраны.
Убрано: env.example (из репозитория, release bundle и README); instance_env_file()
и все его чтения в common/compose/deploy/doctor/status; --env-file instance .env
из вызова compose — остаётся только release.env с digest-пинами образов от CI;
создание .env в gateway:validate. Профиль calls и его адреса читаются из
переменных окружения — оттуда же, откуда их берёт сам compose.
Тесты CLI переведены с фиктивного .env на переменные окружения; три проверки
удалённого поведения doctor убраны. Девять оставшихся проходят вообще без .env
— это и есть проверка, что установка теперь обслуживается. Заодно у
test_custocrm_cli.py выправлены окончания строк: в файле были одиночные CR
внутри кода, отчего diff по нему больше содержательной правки.
Проверено: pytest tests/cli — 9 passed; docker compose config валиден с пустым
каталогом инстанса; bash -n чист по всем скриптам CLI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Product` оставался в схеме «скрытой технической осью» (ADR-HUB-0041 §7), но
из интерфейса не исчез: в настройках портала жил раздел «Продукты и поддержка»,
в списке порталов — колонка «Продукты». Решение владельца — убрать сущность
целиком вместе со всем контуром, который на ней держался.
Удалено:
- приложения `products` и `support` (контракты идентификации, снимки личности,
Product Support Token, публичные endpoints сессии);
- `Channel.product` и `requires_authenticated_product_identity` (инварианты
P3-P5; остались P1-P2), `LlmInvocation.product`, `SupportPortalProduct`;
- `WebChatWidget.mode` целиком: различать было нечего, у веб-виджета одна
анонимная точка входа. Ключ `mode` ушёл и из ответов API виджета;
- `mode=support` и `ChatballsChat.init` в лоадере, `SupportApp` в web-chat;
- capability `products.*`, аудит-действия `products.*`, продукты в карточке
контакта и в статистике (`byProduct`);
- раздел настроек портала и колонка списка — в коде и в дизайн-базлайне v2.
У диалога один источник identity — контакт: XOR заменён на
`conversation_requires_contact`. `PUT /portals/{id}/products/` удалён,
`GET /portals/{id}/support-channels/` → `GET /portals/{id}/widgets/`.
Миграции — по образцу сноса продаж: исторические миграции уцелевших приложений
вычищены от ссылок (чистая установка объектов не создаёт), существующие БД
чинит `tenancy/0029_drop_product_support`. Данные не экспортируются, но диалоги
со снимком личности не удаляются — каждому создаётся контакт с именем из снимка.
Проверено: backend 583 OK, фронт tsc + vitest 77 OK, сборки internal-ui и
web-chat OK, миграция прогнана на локальной БД, публичный портал открывается.
В коммит также вошли накопленные незакоммиченные правки рабочего дерева
(библиотека знаний, настройки, dev-compose и nginx, макеты базлайна).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Первичное требование: поднял докер — прошёл мастер — дальше всё в интерфейсе.
Ни одной переменной окружения задавать не нужно и негде.
Запуск и секреты:
- .env.example удалён, мёртвые переменные вычищены; секреты инстанса
генерирует одноразовый сервис secrets в именованный том, пароли БД —
через POSTGRES_*_PASSWORD_FILE;
- Caddy: catch-all :80 и on_demand TLS вместо хостов в конфиге — свежая
коробка отвечает по IP и по любому домену, до мастера дойти можно;
- адрес установки, SMTP и TURN переехали в настройки (InstanceSettings,
миграции 0027–0030), внешние ссылки строятся от него;
- deploy/cli больше не читает .env; сборка образов в ghcr через GitHub
Actions, release.env с digest-пинами;
- куки Secure/__Host- выставляются по факту TLS запроса, а не настройкой.
Порталы (дизайн-базлайн v2, кадры PT1–PT8):
- список, карточка портала, библиотека материалов, редактор статьи и
настройки — ширины и ритм как в остальных разделах;
- файлы статьи: изображение вставляется в текст своим механизмом, файл
прикрепляется вложением и выводится на портале списком с иконкой формата;
- ссылки на файлы приводятся к относительным: абсолютный хост резал CSP
портала и картинка не появлялась;
- «Опубликовать» сверяется с сервером и публикует то, что на экране, а не
ранее выбранную редакцию;
- колонка «Оценки» в списке статей и блок оценок в редакторе.
Первый запуск: полоса «демо-данные устанавливаются» — установка идёт в
worker, и без неё человек видел пустые разделы без объяснения.
Починен фон: вторичное хранилище стало best-effort — при живых остаточных
S3-ключах повторная загрузка файла падала в 500.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- нет зашитых доменов: help-домен по умолчанию localhost (задаёт установщик через
CHATBALLS_HELP_BASE_DOMAIN), фронт определяет портал помощи пробой /api/v1/help/,
ALLOWED_HOSTS без вендорских хостов, CI environment url — из CHATBALLS_APP_DOMAIN
- DEFAULT_FROM_EMAIL по умолчанию no-reply@localhost; JWT-аудитория support-токена
chatballs.support; загрузчик виджета: window.ChatballsChat, события chatballs-chat-*
- «Контакты»: продукты берутся из данных организации ({code, name}), цвет —
детерминированно по коду; зашитый список продуктов вендора убран
- npm-скоупы @chatballs/*, тема chatballsTheme/buildTheme
- тестовый bootstrap: организация demo, сотрудник staff.member@example.org,
продукты site/app; фикстуры на example.com
- убрана мёртвая привязка Shell к e-mail сотрудника вендора
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- каталог code/custocrm → code/chatballs; CLI custocrm → chatballs; пакеты
hub_platform → chatballs, hub_backend → chatballs_backend (app labels прежние)
- переменные CUS_* и CUSTOCRM_* → CHATBALLS_*; образы chatballs-*; compose-проект
и база chatballs (были edevs_hub); роли Postgres chatballs_* (были custocrm_*);
схема RLS chatballs и GUC chatballs.organization_id; cookie chatballs_*
- CI: APP_DIR code/chatballs, DEPLOY_ROOT /opt/chatballs
- deploy/migrate/rename-to-chatballs.sh — миграция существующей установки без
потери данных: остановка старого проекта, .env (с резервной копией),
переименование суперпользователя initdb через временную роль, остальных ролей,
базы, схемы и функции RLS; проверено на локальном стеке
- dev-стек хранит Postgres в bind-mount data/postgres, как prod (именованный том
compose.dev был устаревшим снимком и вводил в заблуждение)
- снятие демо удаляет объекты, созданные поверх демо-данных тестировавшим
(звонки по демо-диалогу), вместо падения на PROTECT
- реальные домены *.custocrm.ru и идентификаторы документов не тронуты
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Исполнение ADR-HUB-0028 §«Разделение bootstrap-данных»: в облачной модели
арендатора наполняют provisioning и продуктовые API, а не Edevs-specific
команды. Контур был замкнутым островом — единственная точка входа
seed_hub_initial_data, которую не вызывали ни CI, ни compose, ни deploy,
а CLI-тест прямо проверял, что init её не запускает.
Удалены seed_hub_initial_data, _seed_specs, seed_channels, seed_catalog,
seed_orders, seed_support и импортер AI-контента. bootstrap_owner остаётся:
на нём держится тест-сьют. Файлы content/ai-content-*.md сохранены.
Для SPEC-HUB-0027 это снимает половину проблемы этапа 3: унаследованных
seed-каналов, нарушающих P1-P2, больше нет — остаются только дефолты
модели Channel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Hub→CustoCRM rebrand (192497e) missed five user-facing strings and the
matching Playwright selector. The sidebars still offered «Назад в Hub»,
the auth recovery/reset success states still referred to «в Hub», and the
MAX/Telegram notifier hint told staff to open «профиль в Hub».
Replace the last Hub references with the product name CustoCRM (auth,
notifier hint) or drop the qualifier entirely (sidebar back button, which
just returns to the command center). Update the Playwright selector that
asserted the «Назад в Hub» button is absent for operators.
- layout/SalesSidebar.tsx, layout/SupportSidebar.tsx: «Назад в Hub» → «Назад»
- features/auth/AuthPasswordRecovery.tsx, AuthResetPassword.tsx: «в Hub» → «в CustoCRM»
- notifications/binding.py: «профиль в Hub» → «профиль в CustoCRM»
- tests/e2e/internal-ui.spec.ts: selector «Назад в Hub» → «Назад»
Системные роли OWNER/ADMIN/EMPLOYEE (удалён OPERATOR), обязательная должность
position_title и основной отдел primary_department. Инварианты: владелец на уровне
компании, ровно один OWNER на организацию. Двухфазная миграция OPERATOR->EMPLOYEE
без расширения прав; операционный доступ сохранён compatibility-адаптером.
Обновлены DTO/API/формы, список и карточка сотрудника, тесты модели и инвариантов.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Playwright e2e (mocked API) for internal-ui: OWNER lands on the command
center with the global sidebar; OPERATOR lands on sales dialogs with the
sales sidebar and no company-level navigation; owner-only route shows the
403 permission screen; shell renders at the 1024px minimum width
- auto-start the internal-ui dev server via Playwright webServer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>