166 lines
12 KiB
Markdown
166 lines
12 KiB
Markdown
# Balda → SSH → ACP: проверка 2026-10-18
|
||
|
||
## Статус этой заметки — 2026-10-19
|
||
|
||
Ниже сохранён датированный технический прогон 2026-10-18, включая его тогдашние открытые проверки. Текущая эксплуатационная сводка: [[personal/projects/balda/setup]], планы: [[personal/plans/balda-remote-agent]] и [[personal/plans/workspace-gateway]]. С тех пор приняты production SSH/SRT к Mac, SMB доступ/отзыв, shared Beads fixture, native approval bridge и generic SSH Manager 0.3.2 с импортом существующего доверенного доступа Mac и проверкой установленного ключа из клиента; текущая приёмка: [[personal/projects/balda/ssh-client-route-2026-10-19]]. Полный модельный Prism lifecycle и реальные project DB остаются отдельной приёмкой.
|
||
|
||
## Исторический протокол проверки
|
||
|
||
Проверено на исходниках Balda `a908ee1a5ff2`, runtime `v2.6.3`, ACP adapter `v2.6.1`.
|
||
Актуальный план: [[personal/plans/balda-remote-agent]].
|
||
|
||
## Вывод
|
||
|
||
Штатный `generic_acp.cmd` Balda запускает обычный OpenSSH-клиент и работает с ACP-агентом по SSH без отдельной реализации ACP-транспорта.
|
||
|
||
## Что именно запускалось
|
||
|
||
- Контрольный SSH-сервер на `golang.org/x/crypto/ssh` в отдельном временном контейнере; клиент — настоящий `/usr/bin/ssh -T` с проверкой закреплённого ключа хоста.
|
||
- Штатная фабрика провайдеров Balda/runtime, настоящий ADK runner и штатный обработчик разрешений Balda.
|
||
- Контрольный ACP-агент для разрешения, отказа, отмены и вызова настоящего HTTP MCP-инструмента.
|
||
- Реальный установленный OpenCode: `initialize`, `session/new`, разрыв SSH, новое подключение и `session/resume`.
|
||
- Штатные тесты Balda для подтверждения в Mattermost, отклонения чужого ответа и отмены по timeout; сообщений в реальный Mattermost не отправляли.
|
||
|
||
Контейнер работал от `node`, с read-only rootfs, `cap-drop=ALL`, без privileged и host mounts.
|
||
Каталог `/Users/admin/Developer` и production `/state/state.db` внутри отсутствовали.
|
||
Тестовые контейнеры и производный образ удалены; исходные Balda и Eagle продолжили работу.
|
||
|
||
## Выявленные условия
|
||
|
||
- Высокоуровневый ACP adapter Balda проверяет `cwd` локально; путь, существующий только на удалённом хосте, отклоняется до создания ACP-сессии.
|
||
- Для прямого SSH нужны согласованные абсолютные пути либо отдельное сопоставление cwd; один `cd` в удалённой команде не меняет `session/new.cwd`.
|
||
- MCP-адреса должны быть достижимы из удалённой среды и проходить проверку HTTP hostname; Balda автоматически не создаёт SSH port forwarding.
|
||
- В тесте вызов MCP прошёл по явно настроенному достижимому адресу; проверка DNS rebinding разрешала только тестовые hostname.
|
||
- `permissions.mode: ask` обрабатывает `session/request_permission`, поступивший от агента; режим самого агента также должен запрашивать разрешения.
|
||
- Файловая изоляция обеспечивается правами ОС и sandbox среды; выбор cwd её не заменяет.
|
||
- На момент проверки рабочая конфигурация Balda сохраняла `allow_all`; тест не менял её.
|
||
|
||
## Границы доказательства
|
||
|
||
Модели не вызывались; полный Prism lifecycle и реальные удалённые Claude/Codex не запускались.
|
||
Проверка протокола не является приёмкой production SSH-хоста, полной политики sandbox каждого агента или доступа к исходной БД Beads.
|
||
Для реального OpenCode проверены создание и возобновление сессии, а подтверждения и вызов MCP проверены контрольным ACP-агентом.
|
||
Поддержка продолжения сессии зависит от capability и сохранённого состояния конкретного агента.
|
||
|
||
## Готовые проекты
|
||
|
||
- [Scarf: ACP Subprocess](https://github.com/awizemann/scarf/wiki/ACP-Subprocess) и исходный `ProcessACPChannel.swift`: ACP поверх `ssh -T`; изучены документация и код, приложение не запускалось и не является плагином Balda.
|
||
- [acp-tunnel](https://github.com/benthecarman/acp-tunnel), revision `6e0543cd5692a0c2a27f5a7b11fb1ad536315461`: просмотрены исходники преобразования cwd и MCP policy; транспорт WebSocket, бинарник не запускался.
|
||
- Утверждение, что такой прослойки нигде нет и её обязательно писать заново, проверкой опровергнуто.
|
||
|
||
## Сохранённый отчёт
|
||
|
||
```json
|
||
{
|
||
"source": {
|
||
"balda": "a908ee1a5ff2",
|
||
"runtime": "v2.6.3",
|
||
"acpagent": "v2.6.1",
|
||
"remote_image": "balda-prism:20261018"
|
||
},
|
||
"verified": {
|
||
"native_generic_acp_with_real_ssh": true,
|
||
"native_allow_and_deny_callback_roundtrip": true,
|
||
"ask_without_context_denied": true,
|
||
"native_mattermost_permission_unit_tests": true,
|
||
"wrong_requester_rejected_unit_test": true,
|
||
"permission_timeout_cancels_unit_test": true,
|
||
"actual_remote_http_mcp_tool_call": true,
|
||
"local_only_cwd_validation_reproduced": true,
|
||
"prompt_cancellation_bounded_ms": 253,
|
||
"fixture_session_resume_after_ssh_reconnect": true,
|
||
"real_opencode_initialize_and_new_session_over_ssh": true,
|
||
"real_opencode_resume_after_ssh_reconnect": true,
|
||
"pinned_host_key_mismatch_rejected": true,
|
||
"test_container_host_repositories_and_production_state_unavailable": true
|
||
},
|
||
"requirements": [
|
||
"SSH credentials and pinned host key",
|
||
"Prepared remote ACP agent and native authentication",
|
||
"Remote repository present; matching cwd paths or cwd mapping adapter",
|
||
"MCP endpoint reachable from remote environment with correct hostname policy",
|
||
"OS filesystem isolation independently of ACP permissions",
|
||
"Native ask policy for interactive review; production config currently allow_all"
|
||
],
|
||
"limits": {
|
||
"model_inference_performed": false,
|
||
"production_config_changed": false,
|
||
"production_sessions_reset": false,
|
||
"external_chat_messages_sent": false,
|
||
"real_claude_or_codex_remote_runs_tested": false,
|
||
"full_prism_lifecycle_tested": false,
|
||
"acp_tunnel_binary_run": false,
|
||
"scarf_application_run": false
|
||
},
|
||
"existing_projects": {
|
||
"acp_tunnel": {
|
||
"revision": "6e0543cd5692a0c2a27f5a7b11fb1ad536315461",
|
||
"assessment": "Source reviewed: WebSocket transport, cwd mapping and MCP policy; no SSH transport; not required for tested direct SSH path"
|
||
},
|
||
"scarf": "Primary source reviewed: ssh -T carries ACP and permission requests; separate application, not a Balda plugin"
|
||
},
|
||
"cleanup": "Both temporary containers and their derived image removed; baseline image preserved",
|
||
"evidence": [
|
||
"compat.log",
|
||
"opencode-live.log",
|
||
"compat_test.go",
|
||
"cmd/fixture/main.go"
|
||
],
|
||
"evidence_sha256": {
|
||
"compat.log": "5952082d73d418b41b0d92661305926ed5f5a902a250f735562a417ddc7ab2b1",
|
||
"opencode-live.log": "4a9fcaa1fe7619dbb371bffad6327c7364a6f5dfd3bc34c973aa8323eb05df5d",
|
||
"compat_test.go": "d3eb2ccc55c4b5ce7b479a3f7d02689219b6c9a32880f76efb9a79665fa0ded5",
|
||
"cmd/fixture/main.go": "90188cb209f97b5b11e015f3c76decc7219398dd66abc037ea013dea9aaec80b"
|
||
}
|
||
}
|
||
```
|
||
|
||
## Протокол SSH-проверки
|
||
|
||
```text
|
||
=== RUN TestNativeGenericACPThroughSSH
|
||
=== RUN TestNativeGenericACPThroughSSH/approve
|
||
=== RUN TestNativeGenericACPThroughSSH/deny
|
||
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
=== RUN TestNativeGenericACPThroughSSH/ask_missing_context_fails_closed
|
||
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
--- PASS: TestNativeGenericACPThroughSSH (0.16s)
|
||
--- PASS: TestNativeGenericACPThroughSSH/approve (0.05s)
|
||
--- PASS: TestNativeGenericACPThroughSSH/deny (0.05s)
|
||
--- PASS: TestNativeGenericACPThroughSSH/ask_missing_context_fails_closed (0.06s)
|
||
=== RUN TestMCPRealToolThroughSSH
|
||
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
--- PASS: TestMCPRealToolThroughSSH (0.06s)
|
||
=== RUN TestNativeRemoteOnlyCWDRejectedLocally
|
||
compat_test.go:124: observed local validation: stat acp session cwd "/tmp/acp-remote-only-20261018": stat /tmp/acp-remote-only-20261018: no such file or directory
|
||
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
--- PASS: TestNativeRemoteOnlyCWDRejectedLocally (0.03s)
|
||
=== RUN TestCancellationThroughSSH
|
||
compat_test.go:135: native cancellation completed in 252.165125ms: context deadline exceeded
|
||
2026/10/18 16:21:02 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
|
||
--- PASS: TestCancellationThroughSSH (0.28s)
|
||
=== RUN TestResumeAfterSSHReconnect
|
||
--- PASS: TestResumeAfterSSHReconnect (0.09s)
|
||
=== RUN TestRealOpenCodeACPInitializeAndSessionOverSSH
|
||
compat_test.go:194: real OpenCode ACP initialization and session/new succeeded without inference
|
||
--- PASS: TestRealOpenCodeACPInitializeAndSessionOverSSH (0.81s)
|
||
=== RUN TestPinnedHostKeyRejectsMismatch
|
||
--- PASS: TestPinnedHostKeyRejectsMismatch (0.01s)
|
||
=== RUN TestConfigurationRecord
|
||
compat_test.go:226: {"acpagent":"v2.6.1","balda":"a908ee1a","model_inference":false,"production_changes":false,"runtime":"v2.6.3"}
|
||
--- PASS: TestConfigurationRecord (0.00s)
|
||
PASS
|
||
```
|
||
|
||
## Реальный OpenCode после обязательного initialize и с повторным SSH
|
||
|
||
```text
|
||
=== RUN TestRealOpenCodeACPInitializeAndSessionOverSSH
|
||
compat_test.go:189: real OpenCode capabilities: {"auth":{},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"sessionCapabilities":{"close":{},"fork":{},"list":{},"resume":{}}}
|
||
compat_test.go:198: real OpenCode ACP initialization and session/new succeeded without inference
|
||
compat_test.go:213: real OpenCode session/resume after SSH reconnect succeeded without inference
|
||
--- PASS: TestRealOpenCodeACPInitializeAndSessionOverSSH (1.56s)
|
||
PASS
|
||
```
|