# Workspace Gateway — план ## Текущее состояние — сверено 2026-10-19 08:06 UTC | Компонент | Проверенное состояние | | --- | --- | | SSH Manager | 0.3.2, `a86df58`, 8 MCP-инструментов, `127.0.0.1:17796` | | Workspace Manager | `0c82b8f`, четыре функции, native SMB3, `127.0.0.1:17795`; root только `developer`, recipient `balda` | | Balda | `eee69fc5`, `balda-mm-mac`, provider `pool`, permissions `ask`; старт `2026-10-19T08:05:42.795540715Z` | | Проверка установленного SSH-ключа | Реальный контейнер Balda, `registered`, `client_verified_at=2026-10-19T06:29:15.692883Z`; прежний key id сохранён | | Исходники / runtime | Все три checkout чистые; build metadata обоих исправленных binaries указывает на эти чистые commits; активные hashes/inode проверены | SSH Manager импортирует существующий Mac SSH alias из native config/Include и доверенных known_hosts, проверяя существующую аутентификацию; повторно присылать server key для такого хоста не требуется. Для клиента отдельно проверяется маршрут: без `via` сначала pinned no-auth handshake из настроенного `client_container`; при сетевом отказе выбирается единственный зарегистрированный relay. Неизвестный или неверный pin не вызывает fallback; при нескольких relay нужен явный `via`. Owner-form включает точную замену relay key line с ограниченным permitopen. Хардкода Kraken или mac-relay в выборе маршрута нет. Успех `ssh_authorize` теперь требует свежего SSH-вызова из фактического клиента его установленным dedicated key с проверкой случайного маркера. Ошибка сохраняет `installed_unverified` и возвращает tool error; удаление host до отзыва такого ключа запрещено. Существующий принятый доступ можно проверить/исправить без замены целевого ключа и sandbox policy. В operator config указан `client_container: balda-mm-mac`, volume проверяется read-only. Inventory сейчас: `mac`, `mac-relay`, `kraken`. Kraken добавлен владельцем через Balda в сессии по ссылке ниже; маршрут исправлен самим менеджером на `mac-relay → kraken`, relay ограничен текущим endpoint. Новый target key при исправлении не устанавливался, прежняя identity и изоляция сохранены. Отдельный независимый SSH-вызов UID 1000 из Balda вернул ожидаемый маркер. Треды Mattermost DM: `eee69fc5` сохраняет `root_id` пользовательского сообщения в адресе доставки; обычные ответы, progress и вопросы идут в его тред, дальнейшие replies сохраняют этот root. DM/group session IDs остаются прежними. Это поведение транспорта в коде; настройки переключения нет. Отдельный approval-тред не создаётся. `254f3e29` откачен коммитом `3f6a1363`: обработчик обычного числового ответа без треда и изменения хранилища удалены. Узкое сопоставление raw user ID для штатного threaded reply исправлено отдельно в `eee69fc5`. Исправление двойного меню `3e86b995` сохранено. `9018e7a3` исправляет transport routing; `c061e266` исправляет выбор pending-вопроса в общем root вместо уже отвеченного; неоднозначные параллельные pending-вопросы не подтверждаются. `eee69fc5` сохраняет прежнюю классификацию лички в архиве. Приёмка и ограничения: [[personal/projects/balda/mattermost-dm-threads-2026-10-19]]. Balda: меню подтверждения рендерится один раз — permission layer передаёт готовую presentation, questions layer сохраняет structured options для ответа, но не дописывает второе меню. Regression проходит через permission review, session question delivery и decision resolution. Добавление host и установка ключа остаются двумя разными owner confirmations; дополнительные generic MCP approvals у Codex выключены, native изменения доступа подтверждаются владельцем. MCP: `obsidian`, `virfield`, `workspace-manager`, `ssh-manager` и встроенный `balda`. Legacy Broker/Prism MCP отсутствуют в runtime, plugin `execution-broker` выключен, оригинальные Prism/Prism Callee 0.7.0 включены. Codex: `workspace-write`, дополнительный writable root только grants получателя, `--mcp-approval-policy=allow`. Workspace: `workspace_folders`, `workspace_connect`, `workspace_disconnect`, `workspace_lease`; discovery только папок, выдача конкретной папки RO/RW с TTL. Downloads и общий Developer/home mount не включены. Lease journal пуст. Приняты SMB revoke существующих FD/expiry и shared Beads fixture; runtime Workspace в этом исправлении не менялся. Проверено: SSH race/vet/build; 15 native SSH lifecycle checks и 16 checks на случайных aliases в сети реальной Balda, включая alias-only import без via, автоматический relay, pin failure, retry, restart/revoke/remove. Историческая model-проверка после deployment SSH-маршрута 06:28 UTC: `01a152d9-7d04-7f21-9597-91c259813c7c`, ровно одна Owner confirmation существующего key id, без generic MCP approval; client receipt выше. Полный race-прогон Balda: 142 пакета с тестами; golangci-lint и architecture checks — pass; launchd Workspace/SSH/Beads-Dolt running, health/login 200. Внешние тестовые сообщения Mattermost не отправлялись. Первичный провал приёмки: [сессия Mattermost](https://mm.qentra.top/qentra/pl/bjb4nhz6o3yoxqg9rs1qcpncjh). Причины и исправление: [[personal/projects/balda/ssh-client-route-2026-10-19]]. Evidence: `~/.local/share/balda-workspace/candidate/client-route/`; backup: `~/.local/share/balda-workspace/backups/client-route-20261019T062059Z`. Оставшийся исходный объём: полноценные удалённые Mac model runs с отдельными CLI credentials/OpenCode network решениями, полный модельный Prism lifecycle и реальные project Beads DB. Host Docker/socket/sudo authority не добавлялась: установленный target key по-прежнему ограничен принятой песочницей. Эти пункты не объявляются выполненными этим исправлением. ## Исторический журнал изменений и исходный план ## 2026-10-19 — существующий SSH-доступ Mac без повторной передачи ключа Исправлен оставшийся дефект 0.3.0: обязательный public_host_key заставлял Balda просить ключ сервера даже для уже настроенного SSH alias. SSH Manager **0.3.1** развёрнут, коммит afa3813; для импорта достаточно alias. Hostname/user/port/public_host_key необязательны и берутся из native SSH config с Include и существующих known_hosts, включая hashed записи. Пароль/private key оператора не передаются модели и не копируются в Balda. Менеджер проверяет аутентификацию существующим доступом Mac с доверенным pinned server key. Auto сначала использует операторский маршрут, затем при необходимости пробует только адреса, уже связанные с тем же доверенным ключом, с приоритетом локальных сетей. Каждый выбранный адрес должен пройти pinned SSH auth. Сетевого сканирования, доверия к keyscan или accept-new нет; unknown/revoked/changed trust не принимается. В owner-form показываются полученный endpoint, fingerprint и источник доверия. Для действительно нового доверия остаётся explicit verified public key. Проверено: - race/vet/build; native Include/defaults, hashed known_hosts, revoked/unknown keys, нестандартный port syntax и безопасный разбор путей. - Настоящие два sshd, исходный operator Include + hashed trust + сломанный proxy/hostname: alias-only import без адреса/логина/ключа; native isolation, ProxyJump, wrong pin, restart/revoke/remove — pass. - Реальная модель Balda добавила и авторизовала fixture, получив только alias/via. Session 01a152a5-8b64-7dc1-be62-d007ff3a09c5. - После deployment модель получила запрос настроить Kraken через существующий Mac доступ. Менеджер сам прочёл доверенный ключ и подтвердил SSH auth на рабочем LAN endpoint; модель дошла до одной обычной формы добавления без запроса ключа у пользователя. Session 01a152a8-1659-7860-bc63-ea9e8a2d92ba. Тест выбрал Decline: новая запись/ключ/пакеты Kraken не устанавливались. - Хеши личных ~/.ssh/config, known_hosts, authorized_keys, Manager inventory/journal и Balda compose/config совпадают с backup. Чужие доступы сохранены. - Balda restart 2026-10-19T05:34:41.958112077Z; Mattermost websocket connected; /backoffice/healthz и /backoffice/login — 200. Активный Manager inode совпадает с установленным binary SHA256 f46fb2f4040ac33e94440f95556052e65f3c4e98d9b88b48c9db18d952749376; runtime source hashes совпадают с HEAD. Это общее использование существующих SSH-настроек, без захардкоженного адреса или отдельной ручной настройки Kraken. Нативные подтверждения изменения доступа и приёмка filesystem isolation сохраняются. Внешние Mattermost сообщения от имени владельца не отправлялись. Приёмка: /Users/admin/Developer/ssh-manager/docs/operator-ssh-import-2026-10-19.md. Evidence: ~/.local/share/balda-workspace/candidate/operator-import.json. Backup: /Users/admin/.local/share/balda-workspace/backups/operator-import-20261019T053314Z. ## 2026-10-19 — добавление произвольных SSH-хостов через Balda SSH Manager **0.3.0** развёрнут, коммит 85227c4, рабочее дерево чистое. Теперь доступны восемь MCP-инструментов, включая ssh_host_add и ssh_host_remove. Новые хосты добавляются через Balda: alias, адрес, пользователь, проверенный public host key, при необходимости port/via/bootstrap alias. Ручная правка операторского JSON для добавления не требуется. Подтверждение добавления показывает endpoint/login, fingerprint и public host key, выбор bootstrap и точную замену relay-строк. Для каждого нового alias создаётся отдельная identity; inventory и актуальный SSH config сохраняются сразу и переживают restart. Затем ssh_authorize подтверждает и выполняет native подготовку изоляции и установку ключа. Существующий bootstrap SSH доступ оператора остаётся необходимым; пароли/private bootstrap keys в инструменты и чат не передаются. Удаление требует предварительно отозвать registered/pending ключи и убрать зависимые jump hosts. Relay permissions обновляются вместе с inventory; при отсутствии целей forwarding отключается. Чужие authorized_keys сохраняются, существующие сессии не завершаются. Файлы private identity при удалении inventory сохраняются. Ошибки sync откатывают inventory и восстанавливают relay/client config либо явно сообщают о неподтверждённом откате. Частная запись Kraken удалена штатным ssh_host_remove; её прежний permitopen тоже убран. Сохранились два исходных Mac host/relay alias, их ключи и журнал регистраций. Прямой ручной настройки самого Kraken не выполнялось. У менеджера нет привязки к этому хосту. Проверено: - race/vet/native build; tests отмены, сохранения/удаления, stale config, rollback, unsafe input, защиты от подмены alias, guards revocation/dependants и read-only public identity. - Два настоящих временных sshd с пустым начальным inventory, случайными alias/портами: add, relay update, native isolation, ProxyJump command, wrong pin rejection, restart, revoke/auth denial/remove; 12 проверок passed. - Реальная модель Balda сама добавила и авторизовала новый fixture-хост, ровно две Owner confirmation, затем прошли настоящий SSH и lifecycle. Session 01a15295-479a-7123-b64d-9ee46465b5dd. - Рабочий production ssh_host_add через модель: одна форма, тест Decline, inventory/keys/journal неизменны. Session 01a15297-3e86-7fd3-831a-8283d0a2a64a. - Рабочая модель после restart: четыре SSH lookup и workspace discovery без approvals, workspace RW и отказ вне него. Session 01a15298-98b7-7de0-9274-b2cd63c61958. - Прежний Mac execution через обязательный SRT работает; пустой relay аутентифицируется и административно отказывает forwarding. - /backoffice/healthz и /backoffice/login — 200; Mattermost websocket connected. Balda restart: 2026-10-19T05:16:18.960287594Z. Compose/config и registration journal сохранены. Активный native Manager inode совпадает с установленным бинарником; SHA256 d3a9b63570932782942d8b3e2e2c92fabead5a5551f3b3d8c330922d9ec58d98. Source HEAD и runtime source hashes сверены. Внешние Mattermost сообщения от имени владельца не отправлялись. Полная исходная установка с отдельными решениями по личным CLI credentials этим исправлением не объявляется завершённой. Приёмка: /Users/admin/Developer/ssh-manager/docs/host-inventory-2026-10-19.md. Evidence: ~/.local/share/balda-workspace/candidate/generic-host-manager.json. Backup: /Users/admin/.local/share/balda-workspace/backups/generic-hosts-20261019T051417Z. ## 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 и дала одинаковый хеш. Исторический патч `254f3e29` **откачен** в `3f6a1363`; описанное далее поведение больше не действует. Тогда обычный `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`; дополнительные roots включаются по выбору владельца), а также поиск папок по имени; без выдачи списка файлов и их содержимого. 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`; дополнительные roots включаются по выбору владельца), листинг/навигацию и поиск папок по имени внутри этих корней; затем запрос подключения, связав подтверждение владельца с точным путём, получателем, правами и сроком. Сначала проверю пригодность штатных взаимодействий Balda. Приёмка поиска включает несколько корней, одинаковые имена папок, недоступный корень и запрет выхода через ссылки или `..`. 4. Я реализую отключение и временный лизинг, сохранение состояния разрешений и обработку истечения срока после перезапуска сервиса; неполученное подтверждение оставляет доступ закрытым. 5. Я проведу приёмку с двумя клиентами: общие файлы, RO/RW, запрет соседних путей и обхода через ссылки, отказ владельца, отзыв, истечение, продление и повторный запуск сервиса. Выбран и принят SMB3 backend: native Samba, mount.cifs Colima и mount_smbfs macOS. Приняты оригинальные файлы, RO/RW, динамические grants, отзыв открытых FD, независимый expiry и реальный отзыв после перезапуска Manager. Подробности — в актуальном журнале ниже. Принятый backend — SMB3. Общая копия всех репозиториев и ослабление изоляции не заменяют выдачу доступа. ## Что уже проверено и что остаётся - [x] Создан отдельный Git-проект с зафиксированной границей четырёх функций. - [x] Проверена независимая возможность штатного ACP Balda поверх SSH: [[personal/projects/balda/ssh-acp-verification-2026-10-18]]. - [x] Выбрана и испытана сетевая файловая система: native SMB3, Linux и Mac. - [x] Реализованы четыре функции, native подтверждения, реальный отзыв открытых FD и независимый срок доступа. - [x] Shared Beads/Dolt проверен на fixture: параллельное создание/чтение, единственный победитель claim, чтение через принятый SSH/SRT. - [ ] Полная модельная работа реальных агентов через полученный workspace и реальные project Beads DB; fixture не считается миграцией проектов. Доступ к исходникам и доступ к состоянию 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]].