Перейти к основному содержимому

ADR 0142 — Проверка смонтированного workspace откладывается до материализации

Контекст​

ADR 0141 решил, ГДЕ живёт проект mounted — внутри BRABO_PROJECTS_BASE, единой базы, смонтированной по идентичности в api и engine, — и явно оставил следующему PR другую половину: когда эта папка начинает существовать и кто проверяет, что она годится.

Требование, вынуждающее задать вопрос, исходит от владельца продукта, и оно буквально:

если это Pasta montada, bind-mount должен создаваться ПОСЛЕ решения архитектора.

Сегодняшний код делает это невозможным. validarCaminhoDeWorkspaceLocal (project-workspaces-root.ts) выполняется при СОЗДАНИИ проекта и задаёт три дисковых вопроса — существует ли папка? является ли она каталогом? может ли процесс писать в неё? — и отказывает с 400, когда любой из них не проходит (RN-170/RN-422). Создание проекта — ПЕРВЫЙ экран потока; решение Arquiteto (project_image, module_map, module_routing) происходит через много сессий. Требовать готовую папку при создании — значит требовать её до того, как существует хоть какое-то решение.

И проблема не гипотетическая и не эстетическая. Это то, что отделяет mounted от выбора первого класса: мастер создания предлагает <base>/<slug do nome>, а папка, предложенная нами, по построению — папка, которой ещё не существует.

Режим runner уже решил ровно это противоречие, и решил хорошо (RN-423, ADR 0104): он проверяет при создании только ЛЕКСИКУ — абсолютный, без .., вне системной папки, без пересечения с checkout Brabo — и оставляет диск тому, у кого есть полномочия над ним, — CLI, подключающемуся на машине пользователя. Разница между двумя режимами никогда не заключалась в том, что считается допустимым путём; она в том, когда и кто подтверждает дисковую часть.

Решение​

mounted начинает проверять при создании только ЛЕКСИКУ плюс базу, а папка МАТЕРИАЛИЗУЕТСЯ, когда она нужна тому, у кого есть полномочия над диском.

При создании и преобразовании: лексика + база, ноль I/O​

validarExecutionModeEWorkspacePath перестаёт трогать диск в ветке mounted. Она начинает требовать две вещи:

  1. тот же лексический предикат, что у runner (caminhoDeWorkspaceLocalValido), который действует всегда и поэтому выполняется также при каждом чтении;
  2. нахождение внутри BRABO_PROJECTS_BASE (dentroDaBaseDeProjetos, ADR 0141).

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

Проект рождается, как и runner, с workspaceVerifiedAt: null.

materializarWorkspaceMontado и два места, которые её вызывают​

Отложенная проверка снова происходит в одной функции, mkdir -p плюс три привычных дисковых вопроса, с отказом за нахождение вне базы ДО mkdir — иначе путь, записанный в обход продукта, заставил бы api создавать папку в любом месте, куда она дотягивается.

1. ExecuteContainerStartUseCase — обычное место. Когда Infra поднимает контейнер проекта mounted, папка создаётся, доказывается её доступность для записи, и workspace_verified_at ставится тем же путём, который использует ConfirmProjectWorkspaceUseCase. Именно здесь требование «после решения Arquiteto» выполняется буквально: container_start существует только после того, как появился решённый образ.

Сбой здесь — это НАЗВАННЫЙ failed, никогда не throw, никогда не 500, — та же дисциплина, которой уже следуют BrokerIndisponivelError/RunnerNaoConectadoError в том же файле, — и жизненный цикл не доходит до отметки provisioning. Отметить provisioning и лишь затем обнаружить, что писать нельзя, оставило бы строку project_containers утверждающей состояние, которого никогда не было.

2. ConvertProjectExecutionModeUseCase — исключение, и оно объявлено. У преобразования проекта в mounted нет шага контейнера, к которому можно привязать работу: это изолированное действие, и сразу после него оно ПЕРЕМЕЩАЕТ permissions.json в permissionsFilePath(localNova), что в mounted — projectScopeRoot, то есть папка пользователя. Перемещение файла в несуществующую папку не удаётся. Поэтому преобразование материализует в момент решения — до транзакции, чтобы отказ не оставил открытой транзакции, — и становится 400 с поучающим сообщением.

Что НЕ меняется, и это самая лёгкая регрессия здесь​

Правило базы не входит в caminhoDeWorkspaceLocalValido. Этот предикат выполняется при КАЖДОМ ЧТЕНИИ, через projectScopeRoot, на горячем пути: область терминала (ADR 0055), permissions.json, вкладка Code. УСТАРЕВШИЙ проект mounted — созданный, когда bind-mount был строкой compose на каждый проект, а значит вне базы, — начал бы взрываться с LocalizacaoDeProjetoInvalidaError при простом ЧТЕНИИ, хотя никто его не трогал.

База — правило создания и преобразования, где перед экраном есть кто-то, кто ещё может выбрать другой путь. Лексика — навсегда. Для этого есть тест отсутствия регрессии и комментарий в самой функции, объясняющий почему.

Без migration, и CHECK базы остаётся нетронутым​

mounted по-прежнему записывает НЕнулевой workspace_path — составленную подсказку <base>/<slug>, — поэтому (execution_mode <> 'container') = (workspace_path IS NOT NULL) остаётся выполненным байт в байт. Откладывание ПРОВЕРКИ никогда не трогает инвариант ПАРНОСТИ: это разные вопросы, и спутать их было бы кратчайшим путём к migration, которая этому ADR не нужна.

Последствия​

Что становится лучше. Требование владельца продукта становится исполнимым: папка проекта mounted создаётся при подъёме контейнера, а не раньше. Мастер создания может предлагать путь — в этом и смысл превращения mounted в выбор первого класса, — не получая отказа из-за того, что подсказка не существует. И два режима с папкой пользователя получают ОДИНАКОВУЮ дисциплину при создании, что делает разницу между ними (кто подтверждает диск) проще для объяснения, чем когда один трогал диск, а другой нет.

Окно между созданием и материализацией реально, и оно принято. Между созданием проекта и подъёмом контейнера workspace_path указывает на папку, которой может не быть. Из-за этого ничего не ломается — projectScopeRoot лексичен, permissions.json деградирует до EMPTY_PERMISSIONS_FILE (то есть require_approval для всего, безопасная сторона), — но экран должен СКАЗАТЬ об этом, и это делает оговорка «папка создаётся, когда поднимется контейнер», в PR о готовности контейнера. Экран, показывающий путь, не говоря, что его ещё нет, — экран, утверждающий то, чего не знает.

Ошибка приходит позже, поэтому должна быть явнее. Проверка при создании имела достоинство: отказ происходил на экране, где было принято решение. Теперь он может произойти в одобренном часами позже container_start, и поэтому failed НАЗЫВАЕТ переменную (BRABO_PROJECTS_BASE=<valor>), путь, вероятную причину (владелец папки на хосте — образы работают non-root, ADR 0024) и что делать дальше («снова одобрите container_start»). Общий failed здесь был бы хуже старой проверки.

Старое сообщение УМЕРЛО, намеренно. comoMontar учил добавлять - <caminho>:<caminho> к сервисам api и engine в docker-compose.yml. Это средство стоило перезапуска обоих сервисов на каждый проект — каждого хода агента, сокета терминала и вызова LLM в полёте установки, — и ADR 0141 заменил его базой, настраиваемой один раз. Продолжать учить старому средству значило бы отправлять пользователя ронять установку, чтобы решить проблему, которую база уже решает. Есть утверждение теста об ОТСУТСТВИИ того текста.

У workspaceVerifiedAt теперь два источника. Был только у runner (RN-423). Теперь mounted тоже ставит его при материализации. Поле по-прежнему означает ровно то же — кто-то с полномочиями подтвердил этот путь на диске, один раз, — и по-прежнему НЕ является сердцебиением: переподключение не перезаписывает, а экран продолжает говорить «папка подтверждена <data>», никогда «работает» (RN-468). Меняется описание OpenAPI, которое гласило «container/mounted never fill this field in» и теперь описывает код.

Чего этот ADR НЕ решает. Не меняет ветвление ExecuteContainerStartUseCase между broker и runner — дать контейнер режиму mounted через BROKER — это другой PR, а этот трогает файл лишь для того, чтобы добавить материализацию над существующим ветвлением. Не трогает caminhoDeWorkspaceLocalValido, не трогает CHECK, не имеет migration и не касается Engine.Actions.Workspace.ensure!/4 — пробел engine в режиме runner остаётся объявленным и открытым в CLAUDE.md.