Files
orchestrator/docs/work-items/ORCH-063/02-trz.md

15 KiB
Raw Blame History

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

02 — ТЗ (TRZ): ORCH-063 — INFRA: мониторинг диска mva154 + алерт при >85%

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

ТЗ описывает что и где должно измениться (модули/контракты/артефакты), выведенное из BRD и фактического кода. Как (точная структура демона, способ замера, хранение состояния анти-спама, точки врезки) — решает архитектор в 06-adr/. ТЗ фиксирует требования и границы.


1. Сводка изменения

Ввести disk-watchdog — фоновый daemon-поток (по образцу reconciler/job_reaper), который периодически (heartbeat) измеряет заполнение хост-файловой системы через смонтированные в контейнер bind-пути и при пересечении настраиваемого порога (дефолт 85%) шлёт Telegram-алерт оператору. Анти-спам (алерт на пересечение + ограниченное повторение + recovery при возврате ниже порога), наблюдаемость в GET /queue, kill-switch, never-raise. Машина стадий, реестр QG и схема БД-контрактов не меняются; новой миграции не требуется.


2. Задействованные модули / пути

Путь Действие
src/disk_watchdog.py (новый leaf-модуль; имя — на усмотрение архитектора) создать — чистая логика замера + решение об алерте (pure, тестируемо) + daemon-обёртка (threading.Thread(daemon=True) + threading.Event, start/stop/status), never-raise. Образец: src/reconciler.py, src/job_reaper.py.
src/config.py изменить — добавить флаги фичи (см. §8).
src/main.py изменитьstart()/stop() watchdog в lifespan (после reaper.start() / в reverse-порядке на shutdown); добавить read-only блок disk_monitor в GET /queue.
src/notifications.py изменить (опц.) — переиспользовать send_telegram(text) (notifying) напрямую из watchdog или добавить тонкий helper notify_disk_alert(...)/notify_disk_recovery(...) (never-raise). Выбор — архитектор.
.env.example изменить — задокументировать новые ORCH_DISK_* переменные (дескрипторы, без значений-секретов).

Чистую логику (замер по путям, дедуп по устройству, решение «алертить / повторить / recovery» как функция от текущего %, порога и предыдущего состояния) держать в leaf-модуле, never-raise, по образцу src/task_deps.py / src/post_deploy.py — для юнит-тестируемости без фонового потока.


3. Функциональные требования

FR-1 — Heartbeat-демон (BR-1, BR-8)

  • Фоновый daemon-поток измеряет заполнение диска каждые disk_monitor_interval_s секунд.
  • Стартует/останавливается в main.lifespan (паттерн reconciler.start()/reaper.start() и reverse на shutdown). Период — threading.Event().wait(interval) (чистый stop, как reconciler._run).
  • Контракт демона: start(), stop(timeout), status() -> dict (для /queue).

FR-2 — Замер заполнения хост-ФС (BR-1, NFR-3)

  • Для каждого пути из disk_monitor_paths измерить заполнение (used/total, %), свободно (байты/%).
  • Источник — смонтированные хост-пути, а не overlay / контейнера (NFR-3): дефолтный набор путей должен покрывать раздел(ы), на которых растут рабочие данные оркестратора — /repos (host /home/slin/repos) и /app/data (host ./data). Способ замера — предпочтительно stdlib shutil.disk_usage(path) (без субпроцесса df на каждом тике, NFR-2); финальный выбор — архитектор.
  • При совпадении физического устройства у нескольких путей — желательно не дублировать алерт (дедуп по устройству st_dev/mount); требование «желательно», не блокирующее.
  • Недоступный/несуществующий путь → пропуск этого пути с лог-warning, без падения тика.

FR-3 — Алерт при превышении порога (BR-2)

  • Если заполнение пути disk_monitor_threshold_pct (дефолт 85) — сформировать и отправить Telegram-алерт через send_telegram (notifying, не silent — это alert, как notify_error).
  • Содержимое алерта (действенное): идентификатор хоста/пути (точка монтирования), занято %, свободно (ГБ и/или %), порог. Текст — на русском, по стилю существующих notify_*-алертов.

FR-4 — Анти-спам, повтор и recovery (BR-3, BR-4)

  • Решение об отправке — функция от (current_pct, threshold, previous_state, now):
    • переход «ниже→на/выше порога» → отправить алерт (первое пересечение);
    • остаётся выше порога → повторно слать не чаще, чем раз в disk_monitor_realert_s (cooldown), а не на каждом тике;
    • переход «выше→ниже порога» → сбросить состояние алерта и отправить однократное recovery-сообщение «диск ниже порога» (notifying).
  • Состояние анти-спама может быть in-memory (best-effort; после рестарта допустим повторный алерт, если всё ещё выше порога — безопасно, NFR-5). Время — через инъецируемый now-провайдер, чтобы решение было тестируемо без реального таймера.

FR-5 — Конфигурируемость и kill-switch (BR-5, NFR-4)

  • Поведение управляется флагами config.py (см. §8). При disk_monitor_enabled=False watchdog не запускается (демон не стартует в lifespan) — нулевая регрессия.

FR-6 — Наблюдаемость (BR-7)

  • GET /queue получает аддитивный read-only блок disk_monitor (по образцу блоков reconcile/ reaper/serial_gate): enabled, threshold_pct, interval_s, paths с последним замером (used_pct, free_bytes/free_gb), alerting (bool на путь/глобально), last_alert_at. never-raise: при ошибке — минимальный словарь с флагами.

4. Изменения API

  • Новых обязательных endpoint'ов нет. Снимок состояния отдаётся через существующий GET /queue (аддитивный блок disk_monitor, §3/FR-6); существующие ключи ответа не меняются.
  • Опционально (на усмотрение архитектора, не обязательно): отдельный GET /disk для on-demand замера. Если вводится — задокументировать в README. Рекомендация: ограничиться блоком в /queue.

5. Изменения схемы БД

Нет. Состояние watchdog — best-effort, держится в памяти демона (NFR-5). Новых таблиц/колонок/ миграций не вводится. STAGE_TRANSITIONS/QG_CHECKS/tasks/jobs/agent_runs — без изменений.

Если архитектор решит сделать состояние last-alert durable (переживающим рестарт) — допустима только аддитивная, идемпотентная миграция (CREATE TABLE IF NOT EXISTS), но это не требование ТЗ (по умолчанию — in-memory).


6. Требования к новым/изменённым QG checks

Нет. Watchdog — фоновый эксплуатационный демон, не Quality Gate стадии. Реестр QG_CHECKS и check_* не трогаются (аналогично reconciler/job_reaper, которые тоже не являются QG).


7. Совместимость / регресс

  • Аддитивно: новый leaf-модуль + точечные врезки в main.lifespan и GET /queue + флаги config. Существующий код не переписывается.
  • Kill-switch disk_monitor_enabled (дефолт True): False → демон не стартует, /queue-блок отдаёт {"enabled": false} — поведение приложения 1:1 как сейчас (NFR-4).
  • never-raise: изоляция фонового потока (паттерн reconciler/reaper); сбой замера/отправки/ тика не влияет на конвейер (BR-6/NFR-1). Демон бежит в общем self-hosting-инстансе — обязан быть безопасным для enduro-trails.
  • Обратимость: удаление эффекта = выключение флага; миграций БД нет, откат тривиален.
  • Self-hosting: watchdog только читает заполнение и шлёт уведомление — не трогает диск/контейнер, не рестартит прод (NFR-6).

8. Конфигурация (src/config.py)

По образцу reconcile_* / merge_gate_*:

Поле (env) Тип / дефолт Назначение
disk_monitor_enabled (ORCH_DISK_MONITOR_ENABLED) bool = True kill-switch; False → демон не стартует (нулевая регрессия).
disk_monitor_interval_s (ORCH_DISK_MONITOR_INTERVAL_S) int = 300 период heartbeat-замера, сек (порядок минут, NFR-2).
disk_monitor_threshold_pct (ORCH_DISK_MONITOR_THRESHOLD_PCT) int = 85 порог заполнения для алерта (дефолт фиксирован Владельцем).
disk_monitor_realert_s (ORCH_DISK_MONITOR_REALERT_S) int = 21600 минимальный интервал между повторными алертами, пока выше порога (анти-спам; ~6 ч).
disk_monitor_paths (ORCH_DISK_MONITOR_PATHS) str = "/repos,/app/data" (CSV) отслеживаемые пути (смонтированные хост-разделы, NFR-3); пусто → дефолтный набор.

Финальный набор/имена флагов и дефолты уточняет архитектор; диапазон/валидация значений (порог в 1..100, интервалы > 0) — defensive, невалидное → дефолт + лог-warning (паттерн reconcile_grace_*).


9. Артефакты pipeline (создать/обновить в ТОМ ЖЕ PR)

Документация — golden source (CLAUDE.md §2). По итогам разработки обновить:

  • docs/work-items/ORCH-063/06-adr/ADR-001-disk-watchdog.md — решение (способ замера хост-ФС, набор путей/дедуп, хранение состояния анти-спама, точки врезки, дефолты порога/периода).
  • docs/architecture/README.md — новый компонент «Disk-watchdog (ORCH-063)» в списке компонентов + описание блока disk_monitor в GET /queue.
  • docs/operations/INFRA.md — раздел/строки про disk-watchdog: что мониторится, порог, как отключить (ORCH_DISK_MONITOR_ENABLED), что делать при алерте (ручная очистка — ссылка/руководство).
  • .env.example — новые ORCH_DISK_* дескрипторы.
  • CHANGELOG.md — запись feat:.
  • При новом endpoint /disk (если архитектор введёт) — обновить таблицу API в README.

10. Инварианты (не нарушать)

  • STAGE_TRANSITIONS, реестр QG_CHECKS, check_*, схема существующих таблиц БД — без изменений.
  • never-raise на тик демона; сбой watchdog не блокирует и не роняет конвейер (NFR-1).
  • Замер — по хост-разделам (bind-mount-пути), не по overlay / контейнера (NFR-3).
  • Не рестартить/не ронять прод-контейнер; watchdog только читает и уведомляет (NFR-6, self-hosting).
  • При выключенном флаге — поведение 1:1 как сейчас; enduro-trails не затрагивается.

11. Открытые вопросы для архитектора (не блокируют анализ)

  • OQ-1: Способ замера — stdlib shutil.disk_usage(path) vs субпроцесс df (рекомендация — stdlib, NFR-2).
  • OQ-2: Дедуп путей по физическому устройству (os.stat().st_dev), чтобы единый host-раздел не алертил дважды.
  • OQ-3: Состояние анти-спама — in-memory (рекомендация) vs durable (доп. таблица); влияет на поведение после рестарта.
  • OQ-4: Нужен ли второй «критический» порог (напр. 95%) с усиленным/более частым алертом — кандидат, по умолчанию нет (один порог 85%).
  • OQ-5: Helper в notifications.py (notify_disk_alert) vs прямой вызов send_telegram из watchdog.