29 KiB
29 KiB
План работ: 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 volumero, lambda-tools volumero - 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/<chat_id>; 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 policychat=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 - Статус: completed
- Зависимости:
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 - Статус: completed
- Зависимости:
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 - Статус: completed
- Зависимости:
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 - Статус: completed
- Зависимости:
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
Follow-up после M13 review
M15. Канонизация и валидация chat_id
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M13 - Commit required: no
- Scope: сделать
chat_idстрого UUID и убрать path alias/whole-root mount риск через неканоничные значения - Файлы:
adapter/http/fastapi/schemas.py,adapter/docker/runtime.py, при необходимостиusecase/sandbox.pyи тесты вtest/* - Решение: принять
chat_idкак UUID на HTTP boundary, использовать его каноничную строковую форму дальше в usecase/repository/path construction и не принимать произвольные path-like строки - Критерии приемки: не-UUID значения отклоняются на HTTP boundary с
400/422; UUID используется как единое каноничное значение для lock key, repository key и filesystem path; появляются регрессионные тесты на invalidchat_id
M16. Lifecycle reconciliation на startup/shutdown
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M13 - Commit required: no
- Scope: устранить restart-gap между in-memory registry и уже запущенными Docker containers
- Файлы:
adapter/docker/runtime.py,adapter/di/container.py,adapter/http/fastapi/app.py, при необходимости новые outer-layer helper files и тесты вtest/* - Решение: основная стратегия — reconciliation по Docker labels на startup, чтобы после restart master-service продолжал видеть уже запущенные sandbox и не поднимал дубликаты; graceful shutdown cleanup остается опциональным дополнением
- Критерии приемки: после restart master-service может восстановить/синхронизировать state по Docker labels без потери работающих agent containers; one-sandbox-per-chat не нарушается из-за пустого in-memory registry; lifecycle policy явно зафиксирована и покрыта тестами
M17. Управление жизненным циклом per-chat locks
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M13 - Commit required: no
- Scope: ограничить неограниченный рост registry locks по числу когда-либо увиденных
chat_id - Файлы:
repository/sandbox_lock.py, при необходимости тесты вtest/* - Решение: добавить eviction/ref-count/weakref policy во внешнем lock registry без нарушения сериализации lifecycle для активного
chat_id - Критерии приемки: registry locks не растет бесконечно без причины; сериализация для активных чатов сохраняется; поведение покрыто тестами
M18. Перевести sandbox ids на UUID types
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M15 - Commit required: no
- Scope: сделать
chat_idиsession_idтипомUUIDвнутри sandbox scope, оставивcontainer_idстрокой как внешний Docker identifier - Файлы:
domain/sandbox.py,usecase/interface.py,usecase/sandbox.py,repository/sandbox_session.py,adapter/http/fastapi/*,adapter/docker/runtime.py,adapter/di/container.py,test/* - Решение: HTTP boundary принимает/возвращает UUID, usecase и repository работают с UUID objects, Docker labels продолжают сериализоваться в строки через
str(uuid) - Критерии приемки: внутри sandbox flow
chat_idиsession_idбольше не строки;container_idостаетсяstr; pydantic корректно сериализует UUID в response;make pre-commitпроходит
Follow-up после issue #11 observability
M19. ADR и observability contracts для sandbox lifecycle
- Исполнитель:
primary-agent - Статус: completed
- Зависимости:
M18 - Commit required: yes
- Commit message:
add sandbox observability contracts - Scope: зафиксировать sandbox lifecycle observability policy в ADR-lite и подготовить минимальные контракты для traces и current-state metrics без нарушения clean architecture
- Файлы:
docs/008-sandbox-lifecycle-observability.md,usecase/interface.py,repository/sandbox_session.py,adapter/otel/metrics.py,adapter/observability/noop.py - Решение: добавить в
Metricsпорт операциюset(...)для gauge-like current-state сигналов; добавить вSandboxSessionRepositorycount_active()как источник truth дляsandbox.active.count - Критерии приемки: ADR занимает 10-20 строк; inner layers по-прежнему знают только порты
Logger/Metrics/Tracer; current-state метрика активных sandbox выражается без OTel imports во внутреннем слое
M20. Трейсы и метрики в sandbox usecases
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M19 - Commit required: yes
- Commit message:
instrument sandbox usecases - Scope: добавить spans и ключевые lifecycle metrics в
CreateSandboxиCleanupExpiredSandboxes - Файлы:
usecase/sandbox.py,adapter/di/container.py, при необходимости тесты вtest/* - Решение: usecase получает
MetricsиTracerчерез конструктор;CreateSandboxиCleanupExpiredSandboxesпубликуютsandbox.create.total,sandbox.cleanup.total,sandbox.cleanup.error.totalи обновляютsandbox.active.countпосле мутаций registry - Критерии приемки: есть spans
usecase.create_sandboxиusecase.cleanup_expired_sandboxes; span attrs и metric attrs включают ключевые lifecycle identifiers/result fields; reuse/replace/cleanup paths наблюдаемы без OTel imports в usecase
M21. Трейсы и runtime metrics в Docker adapter и reconciliation
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M19 - Commit required: yes
- Commit message:
instrument sandbox docker runtime - Scope: добавить observability в
DockerSandboxRuntimeи reconciliation path для Docker operations и current-state sync - Файлы:
adapter/docker/runtime.py,adapter/sandbox/reconciliation.py,adapter/di/container.py, при необходимости тесты вtest/* - Решение:
DockerSandboxRuntimeполучаетMetricsиTracer; create/stop/list публикуют duration histogramssandbox.runtime.create.duration_ms,sandbox.runtime.stop.duration_ms,sandbox.runtime.list_active.duration_ms, error countersandbox.runtime.error.totalи span attrs по chat/session/container; reconciliation обновляетsandbox.active.countпо registry snapshot - Критерии приемки: Docker adapter остается во внешнем слое; ошибки Docker операций отражаются в spans и metrics; после startup reconciliation current-state метрика активных sandbox синхронизирована с registry
M22. Тесты на sandbox observability
- Субагент:
test-engineer - Статус: completed
- Зависимости:
M20,M21 - Commit required: yes
- Commit message:
add sandbox observability tests - Scope: покрыть regression tests новую observability policy без реального OTel backend
- Файлы:
test/test_sandbox_usecase.py,test/test_docker_runtime.py, при необходимости новые focused tests вtest/* - Решение: использовать типизированные fake metrics/tracer implementations и проверить names/attrs ключевых spans и metrics на create/reuse/replace/cleanup/runtime paths
- Критерии приемки: тесты подтверждают spans и metrics на usecase и adapter paths; constructor wiring обновлен без mypy regressions;
make typecheckи релевантныйpytestпроходят
M23. Boundary review для sandbox observability
- Субагент:
code-reviewer - Статус: in_progress
- Зависимости:
M22 - Commit required: no
- Scope: проверить, что observability изменения закрывают issue #11 и FR-034 без нарушения clean architecture
- Файлы: весь измененный код после
M19-M22 - Критерии приемки: inner layers не импортируют OTel; Docker-specific tracing остается в
adapter/docker/; current-state и duration metrics достаточно покрывают sandbox lifecycle; замечания сведены к minor или отсутствуют
Follow-up после M23 boundary review
M24. Исправить replace trace identity в CreateSandbox
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M23 - Commit required: yes
- Commit message:
fix sandbox replace trace identity - Scope: устранить смешение old/new sandbox identifiers в replace path usecase tracing
- Файлы:
usecase/sandbox.py, при необходимости точечные тесты вtest/* - Решение: сохранять старые и новые sandbox identifiers в отдельных span attrs или child spans так, чтобы replace success и replace failure оставались однозначно трассируемыми
- Критерии приемки: replace path не перетирает previous/new identifiers; при replace failure span остается консистентным и отражает обе стороны lifecycle
M25. Добрать failure-path observability regression tests
- Субагент:
test-engineer - Статус: completed
- Зависимости:
M24 - Commit required: yes
- Commit message:
add sandbox observability failure tests - Scope: покрыть tests для replace-failure trace, cleanup error metrics/spans и Docker stop observability
- Файлы:
test/test_sandbox_usecase.py,test/test_docker_runtime.py, при необходимости другие focused tests вtest/* - Решение: использовать presence-based assertions и проверять ключевые span/metric contracts без brittle exact-order checks
- Критерии приемки: есть тест на replace failure tracing; есть тест на
sandbox.cleanup.error.total; есть тесты на Docker stop observability для success/error/not_found или эквивалентного набора outcome paths
M26. Повторный boundary review для sandbox observability
- Субагент:
code-reviewer - Статус: in_progress
- Зависимости:
M25 - Commit required: no
- Scope: подтвердить, что follow-up fixes закрыли M23 замечания без новых boundary нарушений
- Файлы: весь измененный код после
M24-M25 - Критерии приемки: нет замечаний по replace tracing identity и missing failure-path observability coverage; clean architecture по-прежнему соблюдена
Follow-up после M26 boundary review
M27. Компенсация save failure после runtime.create
- Субагент:
feature-developer - Статус: completed
- Зависимости:
M26 - Commit required: yes
- Commit message:
fix sandbox create rollback gap - Scope: не оставлять untracked running container и неконсистентный
sandbox.active.countпри паденииrepository.save()после успешногоruntime.create() - Файлы:
usecase/sandbox.py, при необходимости точечные тесты вtest/* - Решение: сделать create/replace path registry-safe через rollback или другой явный compensation path без нарушения clean architecture
- Критерии приемки: save failure не оставляет новый container в runtime без registry state;
sandbox.active.countотражает финальное committed state; replace и fresh-create failure paths консистентны
M28. Регрессии на rollback и startup failure observability
- Субагент:
test-engineer - Статус: completed
- Зависимости:
M27 - Commit required: yes
- Commit message:
add sandbox rollback regression tests - Scope: покрыть tests для save-failure rollback и startup observability failure paths
- Файлы:
test/test_sandbox_usecase.py,test/test_docker_runtime.py,test/test_create_http.py, при необходимости другие focused tests вtest/* - Решение: добавить tests на fresh-create/replace save failure compensation,
list_activefailure observability и reconciliation failure span/metric expectations где применимо - Критерии приемки: rollback path покрыт; list/reconciliation failure observability не регрессирует; tests остаются presence-based и стабильными
M29. Финальный boundary review для sandbox observability
- Субагент:
code-reviewer - Статус: completed
- Зависимости:
M28 - Commit required: no
- Scope: подтвердить, что M27-M28 закрыли remaining M26 замечания
- Файлы: весь измененный код после
M27-M28 - Критерии приемки: нет замечаний по rollback gap и startup failure observability coverage; sandbox observability slice приемлем as-is