15 KiB
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). Способ замера — предпочтительно stdlibshutil.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=Falsewatchdog не запускается (демон не стартует в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.