[feat] add context and tasks for master-service

This commit is contained in:
Azamat 2026-04-02 11:57:20 +03:00
parent c5d5e243c1
commit f0c4988b44
3 changed files with 602 additions and 98 deletions

170
tasks.md
View file

@ -1,142 +1,116 @@
# План работ: web-python-skelet
# План работ: master-service MVP sandbox
## Контекст
- Источник требований: `AGENTS.md` и ADR `docs/001`-`docs/004`
- Текущее состояние: в `adapter/`, `domain/`, `usecase/`, `repository/`, `test/` пока только `__init__.py`
- Отсутствуют рабочие каталоги и файлы из целевой структуры: `adapter/config/`, `adapter/otel/`, `adapter/di/`, `adapter/http/fastapi/`, `config/`, `main.py`
- Ограничения: `docs/` и `tasks.md` не добавлять в git; коммиты не делать; работать по одной задаче
- ADR пока покрывают архитектуру, новые ADR нужны только если по ходу работ изменится решение
- Источники требований: `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
## Правила выполнения
- Каждую задачу выполнять отдельным заходом, без параллельной реализации
- Каждый субагент отдает diff, список измененных файлов и проверку, но не делает commit
- Если в задаче всплывает архитектурное изменение, остановиться и вынести вопрос на согласование
- Выполняем по одной задаче
- Коммиты не делаем
- Если по ходу нужна смена архитектуры, останавливаемся и согласуем решение
- `domain/` и `usecase/` не импортируют Docker, FastAPI, OpenTelemetry, env или YAML
- Inner layers работают только через минимальные domain сущности и usecase порты
## Очередь задач
### T01. Базовый каркас домена и usecase
### M01. ADR и минимальный sandbox scaffolding
- Исполнитель: `primary-agent` (scaffolding)
- Статус: completed
- Исполнитель: `primary-agent`
- Статус: pending
- Зависимости: нет
- Commit required: no
- Scope: создать базовые файлы и контракты в `domain/`, `usecase/`, `repository/`
- Файлы: `domain/error.py`, `domain/user.py`, `usecase/interface.py`, `usecase/user.py`, `repository/user.py`
- Критерии приемки: зависимости направлены внутрь; в `domain/` и `usecase/` нет FastAPI/OTel; есть пример сущности, ошибок, портов и простого usecase
- 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 строк
### T02. Конфиг из YAML и env
### M02. Typed config для sandbox runtime
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T01`
- Статус: pending
- Зависимости: `M01`
- Commit required: no
- Scope: собрать typed-config слой в `adapter/config/` и подготовить базовые yaml-файлы
- Файлы: `adapter/config/*`, `config/app.yaml`
- Критерии приемки: конфиг собирается в одну dataclass-структуру; секреты читаются из env; парсинг и валидация не протекают в inner layers
- 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
### T03. Observability порты и OTel adapter
### M03. Docker runtime adapter для sandbox lifecycle
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T01`, `T02`
- Статус: pending
- Зависимости: `M01`, `M02`
- Commit required: no
- Scope: реализовать логгер, метрики, трейсинг и bootstrap OTel в `adapter/otel/` через интерфейсы из `usecase/interface.py`
- Файлы: `adapter/otel/*`, `config/otel-collector.yaml`
- Критерии приемки: inner layers знают только интерфейсы; OTLP exporter настраивается из конфига; нет кастомного trace middleware
- 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 в понятные исключения адаптера
### T04. Composition root и lifetime singleton-объектов
### M04. In-memory session repository и usecase `CreateSandbox`
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T01`, `T02`, `T03`
- Статус: pending
- Зависимости: `M01`, `M02`, `M03`
- Commit required: no
- Scope: собрать контейнер и startup wiring в `adapter/di/`
- Файлы: `adapter/di/container.py`, `adapter/di/__init__.py`
- Критерии приемки: repository/usecase создаются один раз на старте; контейнер хранит инстансы явно; нет пересоздания на HTTP-запрос
- 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
### T05. FastAPI adapter как заменяемый web layer
### M05. Cleanup expired sandboxes и lifecycle wiring
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T04`
- Статус: pending
- Зависимости: `M04`
- Commit required: no
- Scope: поднять HTTP adapter в `adapter/http/fastapi/` с app factory, lifespan, dependencies, middleware и router ` /api/v1`
- Файлы: `adapter/http/fastapi/*`, `main.py`
- Критерии приемки: FastAPI изолирован в adapter-слое; handlers тонкие; request logging и metrics middleware подключены; usecase/repository берутся из контейнера
- 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 корректно завершает фоновую задачу
### T06. Локальный runtime и compose-окружение
### M06. HTTP endpoint `POST /api/v1/create`
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T02`, `T03`, `T05`
- Статус: pending
- Зависимости: `M04`
- Commit required: no
- Scope: добавить контейнерный runtime для сервиса и compose-окружение с OTel и UI для просмотра логов, метрик и трейсов
- Файлы: `Dockerfile`, `docker-compose.yml`
- Ограничения: не трогать репозиторный `config/app.yaml`; docker должен прокидывать свой runtime-config внутрь контейнера; Dockerfile только с двумя стадиями `build` и `run`
- Критерии приемки: `make compose-build` и `make compose-up` опираются на существующие файлы; сервис поднимается в контейнере; OTel telemetry уходит в dockerized stack; есть UI для просмотра логов, метрик и трейсов; для локального docker-окружения достаточно только `Dockerfile` и `docker-compose.yml`
- 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 не добавляется
### T07. Тесты на lifetimes, config и HTTP smoke
### M07. Тесты для create, reuse, TTL и mount policy
- Субагент: `test-engineer`
- Статус: pending
- Зависимости: `T01`, `T02`, `T03`, `T04`, `T05`, `T06`
- Зависимости: `M03`, `M04`, `M05`, `M06`
- Commit required: no
- Scope: покрыть тестами ключевые архитектурные гарантии
- Scope: покрыть тестами ключевое поведение MVP без запуска реального production Docker stack
- Файлы: `test/*`
- Критерии приемки: есть тест на singleton lifetime для repository/usecase; есть тест merge YAML+env; есть smoke-тест для ` /api/v1`; тесты не тянут FastAPI/OTel в inner layers
- Критерии приемки: есть 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 тесты
### T08. Архитектурный и boundary review
### M08. Архитектурный и boundary review по MVP sandbox
- Субагент: `code-reviewer`
- Статус: pending
- Зависимости: `T07`
- Зависимости: `M07`
- Commit required: no
- Scope: проверить импорты, соблюдение слоев, startup lifetimes и заменяемость web adapter
- Scope: проверить соблюдение clean architecture, dependency direction и соответствие MVP-ограничениям
- Файлы: весь измененный код
- Критерии приемки: dependency direction не нарушен; FastAPI и OTel не протекают в `domain/` и `usecase/`; замечания сформулированы как точечные правки или подтверждение готовности к review
### T09. Конфигурируемый runtime observability
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T03`, `T04`, `T05`
- Commit required: no
- Scope: сделать конфигурируемый runtime observability с настраиваемым sink и форматом логов, плюс отдельными флагами для метрик и трейсов
- Файлы: `adapter/config/*`, `adapter/observability/*`, `adapter/otel/*`, `adapter/di/*`, `adapter/http/fastapi/*`, `config/app.yaml`, при необходимости `main.py`
- Конфиг: `logging.output=stdout|file|otel`, `logging.format=text|json`, `logging.file_path=...`, `metrics.enabled=true|false`, `tracing.enabled=true|false`
- Решение: вынести выбор runtime в отдельный adapter-layer factory; `domain/` и `usecase/` не менять; для `stdout` и `file` поддержать оба формата `text` и `json`; `logging.file_path` использовать только при `logging.output=file`; при отключенных метриках и трейсах использовать `Noop`-реализации; OTel runtime поднимать только если нужен хотя бы для одного сигнала
- Критерии приемки: при `logging.output=stdout` логи идут в stdout в формате `text` или `json` по конфигу; при `logging.output=file` логи пишутся в файл по пути `logging.file_path` в формате `text` или `json`; при `logging.output=otel` логи уходят в collector; `metrics.enabled=false` отключает метрики и metrics middleware; `tracing.enabled=false` отключает FastAPI instrumentation и tracer runtime; DI продолжает отдавать единый runtime через контейнер; внутренние слои по-прежнему знают только порты
### T10. ADR: раннее подключение OTel к FastAPI
- Исполнитель: `primary-agent` (docs)
- Статус: completed
- Зависимости: нет
- Commit required: no
- Scope: зафиксировать правило, что FastAPI OTel instrumentation выполняется до первой сборки `middleware_stack`
- Файлы: `docs/005-fastapi-otel-early-instrumentation.md`
- Критерии приемки: ADR занимает 10-20 строк; описаны context, decision, consequences; решение не переписывает историю прошлых ADR
### T11. Перенос FastAPI OTel bootstrap в app factory
- Субагент: `feature-developer`
- Статус: completed
- Зависимости: `T10`
- Commit required: no
- Scope: перенести создание container, установку `FastAPIInstrumentor.instrument_app(...)` из `lifespan` в `create_app`, оставив в `lifespan` только shutdown
- Файлы: `adapter/http/fastapi/app.py`, `adapter/http/fastapi/lifespan.py`, при необходимости `adapter/http/fastapi/dependencies.py`
- Ограничения: не использовать ручной rebuild `app.middleware_stack`; не менять `domain/` и `usecase/`; не добавлять бизнес-логику; сохранить singleton-lifetime container
- Критерии приемки: instrumentation происходит до первой сборки middleware stack; `OpenTelemetryMiddleware` попадает в runtime stack без workaround; shutdown закрывает instrumentation и runtime один раз; compose-конфиг продолжает работать
### T12. Регрессионная проверка HTTP telemetry wiring
- Субагент: `test-engineer`
- Статус: pending
- Зависимости: `T11`
- Commit required: no
- Scope: добавить проверку, что раннее instrumentation wiring сохраняется и не требует ручного rebuild middleware stack
- Файлы: `test/*`
- Ограничения: без реального collector; проверять через ASGI/lifespan или локальные assertions по app runtime; не тянуть FastAPI и OTel в inner-layer тесты
- Критерии приемки: тест подтверждает, что при включенных metrics/tracing `OpenTelemetryMiddleware` присутствует в runtime stack; тест не зависит от внешнего OTel collector; существующие архитектурные границы не нарушены
- Критерии приемки: Docker остается только во внешнем adapter; FastAPI не протекает в `domain/` и `usecase/`; TTL и mount policy читаются как явные, тестируемые правила; замечания сформулированы как точечные правки или подтверждение готовности