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

ADR 0107 — Навигация по локальной папке через Runner

  • Статус: Принят
  • Дата: 2026-08-20
  • Контекст: Волна 4 более крупной программы (локальная папка, управление PR, i18n) — запрос владельца продукта
  • Пересматривает (не редактируя) территорию: ADR 0072, компаньон ADR 0103

Контекст​

ADR 0072 (проект Local, RN-169/170) ЯВНО отказался от селектора папки при создании/принятии проекта: "um seletor exigiria a api enumerar o filesystem do CONTAINER para o navegador, superfície nova para resolver ergonomia". Решение того времени было верным — api работает внутри контейнера, и файловая система, которую она видит, — не файловая система пользователя. Единственным путём было ввести абсолютный путь по памяти, заранее зная, как он выглядит ВНУТРИ контейнера (RN-170).

С тех пор ADR 0103 ввёл apps/runner: CLI, работающий НА МАШИНЕ пользователя, с его привилегиями, аутентифицированный одноразовым тикетом и уже используемый для двух целей (одобренная команда агента, интерактивный PTY). Он меняет вопрос: поверхности, перечисляющей файловую систему, больше не нужно быть api, перечисляющей КОНТЕЙНЕР, — это может быть RUNNER, перечисляющий реальную машину пользователя, тот же компонент, у которого уже был бы этот обзор для работы терминала.

Решение​

Навигация по папкам начинает существовать, но только через Runner. Два новых события в ТОМ ЖЕ канале Phoenix terminal:<projectId>, который уже использует PTY (apps/runner/src/channel.ts, EngineWeb.TerminalChannel): fs_list_dir/fs_list_dir_reply и fs_home_dir/fs_home_dir_reply. Та же конструкция, что у PTY: :web запрашивает, чистый relay engine к pid зарегистрированного runner (Engine.Runners.Registry), :runner отвечает, broadcast фильтруется только для сокетов :web. Корреляция по ref, сгенерированному клиентом, ровно как PTY коррелирует по sessionRef, — engine НИКОГДА не интерпретирует ни одного пути, лишь передаёт дальше.

api ПО-ПРЕЖНЕМУ не перечисляет никакую файловую систему. Это не меняется: ни одного нового HTTP-маршрута для листинга каталога в api не добавляется. Чтение всегда идёт runner → engine (relay) → web, по уже существующему каналу. Аргумент ADR 0072 против перечисления КОНТЕЙНЕРА api остаётся в силе, нетронутым — это решение ему не противоречит, оно переносит проблему на компонент, у которого уже правильный обзор.

Граница безопасности ПО-ПРЕЖНЕМУ — граница ADR 0103, а не allowlist путей. apps/runner/src/guard.ts ограничивает cwd УЖЕ ОДОБРЕННОЙ команды агента корнем проекта (--dir) — но это ограничение НЕ применяется к навигации по папкам, намеренно: цель здесь — именно позволить пользователю СВОБОДНО перемещаться по собственной машине, чтобы выбрать ЛЮБУЮ папку как корень будущего проекта Local. Отказ в этом через allowlist путей противоречил бы самой цели функции. Реальная граница остаётся композицией трёх вещей, той же, что в ADR 0103: аутентификация (только тот, у кого есть тикет канала, может запросить), чистое чтение без какого-либо эффекта (листинг каталога — не команда, не проходит через proposed_action, ничего не пишет) и СОГЛАСИЕ — именно пользователь решает запустить Runner на собственной машине, зная, что он перечисляет то, что запрошено.

Ошибка на ЗАПИСЬ, а не на весь листинг. Запись без права чтения (ограничительный ACL, битый symlink, странное монтирование) пропускается — apps/runner/src/fs-browser.ts никогда не прерывает весь листинг из-за одного проблемного элемента.

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

  • Принятый архитектурный пробел, объявленный (не закрытый в этой поставке): тикет канала (getTerminalTicket/getRunnerTicket) выдаётся НА ПРОЕКТ — Runner запускается как --project <id> --dir <pasta>, привязанный к уже существующему проекту. На экране СОЗДАНИЯ проекта (NewProjectWizard.tsx) проект рождается только на шаге подтверждения, поэтому кнопка "Procurar pasta..." на шаге workspace НЕ может подключиться ни к какому runner — нет проекта, к которому привязать тикет. FolderBrowserModal получает projectId: string | null и, когда null, показывает объявленное состояние ("disponível depois que o projeto existir") вместо попытки подключения, которой сегодняшняя архитектура не позволяет. Поле свободного текста остаётся единственным путём в этом конкретном потоке — ровно как было до этой поставки. Настоящее закрытие этого потребовало бы либо (a) режима тикета/канала для Runner, не привязанного к проекту, либо (b) рождения проекта раньше в потоке мастера (до подтверждения) — оба — изменения большей области, чем эта Волна, и остаются для будущей поставки, которую приоритизирует владелец продукта.
  • Там, где проект УЖЕ СУЩЕСТВУЕТ (вкладка Code проекта Local, например, — навигация доступна и там через тот же FolderBrowserModal, независимо от создания), навигация работает от начала до конца, включая "runner ещё не подключён" с onboarding установки — см. RunnerOnboardingPanel.
  • apps/runner получает третью ответственность в ТОМ ЖЕ канале (exec, PTY, а теперь навигация по папкам) — ни нового канала, ни нового тикета, два события сверх уже существующего протокола.
  • Без события аудита (terminal.session.*) для навигации по папкам — в отличие от PTY, который является СЕССИЕЙ с длительностью. Листинг каталога — точечное чтение, без состояния, без эффекта: то же мерило, что уже освобождает чтение от proposed_action в остальном продукте.