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в остальном продукте.