# Workspace Gateway — план ## 2026-10-18 — исправлены MCP approvals и ответы `1` Лишний `MCP tool-call approval` убран в активных Codex-профилях штатным флагом моста `--mcp-approval-policy=allow`. Режим Balda остаётся `ask`. Точные подтверждения Workspace Connect/Renew и SSH Authorize/Revoke остаются обязательными. Подключённые MCP-серверы и их credentials не расширялись. Upstream Codex ACP 1.11.0 требует elicitation capability, которой нет у текущего клиента Balda. В закреплённой сборке добавлен узкий fallback пустой confirmation-only формы на существующий ACP RequestPermission. Форма с полями и неподдержанные режимы без capabilities не принимаются автоматически. Explicit accept/decline/cancel и неизвестные ответы покрыты тестами. Чистая повторная сборка закреплённого source + patch прошла race/lint и дала одинаковый хеш. В Balda исправлены две причины потерянного ответа: обычный `1` в DM теперь относится к единственному доставленному действующему вопросу той же беседы/сессии; ID отвечающего совпадает с исходным ID автора запроса, без добавления `mattermost:` только к ответу. Проверяются права отвечающего, expiry и время сообщения. Старый `1`, в том числе в пределах той же секунды, не разрешает новый вопрос. Общие каналы и несколько pending-вопросов требуют ответа в треде. Проверено: - Настоящая цепочка Mattermost adapter → ChatFX → questions → SQLite: `1` разрешает, `2` отклоняет; продолжение сохраняется для штатного возобновления. Wrong requester/session/conversation, expiry, недоставленный/неоднозначный вопрос, обычный текст и старое сообщение не выдают разрешение. - Полный `go test -race ./...`: 173 пакета, exit 0; golangci-lint — 0 issues; go-arch-lint — OK. - Реальный Codex внутри production Balda: пять Manager lookup, запись в workspace и запрет записи вне него; generic permission requests = 0. Session `01a14fd8-a855-7bb2-ae76-77d5449db219`. - Реальный `ssh_authorize` с возможностями клиента Balda дошёл до одного окна Owner confirmation; тест выбрал Decline и получил OWNER_DECLINED. Session `01a14fd8-a84d-75d0-9624-ec816c3de844`. Approved authorized_keys и registration journal не изменились. - Балda реально запущена из проверенной сборки: hash `/proc/1/exe` совпал со staged binary. Последний запуск: `2026-10-18T16:41:07.023505736Z`. HTTP health/login — 200; native SSH к Mac через обязательный Seatbelt — pass. State/home/work mounts сохранены; SQLite/compose/config backup — `/Users/admin/.local/share/balda-workspace/backups/direct-reply-20261018T162446Z`. Коммиты: Balda `254f3e29`; Workspace Manager/deployment `0c82b8f` (основное MCP-исправление `a5c0b66`); SSH Manager `8739b1e`. Рабочие деревья проверены отдельно. Подтверждение в реальном Mattermost-чате не отправлялось от имени владельца; приёмка входящего ответа выполнена сквозным тестом. Runtime evidence: `~/.local/share/balda-workspace/candidate/direct-reply-fix.json`. Kraken остаётся **не настроен для execution**: его filesystem isolation не provisioned/accepted; `execution_ready=false` сохранён. Инструмент теперь явно говорит, что chat approval не может подготовить хост, вместо ложного совета подтвердить несуществующее окно. Из Mac имя `kraken` не разрешается через DNS, оба IP из существующего SSH ProxyCommand (`10.99.0.2`, `10.99.1.2`) не ответили на TCP 22; bootstrap SSH истёк на banner exchange. Запрошен доступный адрес/восстановление маршрута. Регистрация нового target-ключа и приёмка этого хоста не заявляются выполненными. Ранее остававшиеся отдельные решения по личным Mac CLI credentials/OpenCode localhost этим исправлением не подменяются. Полная исходная настройка не помечена принятой. Обновлено: **2026-10-18**. Проект: `/Users/admin/Developer/workspace-gateway`. Git: `main`, реализация `00b7e13`; актуальные deployment scripts и приёмка `9e5066a` (2026-10-18). Четыре MCP-функции реализованы; принят native SMB3 backend с фактическим отзывом открытых FD на Linux и Mac, независимым истечением срока и динамическим подключением без рестарта получателя. Сервис установлен как user launchd job на `127.0.0.1:17795`; аутентифицированные вызовы из доверенного образа Balda прошли. Production Balda переключена с явного разрешения владельца и перезапущена 2026-10-18 в 20:13:46 Asia/Bishkek: native ask активен, оба Manager проверены из рабочего контейнера, SSH/Seatbelt прошёл, HTTP health/login — 200. State/home/work, прежний pool и все записи БД сохранены; backup сделан до переключения. Полная модельная приёмка ещё ожидает отдельных решений по существующим CLI credentials и OpenCode localhost binding. Исправления production: Codex `workspace-write` принят реальной моделью в обычном workspace и динамическом RW grant, запись вне разрешённых roots запрещена. Пять безопасных Manager-запросов прошли без approval; изменения прав по-прежнему требуют владельца. Старый broker отключён, оригинальные Prism сохранены. Принятые Docker-профили включены только с точного согласия владельца; privileged/SYS_ADMIN не добавлены. Журнал и повторяемый модельный тест включены в `1076b4f`; SSH Manager — `f876d37`. Корни проектов исправлены по замечанию владельца: **только `developer: /Users/admin/Developer`**. `downloads` был моей лишней настройкой и удалён из рабочего конфига и примера; дополнительные корни нельзя включать из примера без явного выбора владельца. Приёмка `9e5066a`: из рабочего контейнера получены 139 папок Developer, 8 дочерних папок workspace-gateway, регистронезависимый поиск имени — 2 мс. `query` фильтрует текущий уровень, глубокий обход включается только `recursive: true`; прежний неявный обход всех файлов/зависимостей упирался в лимит. Настоящий Codex сделал пять folder calls и вывел реальные корни/количество/имена, permission requests: 0. Отчёт `candidate/project-discovery-acceptance.json`, тест `tests/balda_discovery_acceptance.py`, ACP session `01a14fa7-60f2-7971-81de-b6c838ec1c31`. Предыдущее заявление о полной проверке не включало просмотр внутри каждого рабочего root и было преждевременным. Общий setup Balda: [[personal/plans/balda-remote-agent]]. ## Назначение и четыре функции Сервис выдаёт Balda и удалённым агентам доступ к исходным папкам через сетевую файловую систему. Balda и агенты видят одни и те же файлы; копирование Git-репозитория не считается реализацией общего доступа. 1. **Папки:** листинг и навигация по папкам внутри настроенных корней (`developer`, `downloads` и других), а также поиск папок по имени; без выдачи списка файлов и их содержимого. 2. **Подключение:** выбранная папка появляется в work dir после подтверждения владельцем точного пути, получателя, режима доступа и срока. 3. **Отключение:** фактический отзыв ранее выданного доступа. 4. **Лизинг:** статус и срок действия, автоматический отзыв после истечения срока, продление с подтверждением владельца. Четыре пользовательские функции — согласованный объём проекта. Обработка ответа владельца, журнал выданных разрешений и таймеры обслуживают эти функции, а не расширяют API прикладными сценариями. ## Конфиг корней и поиск папок Workspace Manager — пользовательское название сервиса; текущие путь проекта и имя этой заметки остаются `workspace-gateway`. В конфиге задаются несколько именованных корней; список расширяется без изменения кода. Например: ```yaml roots: developer: /Users/admin/Developer ``` Корень задаёт область обнаружения папок, а не автоматический доступ Balda или агентов ко всем его файлам. Сервис не монтирует целиком `Developer` или `Downloads` только потому, что они указаны в конфиге. Поиск выполняется по имени папки (полное имя или подстрока, без учёта регистра), в выбранном корне либо во всех настроенных корнях. Результат содержит имя корня и относительный путь папки, чтобы различать одинаковые имена. Имена файлов и содержимое файлов в поиск не входят. Выход за корни через `..` и символические ссылки не допускается. Подключение найденной папки по-прежнему требует подтверждения владельца точного пути, получателя, прав и срока. Порядок реализации: загрузить и проверить `roots` из конфига; реализовать листинг/навигацию; добавить поиск по имени; включить операции в MCP; проверить поиск в нескольких корнях, одинаковые имена, отсутствующий/недоступный корень и запрет выхода за границы. ## Границы SSH-запуск, ACP-транспорт, исполнение команд на хосте и Seatbelt относятся к настройке Balda и среды, а не к Workspace Gateway. Prism, Callee, Beads, управление задачами и сессиями используются штатно. Сервис не реализует собственные Story workflows, собственный Prism и дублирующий scheduler. Общий каталог Developer и домашний каталог не выдаются агентам. Листинг папок даёт возможность запросить конкретный путь, а не право читать его файлы. Ответ от модели или аргумент `approved=true` не является подтверждением владельца. Отзыв и истечение срока должны прекращать реальный доступ, включая существующие соединения и открытые файловые дескрипторы. ## Конкретные шаги 1. Я проверю готовую сетевую файловую систему на текущем Docker/Colima и целевых средах: исходные файлы видны обоим клиентам, добавление разрешённой папки обходится без перезапуска Balda, соседняя папка недоступна, отзыв закрывает уже выданный доступ. 2. Я подключу постоянную точку сетевой файловой системы в work dir; это разовая настройка deployment, последующие выдачи и отзывы происходят в рантайме. 3. Я реализую конфиг нескольких именованных корней (`developer`, `downloads` и других), листинг/навигацию и поиск папок по имени внутри этих корней; затем запрос подключения, связав подтверждение владельца с точным путём, получателем, правами и сроком. Сначала проверю пригодность штатных взаимодействий Balda. Приёмка поиска включает несколько корней, одинаковые имена папок, недоступный корень и запрет выхода через ссылки или `..`. 4. Я реализую отключение и временный лизинг, сохранение состояния разрешений и обработку истечения срока после перезапуска сервиса; неполученное подтверждение оставляет доступ закрытым. 5. Я проведу приёмку с двумя клиентами: общие файлы, RO/RW, запрет соседних путей и обхода через ссылки, отказ владельца, отзыв, истечение, продление и повторный запуск сервиса. Выбран и принят SMB3 backend: native Samba, mount.cifs Colima и mount_smbfs macOS. Приняты оригинальные файлы, RO/RW, динамические grants, отзыв открытых FD, независимый expiry и реальный отзыв после перезапуска Manager. Подробности — в актуальном журнале ниже. До выбора backend сохраняется ровно этот объём; общая копия всех репозиториев и ослабление изоляции не являются заменой выдаче доступа. ## Что уже проверено и что остаётся - [x] Создан отдельный Git-проект с зафиксированной границей четырёх функций. - [x] Проверена независимая возможность штатного ACP Balda поверх SSH: [[personal/projects/balda/ssh-acp-verification-2026-10-18]]. - [x] Выбрана и испытана сетевая файловая система: native SMB3, Linux и Mac. - [x] Реализованы четыре функции, native подтверждения, реальный отзыв открытых FD и независимый срок доступа. - [ ] Настроено общее состояние Beads и принята работа реальных агентов через полученный workspace. Доступ к исходникам и доступ к состоянию Beads — связанные, но отдельные пункты общей установки. Для одновременной работы Balda и нескольких агентов Beads описывает server mode Dolt: [официальная архитектура](https://github.com/gastownhall/beads/blob/main/docs/architecture/index.md). Сетевой mount сам по себе не считается приёмкой конкурентной работы БД. ## Выполнение 2026-10-18 — промежуточный журнал - Исходники находятся в `/Users/admin/Developer/workspace-gateway`: конфиг именованных корней, навигация и поиск только папок; запрет `..`, выхода через ссылки и выдачи целого корня. - Ровно четыре MCP-инструмента: `workspace_folders`, `workspace_connect`, `workspace_disconnect`, `workspace_lease`. Подтверждение через MCP elicitation; путь, получатель, RO/RW и точный срок перечисляются в запросе владельцу. - Написано ядро лизинга: атомарный журнал с fsync, отдельно подтверждаемое продление, видимый отказ отзыва и повторные попытки, закрытие незавершённых/активных разрешений после перезапуска. Это проверено тестовым backend; **реальные mounts и отзыв файлового доступа ещё не приняты**. В исполняемом сервисе подключение закрыто до выбора проверенного backend. - В реальном проекте прошли `go test -race ./...`, `go vet ./...`, сборка. Настоящий stdio MCP: initialize, tools/list (4 инструмента), листинг без имён файлов, отказ на traversal и неподтверждённом backend. - HTTP MCP: авторизация проверена локально на Mac, `host.docker.internal` разрешён, чужой Host отвергается. Из отдельного контейнера Colima проверены достижимость и HTTP 401 без токена; аутентифицированный MCP из контейнера не проверялся. Рабочий порт для подготовки: `17795` (7795 занят прежним Broker). - Подготовлены `/private/tmp/workspace-setup-20261018/nfs-admin.py` и `nfs-acceptance.py`. Полный запуск: `sudo /opt/homebrew/bin/python3 /private/tmp/workspace-setup-20261018/nfs-admin.py test`. Он предназначен только для fixture `/private/tmp/workspace-setup-20261018/nfs/source/allowed`, клиентов Colima и восстановления исходного состояния после теста. - Первый ручной `start` остановился до изменения системы из-за неверной обработки штатного exit 1 у `nfsd status`; проверка исправлена на наличие самого процесса nfsd и проверена с mock. Полная NFS-приёмка ещё ожидает ручного sudo. На момент этой записи `/etc/exports` отсутствует, nfsd не запущен. - Закрытые отчёты/резервные копии: `/Users/admin/Docker/balda-mm-mac/state/workspace-setup-20261018`. Production Balda не переключалась. ### NFS: ручной запуск и диагностика сетевого пути Пользователь выполнил `nfs-admin.py start`: штатный nfsd реально запущен, RPC NFSv3/2049 и mountd зарегистрированы, `showmount -e localhost` показывает только fixture. Полный `test` этим выводом ещё не подтверждён. Первое подключение обоих контейнеров получило Permission denied. Причина в исходной настройке fixture, не доказательство непригодности NFS: TCP-измерение показало, что Mac видит клиента Colima как `127.0.0.1`, тогда как экспорт разрешал `192.168.5.1`. Отдельно проверено преобразование reserved port: контейнер привязал исходящий порт 900, на Mac наблюдался `127.0.0.1:64152`. Артефакт: `/private/tmp/workspace-setup-20261018/nfs/colima-source-address.json`. `nfs-admin.py test` исправлен: разрешает только fixture для loopback, на время теста учитывает переписывание порта в MOUNT RPC, после проверки восстанавливает исходные байты `/etc/nfs.conf`, удаляет только собственный экспорт и останавливает тестовый сервер. Логика полного восстановления проверена с mock на успешном и ошибочном пути; реальное RO/RW/открытые FD пока ожидает ручного запуска `test` с sudo. Отказ обоим клиентам не считается успешной изоляцией получателей: результат теперь `null` до успешного доступа разрешённого клиента. ### NFS: результат полного запуска и установленная причина отказа Пользователь выполнил полный `nfs-admin.py test`. Оба клиента завершились на mount с Permission denied, поэтому RO/RW и отзыв FD не проверены. Система после сценария реально восстановлена: `/etc/exports` отсутствует, nfsd остановлен, `/etc/nfs.conf` содержит исходный текст. Дальнейшая диагностика установила блокировку **самой операции mount в тестовом клиенте**; она не позволяет считать созданным NFS mount, независимо от возможных предварительных RPC: Docker использует AppArmor `docker-default`; с CAP_SYS_ADMIN обычный mount пустого tmpfs возвращает Permission denied (exit 255). Тот же контроль с `apparmor=unconfined` только у одноразового fixture-контейнера успешно монтирует tmpfs. Это пропуск в тестовом окружении, а не свидетельство непригодности NFS. Предыдущее объяснение одним адресом Colima было неполным; адрес/порт действительно переписываются, но исходные mount-пробы дополнительно блокировались AppArmor. Подготовлен минимальный отдельный образ `workspace-nfs-client:20261018` с официальным `nfs-utils 2.6.4`; image ID `sha256:5468d8d0555b1658bd7626f0f840269203dac8bde49160261268c03aea8b75f4`. Он не содержит пользовательских конфигов, токенов или ключей. Предварительная проверка клиента без Mac sudo реально прошла: стандартный mount.nfs существует, tmpfs монтируется. Исправлены fixture options и fail-fast: при неудачном initial тест больше не прогоняет RO/отзыв на остановленных контейнерах. Восстановление root-сценария снова проверено с mock для успеха и ошибки. Для реальной проверки NFS всё ещё требуется повторный ручной `sudo ... nfs-admin.py test`. ## Принятый SMB backend — 2026-10-18 Финальная реализация закоммичена `00b7e13`. Используются готовые Samba SMB3 и штатные клиенты macOS/Colima. На каждый lease выдаётся отдельный server/share с отдельным credential, точным bind только разрешённой папки и RO/RW на сервере. Истечение исполняет сам server supervisor, независимо от процесса Manager. Отзыв удаляет share, reload и close-share закрывают открытые FD, затем снимаются mounts; server ещё 60 секунд отвечает отказом старым клиентам и сам удаляется. Приняты реальные проверки: динамическое подключение в уже работающий непривилегированный контейнер; чтение/запись оригинала с Linux и Mac; server RO; продление без смены mount; отзыв открытых read/write FD обоих клиентов; истечение без Manager; отказ продлению истёкшего share. Дополнительно Go→встроенный backend и фактический отзыв при перезапуске Manager прошли. Ранний протокольный тест также проверил отказ credentials другого получателя, symlink escape и сохранение другого share при отзыве первого. Сохранённая повторяемая приёмка: `tests/smb_acceptance.py`; отчёт `~/.local/share/workspace-manager-acceptance/report.json`. Постоянный privileged mount-helper был отклонён автоматической проверкой и исключён. Финальный вариант устанавливает `cifs-utils` в Colima и выполняет обычные разовые native mount/umount команды; привилегированный Docker daemon/helper не остаётся. Корни Developer/Downloads служат только обнаружению. В recipient монтируется только его mount root `/var/lib/workspace-manager/balda`, с rslave, по совпадающему абсолютному пути на Mac/в Balda. NFS больше не является зависимостью выбранного backend; повторный ручной sudo тест не требуется. Сервис установлен на `127.0.0.1:17795`, private config/state: `~/.local/share/balda-workspace/workspace`; настоящее authenticated MCP initialize/tools/list/folders из доверенного образа Balda прошло. Production конфиг Balda ещё не переключался: candidate и финальная прикладная приёмка отслеживаются в [[personal/plans/balda-remote-agent]].