Skip to content

Готовые проекты вместо стартеров: сборка настоящих открытых репозиториев - #37

Merged
keksmd merged 21 commits into
mainfrom
claude/hermes-agent-vps-n898yt
Aug 7, 2026
Merged

Готовые проекты вместо стартеров: сборка настоящих открытых репозиториев#37
keksmd merged 21 commits into
mainfrom
claude/hermes-agent-vps-n898yt

Conversation

@keksmd

@keksmd keksmd commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Что меняется

Витрина на пустом проекте предлагала три наших же стартера — Next.js, FastAPI, статический сайт. Такая карточка доказывает ровно одно: что деплой случился. И доказывает это на репозитории, написанном так, чтобы наше автоопределение сработало.

Вместо них — четыре настоящих открытых проекта и поле «вставьте ссылку на любой публичный репозиторий»:

Проект Что это Порт
Excalidraw доска для схем от руки 80
IT-Tools 100+ инструментов разработчика 80
Gitingest репозиторий → один текст для LLM 8000
DevDocs документация 500+ библиотек 9292

Ключевое решение: репозиторий, а не образ

Каждая запись каталога — это репозиторий, а не опубликованный образ. Карточка идёт обычным пользовательским путём: подключить публичный репо → определить фреймворк → собрать у нас → задеплоить.

Взять n8nio/n8n:2.34.2 было бы красивее в вёрстке и не доказывало бы ничего — это была бы наша платформа, запускающая чужую сборку.

У выбора есть цена, и она же — смысл. Настоящие репозитории это монорепы, двухэтапные сборки, порт из переменной окружения и Dockerfile тремя каталогами ниже. Там, где автоопределение промахнётся, карточка сломается на глазах — и мы починим автоопределение. Это дороже каталога, который его никогда не касается.

Проверенные факты

Все четыре записи проверены до того, как попали в каталог: Dockerfile в корне репозитория, порт прочитан из него, а не угадан по фреймворку. Неверный порт даёт зелёный деплой и 502 — симптом, который читается как «платформа сломана».

Что осознанно ограничено

Записи v1 без состояния. Приложение, созданное сборкой, берёт спеку из git_repos, где тома нет, — проект с состоянием на диске молча терял бы его при каждом передеплое. Ограничение описано в пакете, тест на него есть; снимается вместе с поддержкой тома в build-спеке.

Жнец демо

Демо теперь — это репозиторий каталога. Старые стартеры остаются в списке: у уже развёрнутых из них приложений проставлен дедлайн, и удаление записи осиротило бы их в чужих проектах.

Репозиторий, который пользователь вставил сам, демо не считается никогда: он его выбрал, и ставить его выбор на таймер — другой продукт.

Бенчмарк автоподнимателя

tasks/autodeploy-benchmark-50-oss.md — 50 открытых репозиториев, четыре ступени (определилось / собралось / поднялось / отвечает), словарь причин провала и порог, ниже которого каталог не расширяется за пределы проектов с корневым Dockerfile.

После этого PR процент успеха автоопределения на чужих репозиториях — это напрямую процент успеха первого экрана. Такого числа у нас сейчас нет.

Состав

  • backend/internal/solutions — каталог, разбор вставленной ссылки (браузерный URL, clone URL, SSH-ремоут, голое owner/name), тесты
  • GET /solutions, GET /solutions/{slug}, GET /git/parse-repo-url; отдельного install-эндпоинта нет намеренно — деплой идёт существующим путём
  • фронт: карточки из каталога вместо списка в файле + поле ссылки с необязательной веткой (полно репозиториев с master по умолчанию)
  • swagger перегенерён

Проверка

  • go build ./..., go vet, go test ./internal/... — зелёные, гейт покрытия OpenAPI проходит
  • Фронт типами не проверен: npm ci в окружении сборки упирается в приватный Nexus (401). Требует прогона tsc/lint на машине с доступом к реестру
  • Первый коммит ветки — снятый с производства подход (готовые решения на VPS); итоговое состояние дерева его не содержит

Generated by Claude Code

claude added 5 commits August 6, 2026 10:47
Демо-стартер (`api/demo_apps.go`) доказывает ровно одно: деплой случился.
Через несколько часов жнец его удаляет, и человек остаётся ровно там, где
был. У провайдеров VPS на этом месте стоит библиотека готовых решений — от
n8n до Битрикса, — потому что продукт, который человек оставит себе,
отвечает на тот же вопрос «мне нечего задеплоить» несопоставимо честнее.

Первая запись каталога — Hermes Agent: открытый автономный AI-агент с
терминалом, памятью и веб-дашбордом.

Каталог — замороженная переменная в коде (идиома `boxcatalog`/`profiles`),
а не таблица: запись реальна только тогда, когда её образ опубликован и
её compose-форма хоть раз отработала на VM, а деплой — ровно то событие,
которое двигает и то, и другое. Запись без digest'а образа видна в
каталоге и не ставится (409): кнопка «в один клик», ведущая на
несуществующий тег, падала бы внутри pull'а Portainer с ошибкой, которую
некому прочитать.

Установка не заводит нового рантайма. Решение раскладывается в обычные
first-class VM Applications — по одному на compose-сервис, — так что логи,
метрики, редактор переменных, рестарт и удаление работают с первого дня, а
человек со стороны не отличит приложение из каталога от сделанного руками.

Безопасность дашборда Hermes — не деталь оформления. Апстрим биндит его на
loopback именно потому, что он держит API-ключи, и с июньского ужесточения
отказывается стартовать на не-loopback адресе без провайдера аутентификации
(`--insecure` там больше ничего не значит). Поэтому установка сама
генерирует пароль и ключ подписи сессий, кладёт их шифрованными и отдаёт
пароль в ответе один раз: наружу выходит дашборд с аутентификацией, а не
открытый шелл. Ключи провайдера модели в payload операции не попадают —
только в `env_vars`, чтобы секрет не лежал в строке, которую читает аудит.

- backend/internal/solutions: каталог, валидация имени и параметров, тесты
  на инварианты записи (один корневой сервис, публикуемый порт только у
  primary, отсутствие коллизий env-ключей, никаких дефолтов у секретов)
- backend/internal/api/solutions.go: GET /solutions, GET /solutions/{slug},
  POST /projects/{p}/environments/{e}/solutions/{slug}
- переменные и операция пишутся одной транзакцией: иначе возможен
  контейнер без пароля, без которого он не стартует
- swagger перегенерён

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CSUPisoFBFxh5E1vPHoED
…репозиториев

Витрина на пустом проекте предлагала три наших же стартера: Next.js, FastAPI,
статический сайт. Такая карточка доказывает ровно одно — что деплой
случился, — и доказывает это на репозитории, написанном так, чтобы наше
автоопределение сработало. Открыть её второй раз незачем, и через несколько
часов жнец её удаляет.

Вместо них — четыре настоящих открытых проекта (Excalidraw, IT-Tools,
Gitingest, DevDocs) и поле «вставьте ссылку на любой публичный
репозиторий».

Ключевое: каждая запись каталога — это РЕПОЗИТОРИЙ, а не опубликованный
образ. Карточка идёт обычным пользовательским путём: подключить публичный
репо → определить фреймворк → собрать у нас → задеплоить. Взять
`n8nio/n8n:2.34.2` было бы красивее в вёрстке и не доказывало бы ничего —
это наша платформа, запускающая чужую сборку.

У выбора есть цена, и она же — смысл. Настоящие репозитории это монорепы,
двухэтапные сборки, порт из переменной окружения и Dockerfile тремя
каталогами ниже. Там, где автоопределение промахнётся, карточка сломается
на глазах, и мы починим автоопределение — это дороже каталога, который его
никогда не касается. Обратная связь записана в
tasks/autodeploy-benchmark-50-oss.md: 50 открытых репозиториев, четыре
ступени (определилось / собралось / поднялось / отвечает), словарь причин
провала и порог, ниже которого каталог не расширяется.

Все четыре записи проверены: Dockerfile в корне, порт прочитан из него, а
не угадан по фреймворку (неверный порт даёт зелёный деплой и 502).

Записи v1 сознательно без состояния: приложение, созданное сборкой, берёт
спеку из git_repos, где тома нет, — проект с состоянием на диске молча
терял бы его при каждом передеплое. Это ограничение, а не вкусовщина, и оно
описано в пакете.

Жнец демо переучен: демо теперь — это репозиторий каталога (плюс старые
стартеры, у которых уже проставлен дедлайн, чтобы не осиротить их в чужих
проектах). Репозиторий, который пользователь вставил сам, демо не считается
никогда: он его выбрал, и ставить его выбор на таймер — другой продукт.

- backend/internal/solutions: каталог, разбор вставленной ссылки (браузерный
  URL, clone URL, SSH-ремоут, голое owner/name), тесты
- GET /solutions, GET /solutions/{slug}, GET /git/parse-repo-url; отдельного
  install-эндпоинта нет намеренно — деплой идёт существующим путём
- фронт: карточки из каталога вместо списка в файле + поле ссылки с
  необязательной веткой (полно репозиториев с master по умолчанию)
- swagger перегенерён

Проверено: go build, go vet, go test ./internal/... зелёные. Фронт типами НЕ
проверен: npm ci в этом окружении упирается в приватный Nexus (401).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CSUPisoFBFxh5E1vPHoED
…ой кнопкой

Граница между облаком и VPS у нас почти стёрлась, значит каталог обязан
работать и на VM — и не вторым каталогом, а тем же.

Изучен Бегет: 59 приложений, поиск по названию, иконка, «Установить»,
затем «выбрать домен»; домен и Let's Encrypt — отдельным шагом в разделе
«Сайты». Модель у них «приложение = образ, запечённый на VM», и её границы
и есть наша возможность: собрать чужой исходник, приложить домен и БД
внутри той же кнопки, положить результат в облако или в свою VM.

Дизайн сводит три сценария (тык по картинке n8n с доменом и БД; «post…» →
Postgres; URL произвольного репозитория в свой контур) к трём сущностям, ни
одна из которых не знает про «облако против VPS»: запись каталога,
нейтральная к рантайму, где единственное различие — поле delivery
(image | source); цель — Environment, а рантайм выбирает рендерер уровнем
ниже; и один резолвер строки, дающий каталог, ссылку и поиск по GitHub в
одной выдаче.

Отдельно — проверенный по коду список швов, которые мешают этому быть by
design: доменов и TLS на VM-треке нет вовсе; у приложения из сборки нет
тома; именованные тома на VM пинятся external, но никто их не создаёт;
needs как декларации зависимости не существует; поиска по GitHub нет.
Порядок работ идёт по швам, а не по экранам.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CSUPisoFBFxh5E1vPHoED
Агрегатный compose пинит именованные тома `external: true` — чтобы свежий
стек никогда не подсунул пустой том поверх живых данных. Но создавать их
было некому: на чистой VM первый же деплой приложения с состоянием упирался
в отсутствующий внешний том и падал ошибкой про неизвестный volume. Старые
compose-приложения этого не замечали, потому что их тома достались от уже
работавшей на машине нагрузки.

Теперь список томов едет вместе с операцией деплоя, а воркер создаёт
недостающие через Docker API Portainer. Идемпотентно по контракту самого
Docker: создание тома с существующим именем возвращает его нетронутым, не
пересоздаёт и не чистит, — поэтому вызов безопасно делать на каждом деплое,
а не только на первом.

Список берётся из рендерера (AuthoredNamedVolumes), а не выводится заново из
compose-файла: рендерер и решает, какие тома внешние, и два независимых
вывода рано или поздно разойдутся — а разойдутся они в виде упавшего деплоя
на приложении с данными. Тест держит эти два места вместе: то, что пинит
агрегат, ровно то и создаётся.

Откат и adopt передают пустой список осознанно: там тома на машине уже есть
(в adopt это вообще вся премиса сохранности данных).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CSUPisoFBFxh5E1vPHoED

keksmd commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Блокер: Jenkins не может запустить сборку, причина не в этом PR

continuous-integration/jenkins/pr-head два раза подряд отдаёт error — «This commit cannot be built» (сборки #3 и #4). Это не упавшие тесты, а невозможность запустить саму сборку, поэтому в логе изменений кода искать нечего.

Что проверено и исключено:

  • Конфликт с базой — нет. Ветка расходилась с main, конфликтов было 0; я всё равно влил main (1673d40), чтобы отдать Jenkins заведомо мержабельную ревизию. Ошибка повторилась на новой ревизии с тем же текстом — значит дело не в merge-ревизии.
  • Сгенерированный swagger после слияния — перегенерирован (swag init), гейт покрытия OpenAPI зелёный.
  • Кодgo build ./... и тесты backend, gitops-agent, portainer-agent зелёные локально; CodeQL по go/js/python/actions на PR зелёный.

Чего не могу сделать сам: лог Jenkins недоступен — jenkins.dada-tuda.ru отдаёт 403 из окружения, где идёт работа. Без него дальше только гадать, а гадать и пушить в PR наугад я не буду.

Что нужно, чтобы сдвинуть: кусок лога сборки #4 (или скрин страницы) либо доступ к Jenkins. Наиболее вероятные направления, судя по тому, что ошибка на уровне планирования сборки, а не выполнения: индексация ветки в multibranch-джобе, доступность агента/пода сборки, права джобы на PR-head.

Со стороны содержимого PR оставшийся незакрытый пункт один и он записан в описании: фронт не прогнан через tsc/lint, потому что npm ci в этом окружении упирается в приватный Nexus (401).


Generated by Claude Code

keksmd added 12 commits August 6, 2026 17:47
Сборка PR-37 #4 падала не на 'this commit cannot be built', как считала
прошлая сессия, а на стадии 'Go format check': gofmt -l показывал
gitops-agent/internal/renderer/renderer_test.go. Стадия идёт до тестов,
поэтому дальше пайплайн не доходил и выглядел как отказ запуститься.

Not-tested: фронтовые стадии пайплайна (локально tsc и eslint зелёные).
Каталог отвечал только тем, кто уже знает, как называется запись.
Новичок печатает «доска», человек за базой печатает «post», про
вставляет ссылку — раньше два из трёх получали пустоту.

Резолвер отвечает всем троим одним ранжированным списком: вставленная
ссылка бьёт любое нечёткое совпадение (ссылка — это уже решение), ссылка
на репозиторий каталога отдаёт проверенную запись с веткой и портом, а не
голый репозиторий, каталог и управляемые ресурсы идут выше поиска, потому
что у них есть проверенная спека сборки, а у находки — только звёзды.

Поиск GitHub живёт за кешем: 30 запросов в минуту на весь кластер по IP
источника, а интерактивное поле — самый быстрый способ их сжечь.
Запросы короче трёх символов до поиска не доходят вовсе.

Constraint: отказ поиска не роняет запрос — каталог остаётся, поднимается
search_failed. Пустой список читается как «платформе нечего вам дать», а
не как временная проблема наверху.
Rejected: Redis в управляемых ресурсах — в k8s managed DB это Postgres,
запись резолвилась бы на обоих рантаймах, а ставилась на одном.
Not-tested: ни одна из четырёх записей каталога ещё не собиралась нашим
пайплайном.
Блок ниже каталога умел ровно одно: принять ссылку на GitHub. Тот, кто
не знает названия репозитория, и тот, кому нужна база, упирались в поле,
на которое им нечего вставить.

Поле теперь принимает что угодно и показывает ранжированный список от
резолвера: проверенная запись каталога, управляемая база, репозиторий с
поиска. У каждой строки честная плашка — «проверенный» значит, что спеку
сборки кто-то проверял, «с GitHub» значит имя и звёзды, дальше пайплайн
определяет сам.

Запрос дебаунсится на 350 мс: у поиска GitHub 30 запросов в минуту на всю
платформу, и каждое нажатие клавиши без задержки — прямой путь их сжечь.
Управляемая строка ведёт на страницу баз, а не притворяется сборкой.

Rejected: подставлять ?engine= в ссылку на базы — страница его не читает,
получилась бы ложь в URL.
… тремя

Консоль сама собирала последовательность: привязать репозиторий, заказать базу,
запустить сборку. Это не решение, а порядок действий — и когда середина падала,
разбирать половину установки приходилось клиенту. Теперь порядок живёт на
сервере: POST /solutions/install линкует репозиторий, заказывает управляемую базу,
если запись каталога объявила `needs: [postgres]`, и ставит первую сборку.

Общие ядра вынесены, а не скопированы: linkGitRepo даёт те же дефолты и разбор
installation_id, что и connect-repo, createManagedDatabase — ту же генерацию
пароля и подстановку DATABASE_URL, что и ручной заказ базы. Вторая копия правил
VM-трека молча разъезжается, а разъехавшись, выдаёт клиенту приложение, которое
не видит собственную базу.

Failure не откатывается: если база не заказалась, привязка остаётся, и ответ об
этом говорит. Привязку клиент видит и может переиспользовать, а тихое удаление
превращает чинимую половину установки в загадку.

Constraint: имя базы выводится из имени приложения (`app-db` / `app`), потому что
клиент, ставящий готовый проект, о нём мнения не имеет; префикс `db-` нужен,
когда имя начинается с цифры — validatePgName требует букву.
Rejected: заказывать базу до привязки репозитория — тогда падение привязки
оставляет висящую базу, которую клиент в консоли не свяжет ни с чем.
Not-tested: сборка реально не гонялась — тест проверяет строку builds в статусе
queued, а не то, что Jenkins собрал образ.
Карточка сама держала порядок: linkRepo, потом trigger build, а при падении
второго оставляла привязанный репозиторий и никакой сборки. Порядок переехал на
сервер (POST /solutions/install), карточка теперь только называет приложение —
имя с суффиксом остаётся на клиенте, иначе повторная установка того же проекта
столкнётся с занятым именем.

Каталожная строка передаёт только slug: спека сборки (ветка, порт, каталог,
профиль) живёт в бэкенде, и консоль перестала возить её копию, которая может с
ним разойтись. Вставленный репозиторий по-прежнему проходит детект порта на
клиенте — там это единственное, что у нас про него есть.
Аттач кастомного домена на VM рендерил 443-vhost на путь
/etc/nginx/certs/live/<host>/fullchain.pem, который до прихода оператора не
существует. nginx на таком конфиге не стартует вовсе — падает весь ingress
машины, а не один новый домен: соседние сайты уходят в 502 из-за чужого
домена, который ещё даже не делегирован.

VMExtraHost получает TLSReady. Пока сертификата нет, хост отдаётся по
plain http на своё app:port — домен работает сразу, TLS доезжает потом.

ACMEWebroot добавляет location /.well-known/acme-challenge/ во все
listen-80 блоки, а редирект уезжает внутрь location / — серверный
`return 301` отрабатывает в rewrite-фазе, до выбора локации, и съел бы
http-01 челлендж целиком.

Constraint: nginx не умеет условный include, поэтому «есть ли сертификат»
решается на рендере, а не в контейнере.
Not-tested: выдача сертификата и продление — отдельный шаг, dbwatcher
пока TLSReady не выставляет, поведение прода не менялось.
…ется сам

Домен привязывают раньше, чем на него делегирован DNS, и раньше, чем на
http-01 вообще есть кому ответить: челлендж отдаёт тот самый vhost, которого
ещё нет. Поэтому 443-блоки больше не едут в загрузочном конфиге — они
доставляются отдельными файлами, а энтрипоинт кладёт в glob-включаемый
/etc/nginx/tls.d только те, чей сертификат реально лежит на диске. Пустой glob
для nginx не ошибка, и это единственное, что делает отсрочку безопасной.

Тот же sync крутится по таймеру с reload, поэтому сертификат, приехавший
после деплоя (или продлённый), включается без ре-рендера стека: у воркера нет
способа посмотреть на файловую систему ВМ, значит решение обязано жить в
контейнере. Битый блок не проходит nginx -t и просто не перезагружается —
конфиг остаётся устаревшим, а не сайт мёртвым.

Выпуск — сервис certbot рядом с ingress: появляется с первым доменом,
исчезает с последним, отдельный сертификат на хост (общий SAN пришлось бы
перевыпускать на каждый чужой аттач), падения не фатальны и повторяются раз в
полчаса — это и укладывается в лимит LE 5 неудач на хост в час, и служит
продлением.

Rejected: считать наличие сертификата на рендере — воркер про диск ВМ ничего
не знает, а у уже привязанных хостов это молча понизило бы рабочий TLS до
http.
Constraint: nginx не умеет условный include и не стартует при отсутствующем
ssl_certificate — отсюда и tls.d, и отсрочка.
Not-tested: живой выпуск LE на песочничной ВМ; проверено юнитами, включая
прогон энтрипоинта настоящим /bin/sh с подменой только префикса путей и
бинаря nginx.
…рена, а не вспомнена

Формы (root Dockerfile / Procfile / подкаталог / ничего) взяты запросом к
raw.githubusercontent.com по каждому пину: первичный список расходился с
реальностью у трети записей — Stirling-PDF, gotify, uptime-kuma, n8n, mealie,
linkding и другие не имеют Dockerfile в корне, хотя «по памяти» имели.

Rejected: библиотеки и фреймворки (django, flask, starlette, vite, nuxt, kit,
remix, hugo, tailscale) — сборка их корня не является подъёмом приложения,
такие строки мерили бы шум, а не автоподниматель.
Constraint: api.github.com с этой машины отвечает нестабильно, поэтому пины
взяты через git ls-remote, а состав корня — через raw.
Not-tested: сами ступени 2-4 ещё не гонялись, это следующий пункт очереди.
Ступень 1 гоняется через backend/cmd/autodeploy-detect, который зовёт тот же
sourcedetect, что работает на загрузке исходников. Питоновская копия детекта
согласилась бы с корпусом ровно там, где боевой код с ним расходится.

Результат: 22/50. Найден настоящий баг: корневой Dockerfile-симлинк
(vaultwarden, netdata) отбрасывается, потому что listTarGzEntries берёт только
tar.TypeReg; снаружи дефект не виден, так как raw.githubusercontent
разыменовывает симлинк и отдаёт 200.

Constraint: ступени 2-4 через прод недоступны (нет кредов Console API, выписывать
себе новые запрещено), а шаблоны Dockerfile живут в Jenkins shared library вне
репозитория — такие строки помечены template_only, а не «провал».
Rejected: версия «упёрлись в потолок maxEntries=500» — обе симлинк-записи лежат
внутри потолка (позиции 28 и 271), проверено.
Not-tested: сборка и подъём ни одного репозитория корпуса пока не гонялись.
only add a dependency the run does not need.
"""
repos, cur = [], None
for line in open(path, encoding="utf-8"):
keksmd added 4 commits August 7, 2026 02:07
Детектор аплоада отвечал «манифеста нет» там, где репозиторий говорит о себе
прямо: корневой Dockerfile-симлинк, EXPOSE через переменную, порт в compose,
go.mod и pom.xml. На корпусе из 50 открытых проектов ступень 1 выросла с 22/50
до 42/50; каждая правка — новый источник факта, а не новая догадка по стеку.

Ранжирование на не-Dockerfile пути стало явным: маппинг compose (факт) →
Procfile/railway.json (утверждение, что фиксированного порта нет) → дефолт
фреймворка (конвенция).

Rejected: угадывать порт по стеку там, где репозиторий его не заявляет —
 caddy, pocketbase, photoprism и n8n оставлены провалом сознательно.
Constraint: шаблоны Dockerfile для репозиториев без своего живут в Jenkins
 shared library вне этого репозитория, поэтому ступени 2-4 локально не гонялись.
Directive: имена фреймворков и дефолтные порты держать в синхроне с
 build-agent frameworkDefaultPort — иначе два пути платформы разойдутся.
Not-tested: сборка и подъём приложений через прод-конвейер (нет кредов
 Console API, выписывать себе запрещено).
… пустую выдачу

Два места, где подсказка врала о происхождении строки.

Ссылка, вставленная руками, приходит без `from` и получала бейдж
«проверенный» — тот же, что у каталожной записи с собранным нами build-spec.
Теперь бейдж читает `kind`: каталог — «проверенный», поиск — «с GitHub»,
вставленная ссылка — «по ссылке».

Поиск по GitHub при незаданном BUILD_AGENT_URL просто не выполнялся, а ответ
шёл как `searched=true, search_failed=false` — то есть «мы искали и ничего нет»
вместо «мы не искали». Локально это прятало весь сценарий «печатаю n8n».

Проверено на локальной консоли (backend :18099 + build-agent :18098 против
свежей console_demo): `n8n` даёт n8n-io/n8n первым с аватаркой, `post` — сначала
управляемый PostgreSQL, потом GitHub, вставленная ссылка на gitbucket — одну
строку «по ссылке».

Not-tested: обработчик без билд-агента не покрыт go-тестом — тесты ResolveSolution требуют реальной БД.
Прогон на живой консоли, а не на API-заглушке: три сценария владельца прошли,
и по дороге нашлись два места, где подсказка врала о происхождении строки —
оба закрыты 34047d9. Отдельно записано, чего демо не доказывает: кнопка
«Развернуть» не нажималась, установка уходит в Jenkins и требует прод-кредов.
main за 38 коммитов дописал в те же две ручки то, что ветка успела вынести
в переиспользуемое ядро: квоты/шардирование баз и признак worker у репозитория.
Переносить надо было не текст конфликта, а смысл — обе новые вещи легли внутрь
createManagedDatabase и linkGitRepo, иначе установка готового проекта тихо
теряла бы tier, shard и worker, которые видит обычная ручка.

Rejected: оставить сторону main целиком — это откатило бы вынос ядра, ради
которого ветка и существует.
Constraint: docs/ — генерируемые, руками не сводились; пересобраны
`swag init -g cmd/server/main.go --parseInternal -o internal/api/docs`.
Directive: любая новая логика в CreateServiceDatabase/ConnectGitRepo идёт
в ядро (createManagedDatabase/linkGitRepo), а не в тело ручки — иначе
установка готового проекта снова разойдётся с ручным путём.
Not-tested: worker-репозиторий и шардирование баз через живой стенд не
гонялись — только сборка, go test ./... и tsc.
@keksmd
keksmd merged commit d9ea583 into main Aug 7, 2026
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants