# Workspace Gateway — план ## 2026-10-19 — исправлен тайм-аут bootstrap Kraken SSH Manager **0.2.1**, коммит fbbc867. В операторский конфиг добавлен явный bootstrap_direct: существующий ключ доступа и pinned host key сохраняются, у выбранного хоста отключается наследуемый ProxyCommand/ProxyJump. По умолчанию маршруты остальных хостов не меняются. Описание ssh_authorize разделяет bootstrap менеджера и клиентский relay. У существующей записи Kraken выбран уже документированный и проверенный по pinned host key адрес 192.168.1.14:22 вместо неразрешимого имени и мёртвого WG-пути 10.99.1.2. В существующем явно разрешённом relay-ключе Mac через native MCP обновлён только permitopen на тот же проверенный Kraken. Ключ, запрет shell, остальные authorized_keys и журнал регистраций сохранены. Личный ~/.ssh/config не менялся. Проверено: race/vet/build; настоящий sshd/MCP fixture с намеренно сломанным наследуемым proxy; production ssh_authorize дошёл до Linux bubblewrap owner-form; настоящий UID 1000 Balda получил SSH banner Kraken через relay. Реальная модель Balda, session 01a15249-fb40-7593-89b8-afefb628ebae, получила ровно одно Owner confirmation, без generic MCP approvals, и вернула OWNER_DECLINED после тестового отказа. Этот тест не устанавливал ключ/пакеты на Kraken; его execution-регистрация и приёмка изоляции не объявляются завершёнными. Balda перезапущена 2026-10-19T03:52:04.641738976Z; websocket Mattermost подключён; /backoffice/healthz и /backoffice/login — 200. Активный native Manager использует установленный inode; binary SHA256 7e72c8e3fd2b251a39bc94a1e7f980cc8152c323fe5abe975db633a0e3eaaf72. Рабочее дерево чистое. Приёмка: /Users/admin/Developer/ssh-manager/docs/bootstrap-route-2026-10-19.md. Evidence: ~/.local/share/balda-workspace/candidate/ssh-route-fix.json. Backup: /Users/admin/.local/share/balda-workspace/backups/ssh-route-20261019T035049Z. ## 2026-10-19 — SSH Manager: подготовка через менеджер Исправление закоммичено в `ssh-manager`: `e490774`. Рабочий Manager 0.2.0 обновлён, MCP-кеш Balda обновлён разрешённым перезапуском. Последний запуск Balda: `2026-10-18T18:32:13.106914568Z` UTC. Финальный бинарник Manager: SHA256 `059d6c9c79a67893a9772a34c7a103919d2ba7a5e3faa7171af781dd3ec81160`; проверено совпадение inode реально слушающего процесса с этим файлом. Рабочее дерево SSH Manager чистое. Прежнее изменение текста ошибки не решало задачу. Теперь `ssh_hosts` показывает регистрацию и следующее действие по назначению ключа; зарегистрированному `mac-relay` не требуется execution-подготовка. Для обычного непринятого execution-хоста `ssh_authorize` сам планирует native filesystem isolation через существующий pinned bootstrap SSH, получает **одно** подтверждение владельца на подготовку и точную строку authorized_keys, подготавливает и проверяет изоляцию, затем устанавливает ключ. Операторский флаг в JSON вручную не переключается. Guard принятой изоляции и AGENTS.md сохранены. Принятые проверки: - `go test -race ./...`, `go vet ./...`, build, Python compile — pass. - Настоящие macOS sshd + MCP: Cancel не меняет ключи; намеренно сломанная подготовка не устанавливает ключ; повтор после исправления fixture создаёт private policy, регистрирует только fixture-key через bootstrap SSH и проходит реальную аутентификацию. Проверен нестандартный HOME под `/private/var/tmp`, не покрытый обычными read-deny roots. - Native Linux bubblewrap в Colima: RW private workspace; отказ чтения/записи снаружи; отказ symlink escape; ограничения наследуются дочерним процессом; host loopback недоступен. Свежий `/proc`, user/PID/network namespaces и запрет вложенных user namespaces сохранены. Ограничение Docker на fresh `/proc` не обходилось ослаблением политики. - Реальная модель после финального перезапуска: пять безопасных Manager-вызовов без generic MCP approvals, запись в workspace и отказ вне него. Session `01a1504b-b585-7360-841a-877c588ae679`. - Реальный Codex ACP внутри рабочей Balda: сначала `ssh_hosts`, затем `ssh_authorize` непринятого fixture-хоста; одно Owner confirmation с планом native подготовки. Тест выбрал Decline, получен OWNER_DECLINED; production SSH state не изменён. Session `01a1504b-b5fa-7f22-80a6-fe3c4c3cedee`. Сообщение/подтверждение от имени владельца в Mattermost не отправлялось. - HTTP health/login — 200; прежний настоящий SSH к Mac по-прежнему проходит обязательный SRT. Хеши Mac authorized_keys, SSH host config и registration journal совпадают с исходными. Compose/config Balda сохранены. Bootstrap использует `/usr/bin/python3 -I`: read-only preparation script завершался SIGKILL под Homebrew Python, а тот же script под системным интерпретатором прошёл. Причина отмены контекста и безопасная категория ошибки bootstrap теперь сохраняются без возврата raw proxy stderr/credentials. Default private policies имеют изолированную сеть; API/proxy/credentials агента относятся к настройке runtime. Существующая принятая Mac SRT/network policy не менялась. Непринятый custom forced command не заменяется молча. Для операции по-прежнему нужен реально доступный bootstrap-хост; программный флаг не подменяет сеть или права установки native dependencies. Это исправление продукта SSH Manager, **не ручная настройка Kraken**: его адрес/конфигурация и регистрация не переписывались. Прежняя запись о необходимости вручную включить `execution_ready` для обычного хоста больше не описывает текущий Manager. Отдельные нерешённые решения исходного плана по личным Mac CLI credentials/OpenCode localhost этим исправлением не считаются закрытыми. Приёмка: `/Users/admin/Developer/ssh-manager/docs/acceptance-2026-10-19.md`. Подробный runtime evidence: `~/.local/share/balda-workspace/candidate/ssh-manager-flow-fix.json`. Consistent backup: `/Users/admin/.local/share/balda-workspace/backups/ssh-manager-flow-20261018T181430Z`. ## 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]].