Files

12 KiB
Raw Permalink Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-099 analysis analyst ready-for-review 2026-06-10 claude-opus-4-8

01 — BRD (бизнес-требования): ORCH-099 — FND/F1a: лёгкий /metrics в орке (отдать сырьё)

Work Item: ORCH-099 · Repo: orchestrator · Стадия: analysis

1. Бизнес-контекст и проблема

Задача — фундаментный кирпич F1a домена 0 «Фундамент» эпика автономного саморазвития (docs/epics/self-evolution.md). Архитектурная рамка наблюдаемости зафиксирована заказчиком (Слава, 09.06) и для аналитика — установленный факт, не предмет переизобретения:

  • C-1/C-1б: наблюдатель ОТДЕЛЁН от наблюдаемого. Мониторинг живёт в отдельном sidecar-контейнере (watchdog/, рантайм — свой Dockerfile + сервис в compose), а НЕ внутри орка. Если орк упал/завис/съел память — sidecar жив и репортит это.
  • C-2/C-3: без внешнего плеча, тонкий стек (хост впритык: RAM 171Mi free, диск 92% — НЕ Grafana/Prometheus).
  • Разделение ответственности: орк отдаёт только сырьё (лёгкий read-only /metrics — свои внутренние данные, которые знает только он сам), БЕЗ логики мониторинга/порогов/алертов/хранения. Мозг (пороги, алерты, свой Telegram-канал, история) — это F1b (sidecar), отдельная задача.

Боль, которую закрывает задача. Сегодня у орка нет машинного «сырья» о самом себе в одной точке. /health отдаёт лишь {"status":"ok"}, /status — список активных задач, /queue — богатый, но «человеческий» снимок очереди, перемешанный с конфигом демонов. Ни один из них не даёт sidecar'у структурированный, стабильный КОНТРАКТ для детекта: застрявшая стадия, зависший агент (liveness по pid/CPU), деградация очереди (breaker open, рост failed), всплеск стоимости токенов. Без этого источника весь домен наблюдаемости (F1b и далее) слеп и не может стартовать.

Self-hosting контекст. Орк дорабатывает сам себя; прод-контейнер общий для всех проектов. /metrics обязан быть строго read-only и never-raise — он не должен ни при каких входных данных уронить или притормозить прод, обслуживающий enduro-trails.

2. Объём (scope)

В объёме

  • Новый read-only HTTP-эндпоинт (GET /metrics), отдающий JSON-снимок сырья о самом орке.
  • Четыре раздела сырья: активные стадии задач, очередь jobs, agent-liveness, стоимость/токены (agent_runs).
  • Новый leaf-модуль src/metrics.py — сборка снимка из БД (чистый, never-raise, без побочных эффектов), по образцу snapshot()-функций (serial_gate/task_deps/cancel).
  • Документирование формата /metrics как контракта для sidecar (F1b) в docs/architecture/README.md + запись в CHANGELOG.md.
  • Pytest-покрытие: структура ответа, never-raise, read-only-инвариант.

Вне объёма

  • Любая логика мониторинга: пороги, алерты, Telegram, оценка «застрял/завис», хранение истории — это F1b (sidecar).
  • Сам sidecar-контейнер (watchdog/, Dockerfile, compose-сервис) — отдельная задача F1b.
  • Хостовые/контейнерные/внешние метрики (диск/RAM/CPU хоста, docker.sock, пинг Plane/Gitea/Anthropic) — их собирает sidecar, не орк.
  • Изменение STAGE_TRANSITIONS / QG_CHECKS / check_* / схемы БД / любых machine-verdict ключей.
  • Дашборд/UI (упомянут в F1 эпика как отдельный последующий шаг).
  • Прометей-совместимый text-формат — отдаём JSON (контракт под конкретный sidecar; OpenMetrics не требование заказчика).

3. Заинтересованные стороны

  • Заказчик: Слава (рамки наблюдаемости F1, эпик саморазвития).
  • Прямой потребитель контракта: будущий sidecar F1b (watchdog/) — читает /metrics по HTTP. Задача F1b заблокирована этой (ORCH-099 — источник контракта).
  • Затрагивается: прод-инстанс орка (общий с enduro-trails) — поэтому жёсткое требование read-only/never-raise.
  • Принимает результат: reviewer/tester конвейера + Слава как владелец рамок.

4. Бизнес-требования (BR)

  • BR-1 — Эндпоинт сырья. Орк предоставляет HTTP GET /metrics, отдающий JSON с четырьмя разделами: (a) активные стадии задач, (b) очередь jobs, (c) agent-liveness, (d) стоимость/токены. Состав полей каждого раздела — см. TRZ §3 (FR-1…FR-4).
  • BR-2 — Стадии задач. По каждой незавершённой задаче отдаётся work_item, текущая stage и «как давно в стадии» (секунды) — сырьё для детекта застреваний sidecar'ом.
  • BR-3 — Очередь jobs. Отдаются счётчики по статусам (queued/running/failed/…), глубина очереди, информация о ретраях и состояние circuit-breaker'а — сырьё для детекта деградации.
  • BR-4 — Agent-liveness. По каждому running-job отдаётся agent, run_id, pid, runtime_s и сырьё для alive-детекта (CPU-тики pid либо данные, по которым sidecar посчитает CPU-дельту). sidecar — арбитр «жив/завис»; орк лишь поставляет факты.
  • BR-5 — Стоимость/токены. Отдаётся текущая (по running-job) и агрегированная стоимость/токены из agent_runs (cost_usd, input/output/cache_* токены) — сырьё для cost-наблюдаемости (D3).
  • BR-6 — Аддитивность. Существующие /health, /status, /queue остаются байт-в-байт прежними по контракту; /metrics добавляется рядом, ничего не ломая.
  • BR-7 — Документированный контракт. Формат /metrics зафиксирован в docs/architecture/README.md как стабильный контракт для sidecar (F1b) + CHANGELOG.md.

5. Нефункциональные требования (NFR)

  • NFR-1 — Read-only. Эндпоинт НИЧЕГО не мутирует: не пишет в БД, не запускает/останавливает процессы, не рестартит, не дёргает внешние API. Только SELECT'ы + чтение in-memory-снимков демонов.
  • NFR-2 — Never-raise (по полям). Любая ошибка при сборе отдельного поля/раздела → это поле получает null (или раздел — безопасный дефолт), но эндпоинт возвращает 200 и валидный JSON, никогда не 500. Эталон — serial_gate.snapshot() с fallback в except.
  • NFR-3 — Лёгкость. Только быстрые запросы к локальной SQLite + чтение уже посчитанных in-memory снапшотов; без тяжёлых вычислений, без сетевых вызовов, без сканирования файлов/git. Цель — единичные мс на типовом объёме (десятки задач/jobs).
  • NFR-4 — Self-hosting-безопасность. Эндпоинт физически не способен повлиять на прод-конвейер (следствие NFR-1) — безопасен на общем инстансе с enduro-trails.
  • NFR-5 — Совместимость БД/гейтов. STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict ключи / схема БД — НЕ трогаются. Задача читает существующие таблицы (tasks/jobs/agent_runs) и существующие in-memory снапшоты.
  • NFR-6 — Стабильность контракта. Формат — аддитивный и версионируемый (поле schema_version), чтобы будущие расширения не ломали уже написанный sidecar.

6. Допущения и ограничения

  • Данные уже есть в БД. Все нужные поля присутствуют: tasks(stage, work_item_id, updated_at, created_at), jobs(status, attempts, max_attempts, transient_attempts, available_at, pid, run_id), agent_runs(agent, started_at, finished_at, model, effort, cost_usd, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens). Новые колонки/таблицы не нужны.
  • Breaker-состояние — in-memory (queue_worker.worker.status() / CircuitBreaker.snapshot()); читается без БД.
  • CPU-тики pid читаются из /proc/<pid>/stat (Linux прод-контейнер). Допущение: контейнер Linux; при отсутствии/гонке (процесс уже умер) — поле null (NFR-2), НЕ ошибка. Это согласуется с рамкой C-1: «орк лёг → endpoint недоступен = сам сигнал тревоги» — детект делает sidecar.
  • Арбитраж liveness — на стороне sidecar. Орк не решает «завис/жив»; он лишь отдаёт pid, runtime_s и (по возможности) CPU-тики; sidecar считает дельту между опросами.
  • Формат — JSON, не OpenMetrics/Prometheus (рамка C-3: тонкий кастомный sidecar, не Prometheus).

7. Критерии успеха

GET /metrics отдаёт лёгкий, read-only, never-raise JSON с четырьмя разделами сырья; /health//status//queue не сломаны; формат задокументирован как контракт sidecar; pytest зелёный. Детальные PASS/FAIL — 03-acceptance-criteria.md.

8. Риски

  • Гонка чтения /proc/<pid>/stat (процесс умер между выборкой job и чтением proc) → закрывается NFR-2 (null, не ошибка).
  • Расхождение контракта /metrics и ожиданий sidecar (F1b) → закрывается BR-7 (контракт в одном репо, документирован) + schema_version (NFR-6).
  • Соблазн «протащить» в /metrics логику алертинга → закрывается scope-границей (вне объёма) и NFR-1.

Детальная оценка технических рисков — 10-tech-risks.md (заполняет архитектор).