ADR 0135 — Шлюз образа от Arquiteto (RN-105) начинает действовать во всех трёх режимах выполнения
Контекст
Со времён ФАЗЫ 25 (ADR 0065/RN-105) вкладка Code проекта отвечает 409,
пока никто не решил, какой образ контейнера он выполняет, —
artifact.project_image, испускаемый Arquiteto (choose_project_image) и,
со времён ADR 0133 (RN-491), также Infra, выбирающей среди кандидатов
собственной маршрутизации Arquiteto. Порядок задан пользователем: контейнер — это то, что
придаёт смысл чтению кода там, и открыть чтение до того, как появится, где
выполнять, научило бы тому, что шлюз декоративен.
ADR 0072 (RN-169), пересмотренный ADR 0104 (RN-421), освободил от этого шлюза
проекты mounted/runner: поскольку эти два режима не поднимают СОБСТВЕННЫЙ
контейнер — код живёт в папке пользователя, смонтированной в уже существующие
контейнеры (mounted) или подтверждённой CLI, работающим вне их (runner), —
исходное правило вечно отвечало 409 в проекте, где решения
Arquiteto никогда бы не случилось. Освобождение позволяло не закрывать вкладку побочным
эффектом.
Исходный план этой поставки («Выполнение в настоящем контейнере», Часть 1) объявил это освобождение решением #5 к пересмотру, уже принятым до появления этого PR: «Portão RN-105 passa a valer nos TRÊS modos, container e aba Code. Custo aceito: exp001/exp002 perdem a aba Code até o Arquiteto decidir.» Этот ADR исполняет это решение и фиксирует, чего оно стоит и чего оно НЕ меняет.
Исходное освобождение смешивало два вопроса, которые ADR 0130 (broker
контейнеров) и ADR 0133 (выбор Infra) разделили, хотя api за этим не
последовала: «поднимает ли этот проект контейнер на СЕРВЕРЕ?» (нет, в
mounted/runner — вопрос о МЕХАНИЗМЕ) и «имеет ли смысл требовать, чтобы
кто-то решил образ проекта, прежде чем открыть чтение
кода?» (да, во всех трёх режимах — вопрос о ПОЛИТИКЕ). Освобождение
отвечало на второй вопрос ответом на первый.
Решение
ВСЕ ТРИ режима выполнения (container/mounted/runner) начинают требовать
решённый artifact.project_image, прежде чем откроется вкладка Code.
ReadProjectCodeUseCase.portaoDoContainer перестаёт проверять executionMode —
шлюз становится безусловным, так же как он уже был безусловен внутри
режима container. ProjectCodeTab (web) перестаёт обращаться с mounted/runner
как с особым случаем: три режима следуют одному и тому же useQuery
загрузка/ошибка/sem_decisao/решено, что УПРОЩАЕТ компонент —
ветка modoLocal и условный enabled по режиму исчезают.
Принятая цена, со всей ясностью: каждый СУЩЕСТВУЮЩИЙ проект mounted/runner
без решённого artifact.project_image — включая реальные проекты
dogfooding этого репозитория, exp001 и exp002, — ТЕРЯЕТ доступ к
вкладке Code в момент деплоя этого PR, пока Arquiteto (или
Infra) не решит для него образ. Это ДЕЙСТВИЕ ОПЕРАТОРА, требуемое
ПОСЛЕ деплоя, а не прозрачное исправление, которого пользователи даже не заметят, —
поэтому ветка рождается breaking/, а не bugfix/, хотя по содержанию это
исправление пробела: соглашение репозитория (CLAUDE.md) явно говорит,
что содержание не решает префикс, когда изменение требует действия оператора.
Версия поднимается MAJOR как механическое следствие scripts/ci/version.ts,
а не по ручному решению — номер потом не исправляют.
Жизненный цикл контейнера (project_containers) тоже перестаёт отказывать
по режиму, но по более узкой причине.
RegistrarTransicaoDeContainerUseCase отвечал 400 для любого проекта
вне режима container — проверка, которая на самом деле принадлежала
broker (который уже отказывает по СОБСТВЕННОЙ и более конкретной причине), а не этому
сценарию использования. 400 уходит; таблица начинает регистрировать строку для всех трёх
режимов, через ТУ ЖЕ воронку provisioning, которая уже читала решение об образе
(теперь также требуемое для mounted/runner).
Что остаётся НЕВОЗМОЖНЫМ, и по намеренному решению, — это чтобы mounted/runner
по-НАСТОЯЩЕМУ дошли до running. Это применяется в одном месте —
ContainerBrokerPort.start() отказывает с ModoDeExecucaoNaoSuportadoError
(apps/broker/src/operacoes.ts): broker работает на СЕРВЕРЕ и не видит
папку пользователя, где живёт код mounted/runner, поэтому собрать
контейнер там значило бы собрать его из пустой папки. Эта политика НЕ
меняется в этом PR — комментарий класса ошибки уже указывал на этот ADR
(«o PR do portão nos três modos (1.7) revisita as duas ao mesmo tempo»), и
пересмотр подтверждает, что она верна как есть.
Исследовано и решено, без изменения кода: ExecuteContainerStartUseCase
вызывает ContainerBrokerPort.start() ДО любого
RegistrarTransicaoDeContainerUseCase.execute(..., 'provisioning') — если
broker отказывает (обычный случай для mounted/runner), сбой становится
container.start_failed с названной причиной (BrokerRecusouError,
НИКОГДА не crash и не тихий fallback), и переход жизненного цикла
так и не вызывается. То есть по реальному пути одобрения
(container_start) ни одна строка project_containers не рождается для
mounted/runner, даже при удалённом 400 по режиму — broker уже является
действующим шлюзом, более точным, чем старый 400 (он знает, МОЖЕТ ЛИ собрать
контейнер; сценарий использования знал лишь РЕЖИМ). Поэтому этот PR НЕ добавляет
вторую проверку режима ни в propose-action.use-case.ts, ни в
ExecuteContainerStartUseCase: дублирование создало бы второе место для
расхождения с первым — тот же урок, который уже фиксировал сам комментарий broker.
Также исследовано: ни propose_container_start (инструмент Infra
Lead, apps/engine/lib/engine/infra/tools/propose_container_start.ex), ни
GetInfraContextUseCase сегодня не ограничивают по executionMode — Infra Lead
МОЖЕТ предложить container_start для проекта mounted/runner, и
предложение проваливается, только когда кто-то одобряет, а broker отказывает. Это
ПРЕДСУЩЕСТВУЮЩИЙ пробел (не внесённый этим PR), который тратит цикл
человеческого одобрения на действие, обречённое на провал, — но проваливается оно ясно,
с названием, никогда молча. Исправление потребовало бы трогать prompt/инструкцию
Infra Lead в engine, вне охвата этого среза (API/домен); он
объявлен ниже, а не исправлен мимоходом.
Последствия
exp001/exp002 и любой другой проект mounted/runner без решённого
образа теряют вкладку Code, пока кто-нибудь не запустит для них Arquiteto (или выбор
Infra). Это принятая цена решения #5 исходного плана,
а не побочный эффект, обнаруженный потом.
Реальное сдерживание против подъёма контейнера mounted/runner на сервере
не изменилось. Им по-прежнему является broker (ModoDeExecucaoNaoSuportadoError),
а не 400 от api, — на самом деле оно СТАЛО точнее, потому что теперь это решает одно
место, а не два.
project_containers теоретически может зарегистрировать строку provisioning для
mounted/runner, — но ни один путь production сегодня не вызывает
RegistrarTransicaoDeContainerUseCase.execute(..., 'provisioning') для
этих режимов, не пройдя сначала через broker, который отказывает. Если новый путь
когда-нибудь обойдёт ExecuteContainerStartUseCase (вызывая сценарий использования
напрямую), он унаследует ту же защиту: obterImagem.execute по-прежнему требует
решения, и ничего больше здесь не меняется.
Infra Lead может предложить container_start для проекта mounted/
runner, зная, что оно провалится, — предсуществующий пробел, не внесённый
и не закрытый этим PR, объявленный в CLAUDE.md («Estado atual e aberto»),
чтобы не потерялся.
Рассмотренная и отвергнутая альтернатива: добавить проверку режима в
propose-action.use-case.ts или в ExecuteContainerStartUseCase, чтобы
отказывать в container_start для mounted/runner ДО вызова broker.
Это избежало бы упомянутого выше напрасного цикла одобрения — но перенесло бы
знание о том, КТО может собрать контейнер (сегодня это знает только broker,
через PROJECT_WORKSPACES_HOST_ROOT), внутрь api, у которой нет этого
контекста и которая повторила бы правило, уже существующее в другом месте. Отвергнуто
пока: цена — избегаемый цикл одобрения, а не некорректность, — и сообщение о
сбое уже называет причину. Зафиксировано как кандидат в UX, а не как
исправление ошибки, если объём бесполезных предложений это оправдает.