ADR 0144 — Второй корень broker и дискриминированный локатор, который его выбирает
Контекст
Broker (ADR 0130) — единственный процесс продукта, говорящий с демоном Docker
на сервере, и сдерживает его одна-единственная вещь: он не принимает
спецификацию контейнера. Он получает projectId и одну из пяти операций,
идёт в api ПРОЧИТАТЬ решение Arquiteto и сам СОСТАВЛЯЕТ образ, сеть, ресурсы и
единственное монтирование. Ни один путь не пересекает сеть — api присылала
workspaceDirName, а broker конкатенировал его со СВОИМ корнем
(PROJECT_WORKSPACES_HOST_ROOT).
У этой формы был один корень, потому что было одно-единственное место, где папка проекта
могла находиться с точки зрения сервера: /data/project-workspaces, папка,
УПРАВЛЯЕМАЯ продуктом, режима container. Проекты mounted и runner
оставались снаружи, и broker отклонял оба у источника с
ModoDeExecucaoNaoSuportadoError.
ADR 0141 изменил факт мира, на котором держалась половина этого отказа. Каждый
проект в режиме Pasta montada начинает жить под одной базой
(BRABO_PROJECTS_BASE), смонтированной по идентичности в api и engine, а значит
достижимой для демона сервера. Отказ broker касался ГЕОМЕТРИИ — «его
код находится на машине, которую я не вижу», — и геометрия mounted
перестала быть такой. У runner не изменилось ничего.
Это открывает вопрос, на который форма с одним корнем не умеет ответить: для данного сегмента — относительно какого корня он действует?
Очевидный соблазн — чтобы api присылала абсолютный путь, а broker его монтировал. Это
уничтожило бы ADR 0130 целиком: сдерживание процесса, эквивалентного root на
хосте, снова зависело бы от корректности ВЫЗЫВАЮЩЕГО, а это и есть определение отсутствия
сдерживания. Второй соблазн — чтобы broker угадывал: использовал базу, когда
режим — mounted. Это работает до того дня, когда появится третье место, а
до тех пор прячет решение в if по enum, владельцем которого broker не является.
Решение
Broker получает ВТОРОЙ корень, BRABO_PROJECTS_HOST_BASE, а api начинает
присылать ДИСКРИМИНИРОВАННЫЙ локатор, говорящий, относительно какого из двух действует
сегмент.
localizacao:
| { tipo: 'gerenciada'; segmento: string } // PROJECT_WORKSPACES_HOST_ROOT
| { tipo: 'montada'; segmento: string } // BRABO_PROJECTS_HOST_BASE
| { tipo: 'indisponivel'; motivo: string } // nenhuma raiz alcança
Инвариант остаётся ровно там, где был. Сеть по-прежнему пересекает лишь та половина, которую не покрывает корень broker; api НЕ знает, где эти корни находятся на хосте, а broker ничего не знает о проекте. Ни одна из сторон сама по себе не может записать произвольный путь — изменилось то, что api начала говорить, КАКОЙ корень, вместо того чтобы корень был один, а ответ — неявным.
Две переменные, а не одна, и без наследования между ними. Управляемый корень
именуется workspace_dir_name (UNIQUE), а база именуется пользователем. Одно и то же
имя указывает на разные папки в двух местах, и ничто в схеме не мешает
коллизии — это первая причина, по которой ADR 0141 отказался объединять
BRABO_PROJECTS_BASE с PROJECT_WORKSPACES_HOST_DIR, и она действует здесь
в точности так же. Поэтому BaseDeProjetosNaoConfiguradaError — СОБСТВЕННЫЙ класс, по
тому же шаблону, что RaizDeWorkspacesNaoConfiguradaError: источник infra, сообщение,
называющее переменную, ни один контейнер не затронут. Падение на другой корень по умолчанию
смонтировало бы папку ДРУГОГО проекта, и контейнер поднялся бы — с неверным кодом
внутри, без каких-либо указаний на причину.
Три варианта, а не два. indisponivel — не замаскированный null: это
состояние, в котором ни один корень ЭТОГО сервера не достигает папки, и у него два
разных исправления. Проект runner (папка на машине пользователя — исправление
— brabo-runner, с той стороны) и УСТАРЕВШИЙ проект mounted, созданный
до базы (исправление — перенести папку). null отправлял бы оператора не туда
в половине случаев. Папка, которая САМА является базой, тоже попадает сюда,
вместо того чтобы стать пустым сегментом: <raiz>/ смонтировал бы всю базу —
папку всех смонтированных проектов — внутрь контейнера одного.
Список режимов, которые обслуживает broker, — это список РАЗРЕШЁННЫХ, а не отклонённых
(container, mounted). Новый режим в enum api рождается отклонённым, с
сообщением, вместо того чтобы молча рождаться принятым и попадать в сборку без корня,
который бы его разрешил.
Три барьера над конкатенацией, а не один. api отклоняет то, что не
под базой (segmentoSobABaseDeProjetos); packages/docker-port отклоняет
сегмент, который не относителен (segmentoDeProjetoValidado: .., абсолютный,
пустой, двойной слэш, NUL); а результат raiz + segmento всё ещё проходит через
raizDeProjetoValidada, прежде чем стать -v, как всегда проходил. Проверять
сегмент и не проверять конкатенацию значило бы доверять строковой арифметике —
а проверка на стороне broker существует именно потому, что он не может
предполагать, что api корректна.
Следствие на стороне api: ветвление container_start становится по
НАЗНАЧЕНИЮ. container и mounted идут в broker; только runner идёт в
runner. А container_stop/container_remove меняются вместе, обязательно:
поднять на сервере и попросить остановить на машине пользователя значило бы оставить работающим,
без способа остановить, то, что работает.
mounted ВЫБИРАЕТ образ, как container. Это не симметрия: это единственный
дизайн, работающий с этой стороны. Broker составляет из
artifact.project_image, ЗАБИРАЯ его в api, — выбор Infra, который не
записывался бы в этот артефакт, был бы инертен, и контейнер поднялся бы с образом
Arquiteto, тогда как одобренный человеком payload говорил бы другое. Это
буквально аргумент ADR 0133, применённый ко второму режиму. Путь
runner может читать действующий, потому что там api ПРИСЫЛАЕТ поля spec по каналу;
артефакт — не тот источник, к которому обращается другая сторона.
Последствия
Режим Pasta montada перестаёт требовать подключённого brabo-runner, чтобы иметь
контейнер. Именно это требование делало несогласованной последовательность, которую просит план
владельца продукта, — выбрать mounted при создании, и чтобы контейнер поднимался раньше
dev agents.
runner остаётся один на пути runner, и это дизайн, а не
остаток: broker (и сервер) по-прежнему не видят его папку. container
остаётся единственным режимом, поднимающим контейнер на сервере, не завися от
базы, настроенной оператором; mounted поднимается на сервере, когда она
есть.
Объявленная цена 1: у установки может быть один корень и не быть другого. Это законное состояние, и каждая операция говорит, какого не хватает, вместо того чтобы обе падали в общий отказ. Цена — два класса ошибок там, где мог бы быть один с параметром, — принята, потому что ошибку читает тот, кому нужно знать, КАКУЮ переменную задать.
Объявленная цена 2: broker теперь знает второй формат сегмента.
gerenciada — это имя без /; montada — относительный путь, который может иметь
уровни. nomeDeWorkspaceValidado (который служит ИМЕНИ контейнера и намеренно отклоняет /)
перестаёт годиться для проверки пути, и рядом с ним рождается
segmentoDeProjetoValidado. Это два вопроса, которые стали иметь
разные ответы — «как называется контейнер» и «где находится папка», — и
одна функция для обоих работала, пока они совпадали.
Чего этот ADR НЕ решает. Не материализует ни одной папки (это отложенная
проверка, соседний PR). Не меняет шлюз образа — RN-105 уже действует для всех трёх
режимов со времён RN-494/ADR 0135. Не меняет, кто ПРЕДЛАГАЕТ container_start: Infra
Lead по-прежнему может предложить для проекта без подключённого runner и без решённого образа,
пробел, объявленный со времён RN-494. Не меняет число операций
broker: их по-прежнему пять, и шестая по-прежнему — продуктовое решение с
ADR. И не публикует образ broker в GHCR — четыре образа ADR 0119 остаются
четырьмя.
Ссылается на 0130 (broker составляет spec), 0133 (выбор должен быть записан, чтобы иметь эффект), 0137 (другой путь выполнения) и 0141 (база, сделавшая это возможным). Ни один из них не редактируется.