361 lines
29 KiB
Markdown
361 lines
29 KiB
Markdown
# План работ: 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/<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 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`
|
||
- Статус: 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; появляются регрессионные тесты на invalid `chat_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 сигналов; добавить в `SandboxSessionRepository` `count_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 histograms `sandbox.runtime.create.duration_ms`, `sandbox.runtime.stop.duration_ms`, `sandbox.runtime.list_active.duration_ms`, error counter `sandbox.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_active` failure 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
|