# План работ: master-service MVP sandbox ## Контекст - Источники требований: `docs/master.md`, `meetings/meeting_1_document.md`, `README.md`, `docs/*` - Базовый template уже готов: typed config, DI container, observability, FastAPI adapter и versioned API под `/api/v1` - Текущая цель: минимальное управление Docker sandbox без auth - MVP API: `POST /api/v1/create` - Sandbox policy: TTL `300` секунд, одна активная sandbox на `chat_id`, повторный `create` переиспользует активную сессию - Volume policy: chat volume `rw`, dependencies volume `ro`, lambda-tools volume `ro` - Host paths вычисляются из typed config, а HTTP request передает только `chat_id` - Cleanup выполняется периодическим in-process loop внутри master-service ## Вне текущего scope - auth и access control - p2p lease и WebSocket transport - workspace/chat CRUD API - chat files, artifacts, S3, quota и retention policy - центральная БД и multi-node orchestration ## Правила выполнения - Выполняем по одной задаче - Коммиты не делаем - Если по ходу нужна смена архитектуры, останавливаемся и согласуем решение - `domain/` и `usecase/` не импортируют Docker, FastAPI, OpenTelemetry, env или YAML - Inner layers работают только через минимальные domain сущности и usecase порты ## Очередь задач ### M01. ADR и минимальный sandbox scaffolding - Исполнитель: `primary-agent` - Статус: completed - Зависимости: нет - Commit required: no - Scope: зафиксировать MVP-решение в ADR и создать минимальные сущности, ошибки и usecase-контракты для sandbox orchestration - Файлы: `docs/006-mvp-docker-sandbox-orchestration.md`, `domain/sandbox.py`, `domain/error.py`, `usecase/interface.py`, `usecase/sandbox.py` - Критерии приемки: в `domain/` есть минимальная `SandboxSession` и sandbox-ошибки; в `usecase/` есть порты `SandboxSessionRepository`, `SandboxRuntime` и `Clock`; созданы скелеты `CreateSandbox` и `CleanupExpiredSandboxes`; ADR занимает 10-20 строк ### M02. Typed config для sandbox runtime - Субагент: `feature-developer` - Статус: completed - Зависимости: `M01` - Commit required: no - Scope: расширить typed-config слоем `sandbox` с настройками image, TTL, cleanup interval, host paths и container mount paths - Файлы: `adapter/config/model.py`, `adapter/config/loader.py`, `config/app.yaml` - Решение: chat host path строится как путь под общим `sandbox.chats_root/`; request не передает host path напрямую - Критерии приемки: конфиг собирается в typed dataclass-дерево; дефолтный TTL равен `300`; есть отдельные настройки для `chats_root`, `dependencies_host_path`, `lambda_tools_host_path`, `chat_mount_path`, `dependencies_mount_path`, `lambda_tools_mount_path`, `cleanup_interval_seconds`; inner layers не читают env ### M03. Docker runtime adapter для sandbox lifecycle - Субагент: `feature-developer` - Статус: completed - Зависимости: `M01`, `M02` - Commit required: no - Scope: реализовать outer adapter над Docker для создания и остановки sandbox контейнера с нужными labels и mount policy - Файлы: `adapter/docker/runtime.py`, `adapter/docker/__init__.py` - Ограничения: все Docker-детали остаются в `adapter/`; runtime не должен протекать во внутренние слои - Критерии приемки: runtime умеет создать sandbox container по входным параметрам usecase; chat volume монтируется как `rw`; dependency и lambda-tools volumes монтируются как `ro`; контейнер получает labels с `session_id`, `chat_id` и `expires_at`; runtime переводит ошибки Docker в понятные исключения адаптера ### M04. In-memory session repository и usecase `CreateSandbox` - Субагент: `feature-developer` - Статус: completed - Зависимости: `M01`, `M02`, `M03` - Commit required: no - Scope: реализовать in-memory registry активных sandbox-сессий и usecase создания sandbox с логикой reuse по `chat_id` - Файлы: `repository/sandbox_session.py`, `usecase/sandbox.py`, `adapter/di/container.py` - Решение: если по `chat_id` есть неистекшая сессия, usecase возвращает ее без нового container start; если сессия истекла, usecase инициирует stop старой sandbox и создает новую - Критерии приемки: одна активная sandbox на `chat_id`; TTL-логика использует порт `Clock`; usecase не импортирует Docker; container wiring остается singleton-based ### M05. Cleanup expired sandboxes и lifecycle wiring - Субагент: `feature-developer` - Статус: completed - Зависимости: `M04` - Commit required: no - Scope: реализовать usecase cleanup просроченных sandbox и подключить периодический cleanup loop в FastAPI lifecycle - Файлы: `usecase/sandbox.py`, `adapter/di/container.py`, `adapter/http/fastapi/app.py`, при необходимости `adapter/http/fastapi/dependencies.py` - Ограничения: не ломать ADR про раннее OTel instrumentation; lifecycle loop должен стартовать и останавливаться один раз - Критерии приемки: cleanup находит истекшие сессии, останавливает sandbox через runtime и удаляет их из registry; интервал cleanup берется из конфига; shutdown корректно завершает фоновую задачу ### M06. HTTP endpoint `POST /api/v1/create` - Субагент: `feature-developer` - Статус: completed - Зависимости: `M04` - Commit required: no - Scope: добавить минимальную HTTP ручку для создания или переиспользования sandbox без auth - Файлы: `adapter/http/fastapi/schemas.py`, `adapter/http/fastapi/dependencies.py`, `adapter/http/fastapi/routers/v1/router.py`, при необходимости `adapter/di/container.py` - Request: `{ "chat_id": "..." }` - Response: `session_id`, `chat_id`, `container_id`, `status`, `expires_at` - Критерии приемки: router остается тонким; handler только переводит HTTP input в команду usecase и маппит ошибки в HTTP; endpoint живет под `/api/v1/create`; auth не добавляется ### M07. Тесты для create, reuse, TTL и mount policy - Субагент: `test-engineer` - Статус: completed - Зависимости: `M03`, `M04`, `M05`, `M06` - Commit required: no - Scope: покрыть тестами ключевое поведение MVP без запуска реального production Docker stack - Файлы: `test/*` - Критерии приемки: есть unit-тесты для `CreateSandbox` и `CleanupExpiredSandboxes` с fake clock; есть HTTP smoke-тест для `POST /api/v1/create`; есть adapter-level тест с mock Docker client на mount policy `chat=rw`, `deps=ro`, `tools=ro`; тесты не тащат FastAPI или Docker в inner-layer тесты ### M08. Архитектурный и boundary review по MVP sandbox - Субагент: `code-reviewer` - Статус: completed - Зависимости: `M07` - Commit required: no - Scope: проверить соблюдение clean architecture, dependency direction и соответствие MVP-ограничениям - Файлы: весь измененный код - Критерии приемки: Docker остается только во внешнем adapter; FastAPI не протекает в `domain/` и `usecase/`; TTL и mount policy читаются как явные, тестируемые правила; замечания сформулированы как точечные правки или подтверждение готовности ## Follow-up после M08 review ### M09. Сериализация lifecycle sandbox по `chat_id` - Субагент: `feature-developer` - Статус: completed - Зависимости: `M08` - Commit required: no - Scope: убрать гонки между параллельными `create` и cleanup для одного `chat_id` - Файлы: `usecase/interface.py`, `usecase/sandbox.py`, `repository/sandbox_lock.py` или другой outer-layer lock implementation, `adapter/di/container.py` - Решение: ввести явный usecase-port для process-local lock по `chat_id`; outer-layer реализация держит per-chat lock registry; `CreateSandbox` и `CleanupExpiredSandboxes` выполняют мутации сессии под этим lock - Критерии приемки: для одного `chat_id` не поднимаются два sandbox при concurrent create; create-vs-cleanup не оставляет orphan container; locking не протекает в HTTP и Docker adapter как бизнес-логика ### M10. Устойчивый cleanup и вынос blocking cleanup из event loop - Субагент: `feature-developer` - Статус: pending - Зависимости: `M09` - Commit required: no - Scope: сделать cleanup устойчивым к частичным ошибкам и не блокировать FastAPI event loop синхронным Docker stop - Файлы: `usecase/sandbox.py`, `adapter/http/fastapi/app.py` - Решение: `CleanupExpiredSandboxes` обрабатывает stop/delete по каждой сессии отдельно и продолжает batch; HTTP cleanup loop выносит blocking cleanup work в thread через adapter-layer orchestration - Критерии приемки: ошибка на одной expired session не мешает чистить остальные; background cleanup loop не умирает после ошибки; blocking cleanup больше не выполняется прямо в event loop ### M11. Удаление не-MVP user surface из приложения - Субагент: `feature-developer` - Статус: pending - Зависимости: `M08` - Commit required: no - Scope: убрать из runtime app неотносящиеся к MVP user endpoint и seed user wiring - Файлы: `adapter/http/fastapi/routers/v1/router.py`, `adapter/http/fastapi/dependencies.py`, `adapter/http/fastapi/schemas.py`, `adapter/di/container.py` - Решение: оставить в MVP только `health` и sandbox API; примерный user code может остаться в репозитории как template, но не должен быть подключен в runtime app - Критерии приемки: `GET /api/v1/users/{user_id}` больше не опубликован; container не создает seeded user repository/usecase для runtime app; незапрошенная user-surface area исчезает ### M12. Регрессионные тесты на race conditions и cleanup resilience - Субагент: `test-engineer` - Статус: pending - Зависимости: `M09`, `M10`, `M11` - Commit required: no - Scope: добавить тесты на новые гарантии после review fixes - Файлы: `test/*` - Критерии приемки: есть тест на duplicate create для одного `chat_id`; есть тест на create-vs-cleanup race или эквивалентную сериализацию; есть тест, что cleanup продолжает batch после stop failure; HTTP smoke/regression тесты обновлены под удаление user endpoint ### M13. Повторный boundary review после fix-pass - Субагент: `code-reviewer` - Статус: pending - Зависимости: `M12` - Commit required: no - Scope: проверить, что must-fix и should-fix замечания из `M08` закрыты без нарушения clean architecture - Файлы: весь измененный код после `M09`-`M12` - Критерии приемки: нет гонки на one-sandbox-per-chat; cleanup не блокирует event loop и не валится на первом stop failure; runtime app не публикует лишний user API; замечания сведены к minor или отсутствуют ### M14. Починка mypy-типизации тестов после sandbox MVP - Субагент: `test-engineer` - Статус: completed - Зависимости: `M07` - Commit required: no - Scope: устранить текущие ошибки `make pre-commit` в test-suite без изменения production behavior - Файлы: `test/test_docker_runtime.py`, `test/test_create_http.py`, при необходимости общие test helpers в `test/*` - Ошибки: несовместимый fake Docker client для `DockerSandboxRuntime`, неточная типизация `run_calls` и ASGI message payload, использование `object` вместо типизированных test doubles для `AppRepositories`, `AppUsecases`, `AppContainer` - Решение: сделать test doubles типизированными через совместимые fake classes или локальные protocols; убрать `object` и неиндексируемые `dict[str, object]` там, где mypy не может вывести типы - Критерии приемки: `uv run mypy .` проходит; `make pre-commit` доходит как минимум до pytest stage; production code не меняется или меняется только при явной необходимости для testability