From 9838285ff2cd0e49dcfe79f9d05776440a771916 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 18 Oct 2026 16:28:56 +0600 Subject: [PATCH] [2026-10-18] eagle: personal/plans/balda-callee-prism-plan.md personal/plans/balda-remote-agent.md personal/projects/balda/balda.md personal/projects/balda/execution-broker.md personal/projects/balda/setup.md personal/projects/balda/ssh-acp-verification-2026-10-18.md --- personal/plans/balda-callee-prism-plan.md | 7 +- personal/plans/balda-remote-agent.md | 61 +++++++ personal/projects/balda/balda.md | 8 +- personal/projects/balda/execution-broker.md | 7 + personal/projects/balda/setup.md | 30 +++- .../balda/ssh-acp-verification-2026-10-18.md | 159 ++++++++++++++++++ 6 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 personal/plans/balda-remote-agent.md create mode 100644 personal/projects/balda/ssh-acp-verification-2026-10-18.md diff --git a/personal/plans/balda-callee-prism-plan.md b/personal/plans/balda-callee-prism-plan.md index 88b3e4ca..292b1264 100755 --- a/personal/plans/balda-callee-prism-plan.md +++ b/personal/plans/balda-callee-prism-plan.md @@ -1,4 +1,9 @@ -# Balda / Execution Broker — план приёмки +# Balda / Execution Broker — историческая приёмка + +> [!warning] Новый план от 2026-10-18 +> Актуальная работа: [[personal/plans/balda-remote-agent|Balda — удалённый агент по SSH]]. +> Ниже сохранена приёмка прежней реализации; её компоненты и открытые пункты не переносятся автоматически в новый план. +> Штатный ACP Balda через SSH уже проверен: [[personal/projects/balda/ssh-acp-verification-2026-10-18]]. Проверено: **2026-10-16**. Приёмка закрыта в согласованном проверенном объёме. Сверка main/production и публикация Virfield v2.1.1 завершены; датированные доказательства и ограничения сохранены ниже. Точные работающие версии и доказательства: [[personal/projects/balda/execution-broker]]. Запуск двух Balda: [[personal/projects/balda/setup]]. diff --git a/personal/plans/balda-remote-agent.md b/personal/plans/balda-remote-agent.md new file mode 100644 index 00000000..48a17cdf --- /dev/null +++ b/personal/plans/balda-remote-agent.md @@ -0,0 +1,61 @@ +# Balda — удалённый агент по SSH + +Обновлено: **2026-10-18**. +Исходная задача: Balda работает с оригинальными Prism/Beads по подключённым проектам и запускает штатного агента в другой среде; подключение нового репозитория требует подтверждения владельца. +Проверка: [[personal/projects/balda/ssh-acp-verification-2026-10-18]]. +Рабочая установка: [[personal/projects/balda/setup]]. + +## Принятая граница + +Максимально использовать штатный стек Balda: провайдеры ACP, сессии, управление задачами, MCP, skills и подтверждения. +Prism, Prism Callee, Callee и Beads — оригинальные, с сохранением их собственных контрактов. + +```text +Balda → штатный generic_acp → ssh -T → штатный ACP-агент в другой среде +``` + +Единый способ связи — SSH: контейнер, VM или физический хост отличаются окружением, а не протоколом запуска. +macOS-окружения предоставляет Virfield через его MCP. +Штатный агент выполняет работу в выданном workspace; Balda сохраняет управление беседой и сессией. + +## Что проверено + +- [x] Штатная фабрика Balda запускает `ssh` через `generic_acp.cmd` и передаёт ACP. +- [x] Реальный OpenCode создаёт и возобновляет сессию после повторного SSH. +- [x] Контрольный ACP-агент получает штатное решение Balda разрешить или отклонить действие. +- [x] Контрольный удалённый агент вызывает настоящий MCP-инструмент. +- [x] Штатные тесты Balda подтверждают Mattermost review, защиту от чужого ответа и timeout. +- [x] Воспроизведены локальная проверка cwd и требования к достижимости MCP. + +Для передачи ACP новая реализация `broker-acp` не требуется: базовый путь уже проверен. +Дополнительный код допустим только для конкретного воспроизведённого пробела интеграции; прикладные сценарии и учёт фаз Prism остаются в штатном стеке. + +## Следующие конкретные действия + +1. Я настрою для выбранной среды SSH-профиль с отдельной identity, закреплённым host key и ограниченным пользователем. +2. Я предоставлю среде только подтверждённый проект и проверю фактический доступ к его файлам и исходному состоянию Beads; отдельно проверю отказ доступа к соседнему проекту и секретам хоста. +3. Я приведу пути workspace к совместимому виду и настрою достижимые из среды адреса выбранных MCP; проверю чтение статуса через `bd` и вызов MCP. +4. Я подготовлю в среде штатный ACP-агент, его собственную авторизацию и оригинальные skills/tools, затем добавлю в Balda отдельный `generic_acp` provider с командой SSH. +5. Я настрою запросы разрешений у агента и штатный `ask` в Balda, затем проверю реальный рабочий цикл Prism, отказ, отмену и продолжение той же сессии. + +Это оставшаяся работа, а не уже выполненный deployment. + +## Права и репозитории + +Подтверждение подключения репозитория, подтверждение действия агента и файловая изоляция — три отдельные проверки. +Агент не получает полномочий самостоятельно выдавать себе доступ к новому проекту. +Весь каталог `Developer` не передаётся Balda или исполнителю. +Исходная БД Beads соответствующего проекта остаётся источником статусов; Git-клон сам по себе не доказывает наличие актуального состояния Beads. +Механизм выдачи доступа к реальным проектам и их БД ещё требует настройки и приёмки. + +## Текущее состояние и границы + +Основная Balda получила оригинальные Prism/Prism Callee, `bd`, Callee и прямой Virfield MCP; это установка инструментов, а не приёмка удалённого исполнения. +В рабочем конфиге остаётся `permissions.mode: allow_all`; SSH-проверка production-конфиг, историю и авторизацию не меняла. +Полный Prism lifecycle с моделью и реальные удалённые Claude/Codex этой проверкой не приняты. + +Новая ветка: `broker-rebuild`, worktree `/Users/admin/.codex/worktrees/broker-rebuild/execution-broker`. +Она начата с пустого дерева коммитом `1fd7f57`; сейчас есть только незакоммиченный README, реализации нового брокера нет. +Старые `execution-broker`, `execution-prism` и plugin остаются в действующей Balda; очистка новой ветки не удаляла их из deployment. + +Историческая приёмка прежнего брокера: [[personal/plans/balda-callee-prism-plan]]. diff --git a/personal/projects/balda/balda.md b/personal/projects/balda/balda.md index d513d364..a58d26f8 100755 --- a/personal/projects/balda/balda.md +++ b/personal/projects/balda/balda.md @@ -1,9 +1,11 @@ # Balda — актуальный обзор -Проверено: **2026-10-16**. Balda принимает задачи из чата и связывает provider execution, сессии и интеграции. Текущий локальный запуск — **два инстанса на Mac, Mattermost, Codex ACP**: `balda-mm-mac` и `balda-mm-eagle`. Оба работают на чистом `upstream/main` — `3a58254`, без локальных патчей; Eagle имеет те же основные MCP и дополнительный Asana readonly. Точные версии и проверенные границы продолжений — в карточке запуска. +Проверено: **2026-10-18**. Balda принимает задачи из чата и использует штатные провайдеры, сессии, MCP и skills. Основной `balda-mm-mac` работает на `balda-prism:20261018` с базой `balda-upstream:a908ee1a`; Eagle — на `balda-upstream:a908ee1a`. В основной инстанс установлены оригинальные Prism/Prism Callee, Beads, Callee и прямой Virfield MCP; детали и границы проверки — в [[personal/projects/balda/setup]]. ## Источники текущего состояния +- [[personal/plans/balda-remote-agent|Актуальный план удалённого агента по SSH]] +- [[personal/projects/balda/ssh-acp-verification-2026-10-18|Проверка штатного ACP, разрешений и восстановления сессии]] - [[personal/projects/balda/setup|Работающий контейнер, конфигурация, MCP и перезапуск]] - [[personal/projects/balda/execution-broker|Broker/Balda: проверенные версии, артефакты и границы приёмки]] - [[personal/projects/balda/soul|Снимок global instruction]] @@ -17,9 +19,9 @@ Upstream: [baldaworks/balda](https://github.com/baldaworks/balda). Fork: [mallex ## Конфигурация и владение -Действующий конфиг: `/Users/admin/Docker/balda-mm-mac/config/config.yaml`. Dev-конфиг в `~/Developer/balda` — отдельный файл. Provider — `codex_acp`, reasoning `medium`; Obsidian MCP — `type: http`, `http://host.docker.internal:3101/mcp`. +Действующий конфиг: `/Users/admin/Docker/balda-mm-mac/config/config.yaml`; выбран `balda.provider: pool`. Dev-конфиг в `~/Developer/balda` — отдельный файл. Obsidian MCP — `type: http`, `http://host.docker.internal:3101/mcp`. -Balda и локальные агенты владеют оркестрацией, MCP, search/Obsidian и прикладными workflows. Execution Broker владеет disposable worker, lifecycle, credentials, инструментами и артефактами; принятый объём и ограничения перечислены в карточке Broker. +Balda сохраняет штатную оркестрацию, сессии, MCP, skills и подтверждения. Связь с агентом в другой среде — через SSH и штатный ACP; базовый путь уже проверен без нового ACP-брокера. Окружение получает выбранный workspace и ограниченные права; macOS предоставляет Virfield. Исторические функции прежнего Broker и его подключённые MCP описаны отдельно и не задают объём нового брокера. Документация кода: [README](https://github.com/baldaworks/balda/blob/main/README.md), [configuration](https://github.com/baldaworks/balda/blob/main/docs/reference/configuration.md), [plugins](https://github.com/baldaworks/balda/blob/main/docs/reference/plugins.md). diff --git a/personal/projects/balda/execution-broker.md b/personal/projects/balda/execution-broker.md index 6880286a..aee967a8 100755 --- a/personal/projects/balda/execution-broker.md +++ b/personal/projects/balda/execution-broker.md @@ -1,5 +1,12 @@ # Execution Broker — эксплуатация +> [!important] Граница нового брокера — 2026-10-18 +> Текущий план: [[personal/plans/balda-remote-agent|Balda — удалённый агент по SSH]]. +> Штатный `generic_acp` Balda уже передаёт ACP по SSH; собственный ACP-транспорт для проверенного пути не требуется. +> Ветка `broker-rebuild` начата с пустого дерева; в ней пока README, новой реализации нет. +> Старые MCP `execution-broker`, `execution-prism` и plugin остаются подключены в рабочей Balda. +> Сведения о версиях, workflow и прежней приёмке ниже — исторический срез 2026-10-16; установка Balda обновлена в [[personal/projects/balda/setup]]. + Проверено: **2026-10-16** по GitHub, исходникам, работающим контейнерам, binary hashes, MCP и сохранённому state. Broker и Virfield очищены от планов и персональных журналов в tracked файлах; актуальная установка и приёмка ведутся в Obsidian. ## Развёрнутый комплект diff --git a/personal/projects/balda/setup.md b/personal/projects/balda/setup.md index 507fabfa..536e7e47 100755 --- a/personal/projects/balda/setup.md +++ b/personal/projects/balda/setup.md @@ -1,10 +1,38 @@ # Balda — актуальный локальный запуск +## Актуальное обновление — 2026-10-18 + +План удалённого исполнения: [[personal/plans/balda-remote-agent]]. +Проверка штатного ACP поверх SSH: [[personal/projects/balda/ssh-acp-verification-2026-10-18]]. + +- Основной `balda-mm-mac`: image `balda-prism:20261018`, база `balda-upstream:a908ee1a`; проверенный checkout Balda — `a908ee1a5ff2`. +- `balda-mm-eagle` работает на `balda-upstream:a908ee1a`; добавления Prism/Virfield в этой сессии выполнялись только для основного инстанса. +- В основном конфиге выбран `balda.provider: pool`; настроены также провайдеры Codex, Claude Code, OpenCode и hosted API. +- В основном инстансе установлены оригинальные managed plugins `prism` и `prism-callee` версии `0.7.0`. +- Установлен `bd 1.3.1`; npm-пакет Callee `0.22.2` содержит бинарник, сообщающий `0.22.1`. +- Оригинальный Callee pack импортирован в постоянный `/home/node/.config/callee`; обычный каталог разрешает `prism/lifecycle` как Router, `prism/story` и `prism/epic` как Sequential. +- Virfield MCP подключён напрямую: `http://host.docker.internal:7780/mcp`, доступен провайдерам основного инстанса; проверены discovery 20 tools и чтение `virfield_status`. +- В native catalog сохранились `execution-broker`, `execution-prism`, `obsidian` и plugin `execution-broker`; это отдельные прежние интеграции. +- `balda.permissions.mode: allow_all`, `working_dir: /workspace`, `workspace.mode: off`. +- Уже подключён `/Users/admin/Docker/balda-mm-mac/work:/workspace/work`; общего mount `/Users/admin/Developer` в Balda нет. +- Установка сохранила 49 прежних session bindings и их capability snapshot IDs; историю и авторизации не сбрасывали. +- Новые сессии выбирают текущий каталог; существующие сохраняют закреплённый snapshot до штатного reset, который автоматически не выполнялся. +- Рабочие Balda, WebUI proxy и Eagle после SSH-проверки продолжают работать; временные проверочные контейнеры и производный образ удалены. +- Production-конфигурация на удалённый SSH provider ещё не переключалась; полный Prism lifecycle и реальные удалённые Claude/Codex не приняты. + +Операторские версии и backup: `/Users/admin/Docker/balda-mm-mac/prism-runtime/setup.json` и `state/setup-backups/prism-20261018T151411/`. +SSH-проверка не вызывала модели и не отправляла сообщения в реальные чаты. +Доказательства проверки сохранены в самой заметке [[personal/projects/balda/ssh-acp-verification-2026-10-18]], включая JSON, логи и SHA256 исходных файлов. + +## История установки до обновления 2026-10-18 + +Далее сохранены датированные сведения прежней установки; старые одинаковые image/provider/MCP обоих инстансов относятся к указанным старым проверкам. + Проверено: **2026-10-16** по обоим работающим контейнерам, Compose, binary hashes, сохранённому state и authenticated Broker/Prism MCP. Обзор: [[personal/projects/balda/balda|Balda]]. Broker, версии и границы приёмки: [[personal/projects/balda/execution-broker|Execution Broker — текущий локальный контур]]. -## Что работает сейчас +## Снимок установки 2026-10-16 | Параметр | Фактическое значение | | --- | --- | diff --git a/personal/projects/balda/ssh-acp-verification-2026-10-18.md b/personal/projects/balda/ssh-acp-verification-2026-10-18.md new file mode 100644 index 00000000..664cd6f7 --- /dev/null +++ b/personal/projects/balda/ssh-acp-verification-2026-10-18.md @@ -0,0 +1,159 @@ +# Balda → SSH → ACP: проверка 2026-10-18 + +Проверено на исходниках 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 +```