analyst(ET): auto-commit from analyst run_id=661
All checks were successful
CI / test (push) Successful in 3m53s
All checks were successful
CI / test (push) Successful in 3m53s
This commit is contained in:
167
docs/work-items/ORCH-109/01-brd.md
Normal file
167
docs/work-items/ORCH-109/01-brd.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
work_item: ORCH-109
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-14
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 01 — BRD (бизнес-требования): ORCH-109 — timeout budgets + launch-time model telemetry для developer/reviewer
|
||||
|
||||
Work Item: **ORCH-109** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
## 1. Бизнес-контекст и проблема
|
||||
|
||||
Инцидент **ORCH-104** (runs 658/659/660, прод-watchdog 1800s) вскрыл **два независимых дефекта**
|
||||
в подсистеме запуска агентов и телеметрии:
|
||||
|
||||
**Дефект A — недостаточный wall-clock бюджет для тяжёлых ролей.**
|
||||
Агенты `developer` и `reviewer` на сложных задачах **честно** упираются в общий тайм-аут
|
||||
`agent_timeout_seconds = 1800` и убиваются watchdog'ом (`launcher._watchdog → stop_process`,
|
||||
exit 143 / -9). Этот тайм-аут — единый для ВСЕХ ролей, хотя `developer` (effort `xhigh`,
|
||||
кодирующая роль) и `reviewer` объективно требуют больше времени, чем механические роли
|
||||
(`tester`/`deployer`, effort `medium`). Существует механизм per-agent override
|
||||
(`_resolve_timeout` + `agent_timeout_overrides_json`), но в проде он пуст → все роли получают 1800s.
|
||||
|
||||
**Дефект B — потеря модели в телеметрии при оборванном прогоне.**
|
||||
Модель агента (`agent_runs.model`) пишется **только постфактум** — из финального usage-JSON
|
||||
прогона в `launcher._monitor_agent → usage.record_usage` (`_extract_model`). Убитый по тайм-ауту
|
||||
прогон **не успевает эмитить финальный JSON** → `_extract_model` возвращает `None` →
|
||||
`record_usage` пишет `model=COALESCE(None, model)` = остаётся **NULL**. В результате карточка
|
||||
Telegram-трекера (`notifications._stage_line`) и снимок `GET /metrics`/`GET /queue`
|
||||
(`db.get_running_agents`) показывают `model=null` именно тогда, когда что-то пошло не так — в
|
||||
момент, когда модель/эффорт критичны для разбора инцидента.
|
||||
|
||||
Существующий прецедент уже решает половину задачи: **эффорт стампится в момент launch**
|
||||
(`launcher._spawn`, ORCH-087, `UPDATE agent_runs SET effort=?`), потому что CLI его в result-JSON
|
||||
не возвращает. Модель резолвится в той же точке (`resolve_agent_model`, строка 559), но **в БД на
|
||||
launch не пишется** — стампится только эффорт. ORCH-109 распространяет ту же гарантию на модель.
|
||||
|
||||
**Сопутствующие проверки (производные от A и B):**
|
||||
- Поведение оборванного (timeout-killed) прогона в трекере и status-комментариях: модель и эффорт
|
||||
должны быть видны даже если финальный JSON не записан.
|
||||
- Нужен ли отдельный guard: не пускать timeout-killed `developer`/`reviewer` автоматически дальше
|
||||
по конвейеру (`development → review`, `review → testing`) без явного salvage-режима.
|
||||
|
||||
**Установленные факты (по коду, не изобретать):**
|
||||
- `agent_runs.model` — колонка `TEXT` (NULLABLE), уже существует (`db._ensure_column`); **миграция
|
||||
не нужна**.
|
||||
- `record_usage` уже использует `model=COALESCE(?, model)` — то есть постфактум-парс уже
|
||||
**сохраняет** ранее проставленное значение и не затирает его `NULL`'ом. Не хватает только
|
||||
записи на launch.
|
||||
- `_resolve_timeout(agent)` уже умеет per-agent override через `agent_timeout_overrides_json`;
|
||||
малформный JSON → откат на глобальный дефолт + лог (never-break).
|
||||
- Кросс-инвариант reaper: `reaper_max_running_s = 3600` с зафиксированным в `config.py` правилом
|
||||
«MUST be > max agent_timeout + grace» (Tier-3 backstop job-reaper'а, ORCH-065).
|
||||
|
||||
## 2. Объём (scope)
|
||||
|
||||
### В объёме
|
||||
- **Launch-time стамп модели:** записывать резолвенную `resolve_agent_model(...)` в
|
||||
`agent_runs.model` в момент launch (`launcher._spawn`), рядом со стампом эффорта (ORCH-087).
|
||||
- **Конфигурируемый поднятый wall-clock бюджет для `developer` и `reviewer`** через config-override,
|
||||
**без изменения** бюджета остальных ролей (`analyst`/`architect`/`tester`/`deployer`).
|
||||
- **Сохранение постфактум-enrich:** `usage.record_usage` остаётся источником обогащения
|
||||
модели/токенов/стоимости из usage-JSON, но **перестаёт быть единственным источником истины** о
|
||||
модели (launch-стамп — первичный, JSON — уточняющий).
|
||||
- **Видимость при timeout/kill:** строка стадии трекера и status-комментарии показывают реальные
|
||||
модель + эффорт для оборванного прогона (model не `null`).
|
||||
- **Guard анти-salvage:** гарантия (и регресс-тест), что timeout-killed прогон
|
||||
(`exit_code != 0`, в т.ч. -9/-15/143) **не продвигает** стадию автоматически в следующую без
|
||||
явного решения.
|
||||
- **Обновление документации/комментариев** по конфигу тайм-аутов (`config.py`, `.env.example`).
|
||||
- **Тесты**, покрывающие все перечисленные FR.
|
||||
|
||||
### Вне объёма
|
||||
- Изменение model-routing: все 6 агентов остаются на `claude-opus-4-8` (ORCH-41 G3 не включается).
|
||||
- Любые изменения `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключей / схемы БД
|
||||
(колонка `agent_runs.model` уже есть — миграции нет).
|
||||
- Изменение тайм-аута для ролей кроме `developer`/`reviewer`.
|
||||
- **Salvage / возобновление** недоделанной работы убитого прогона (поднять «как было», дописать,
|
||||
переиспользовать частичный результат) — в объёме ТОЛЬКО гарантия не-продвижения, не salvage.
|
||||
- Изменения транспорта Telegram/Plane (`send_telegram`/комментарии) — только использование уже
|
||||
доступных полей.
|
||||
- Перезапуск/деплой прод-контейнера в рамках задачи (self-hosting безопасность).
|
||||
|
||||
## 3. Заинтересованные стороны
|
||||
|
||||
- **Заказчик/Owner (Слава)** — инициатор; нуждается в надёжной телеметрии для разбора инцидентов и
|
||||
в адекватных бюджетах тяжёлых ролей при пакетном автономном прогоне (эпик ORCH-088).
|
||||
- **Оператор self-hosting** — потребитель карточки трекера и `GET /metrics`/`GET /queue`; без модели
|
||||
в карточке теряет ключевой контекст инцидента.
|
||||
- **Сам конвейер (self-hosting)** — затрагивается поведение запуска агентов; общий прод-инстанс
|
||||
обслуживает и enduro-trails (тайм-аут — глобальная per-agent настройка, не repo-scoped).
|
||||
|
||||
## 4. Бизнес-требования (BR)
|
||||
|
||||
- **BR-1** — Резолвенная модель агента сохраняется в `agent_runs.model` **в момент launch**, рядом
|
||||
с эффортом, а не только постфактум из usage-JSON. Значение присутствует на строке прогона с
|
||||
момента запуска и переживает любой исход прогона.
|
||||
- **BR-2** — Постфактум-парс usage/model (`usage.record_usage`) сохраняется как **обогащение**, но
|
||||
**не как единственный источник истины**: при отсутствии/обрыве финального JSON launch-стамп модели
|
||||
не теряется.
|
||||
- **BR-3** — Wall-clock тайм-аут для `developer` и `reviewer` поднимается и **настраивается через
|
||||
config-override**, **без изменения** тайм-аута остальных ролей; механизм покрыт тестом/проверкой.
|
||||
- **BR-4** — При timeout/kill (оборванный прогон без финального JSON) строка стадии в трекере и
|
||||
status-комментарии показывают **реальную модель (не `null`) и эффорт**.
|
||||
- **BR-5** — Timeout-killed прогон `developer`/`reviewer` **не продвигается** автоматически на
|
||||
следующую стадию без явного salvage-режима; поведение зафиксировано регресс-тестом. (Анализ
|
||||
определяет, нужен ли отдельный guard поверх существующей гарантии «advance только при чистом
|
||||
exit + зелёный QG».)
|
||||
- **BR-6** — Документация и комментарии по конфигу тайм-аутов обновлены (паспорт изменения внутри
|
||||
`config.py` + `.env.example`).
|
||||
|
||||
## 5. Нефункциональные требования (NFR)
|
||||
|
||||
- **NFR-1 — Обратная совместимость / нулевая регрессия.** Стамп модели аддитивен (колонка уже
|
||||
существует, миграции нет). Дефолтный тайм-аут ролей, кроме `developer`/`reviewer`, не меняется;
|
||||
при пустом override-конфиге поведение байт-в-байт прежнее.
|
||||
- **NFR-2 — never-raise / never-break.** Сбой стампа модели (ошибка БД) **не блокирует** launch
|
||||
(та же `try/except`-изоляция, что у стампа эффорта). Малформный/невалидный timeout-конфиг →
|
||||
откат на глобальный дефолт + WARNING, прогон не падает.
|
||||
- **NFR-3 — Неприкосновенность контрактов.** `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`,
|
||||
machine-verdict ключи (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`/
|
||||
`coverage_status:`), схема БД — **не трогаются**.
|
||||
- **NFR-4 — Сохранение reaper-инварианта.** Любой поднятый бюджет `developer`/`reviewer` обязан
|
||||
сохранять `reaper_max_running_s > max(резолвенный тайм-аут любого агента) + agent_kill_grace_seconds`
|
||||
(Tier-3 backstop ORCH-065); иначе job-reaper может реапнуть **здоровый** долгоиграющий прогон до
|
||||
срабатывания его собственного watchdog'а. Если новый бюджет нарушает неравенство —
|
||||
`reaper_max_running_s` поднимается синхронно (решение архитектора).
|
||||
- **NFR-5 — Self-hosting безопасность.** Изменение не рестартит/не роняет прод-контейнер, не
|
||||
трогает deploy-путь, безопасно для общего инстанса (enduro-trails не затронут негативно).
|
||||
- **NFR-6 — Наблюдаемость in-flight.** Модель становится видна в `GET /metrics`/`GET /queue`
|
||||
(`db.get_running_agents`) **во время** прогона, а не только после завершения (побочное улучшение
|
||||
launch-стампа).
|
||||
|
||||
## 6. Допущения и ограничения
|
||||
|
||||
- Тайм-аут — **глобальная per-agent** настройка (не repo-scoped): поднятие бюджета
|
||||
`developer`/`reviewer` действует на все репо. Для enduro это благоприятно/нейтрально.
|
||||
- Колонка `agent_runs.model` уже существует и NULLABLE — повторная запись/COALESCE безопасны.
|
||||
- CLI не возвращает effort в result-JSON (причина launch-стампа эффорта ORCH-087); модель в JSON
|
||||
возвращается, но только при успешном финале — отсюда необходимость launch-стампа модели.
|
||||
- Точные числовые значения новых бюджетов (`developer`/`reviewer`) и способ их конфигурации
|
||||
(выделенные ключи vs `agent_timeout_overrides_json`) — решение архитектора/Owner в рамках FR-3;
|
||||
BRD фиксирует только **способность + инвариант NFR-4 + тест**.
|
||||
- Salvage недоделанной работы — отдельная возможность, вне этой задачи.
|
||||
|
||||
## 7. Критерии успеха
|
||||
|
||||
Модель агента видна (не `null`) в трекере, status-комментариях и `/metrics` для ЛЮБОГО исхода
|
||||
прогона, включая timeout-kill; бюджеты `developer`/`reviewer` подняты и конфигурируемы без влияния
|
||||
на прочие роли и без нарушения reaper-инварианта; timeout-killed прогон не «протекает» в следующую
|
||||
стадию; всё покрыто тестами; конфиг задокументирован. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||||
|
||||
## 8. Риски
|
||||
|
||||
- **R-1** — Поднятие бюджета выше `reaper_max_running_s − grace` → ложный reap здорового прогона
|
||||
(NFR-4). Митигируется sanity-тестом конфига и/или синхронным поднятием `reaper_max_running_s`.
|
||||
- **R-2** — Постфактум-enrich затирает корректный launch-стамп при странном JSON. Митигируется
|
||||
семантикой COALESCE (NULL не затирает) + тестом enrich-кейсов.
|
||||
- **R-3** — Гонка двух писателей `exit_code` (`_record_kill` = -9 и `_monitor_agent` = `proc.wait()`)
|
||||
не должна влиять на телеметрию модели (модель — отдельная колонка). Подтверждается тестом FR-4.
|
||||
- **R-4** — Глобальность тайм-аута: поднятие для enduro-developer могло бы маскировать зависший
|
||||
прогон. Митигируется тем, что Tier-3 backstop reaper'а сохраняется (NFR-4).
|
||||
|
||||
Детали рисков и архитектурные трейд-оффы — `10-tech-risks.md` (заполняет архитектор).
|
||||
145
docs/work-items/ORCH-109/02-trz.md
Normal file
145
docs/work-items/ORCH-109/02-trz.md
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
work_item: ORCH-109
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-14
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 02 — ТЗ (TRZ): ORCH-109 — timeout budgets + launch-time model telemetry для developer/reviewer
|
||||
|
||||
Work Item: **ORCH-109** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD и фактического кода.
|
||||
> Архитектурное обоснование/решения (выбор «выделенные config-ключи vs `agent_timeout_overrides_json`»,
|
||||
> точные числовые бюджеты, синхронная правка `reaper_max_running_s`) — задача архитектора (`06-adr`).
|
||||
|
||||
## 1. Сводка изменения
|
||||
|
||||
Две независимые, но связанные правки в подсистеме запуска агентов:
|
||||
|
||||
1. **Launch-time стамп модели.** В `launcher._spawn` резолвенная `resolve_agent_model(...)` (уже
|
||||
вычисляется на launch, строка ~559) записывается в `agent_runs.model` в той же DB-сессии, что и
|
||||
стамп эффорта (ORCH-087, строки ~566–571). Постфактум-парс (`usage.record_usage`,
|
||||
`model=COALESCE(?, model)`) сохраняется как **обогащение** и уже не затирает launch-значение
|
||||
`NULL`'ом. Следствие: модель присутствует на строке прогона с момента запуска, переживает
|
||||
timeout-kill и видна in-flight в `GET /metrics`/`GET /queue`.
|
||||
|
||||
2. **Конфигурируемый поднятый wall-clock бюджет для `developer`/`reviewer`.** `_resolve_timeout(agent)`
|
||||
должен возвращать поднятый бюджет для `developer` и `reviewer`, конфигурируемый и не затрагивающий
|
||||
прочие роли; механизм покрыт тестом. Сохраняется never-break (малформный конфиг → глобальный
|
||||
дефолт) и кросс-инвариант reaper (`reaper_max_running_s > max(timeout)+grace`).
|
||||
|
||||
Плюс верификационные требования: телеметрия timeout-killed прогона (модель+эффорт не `null`) и
|
||||
guard анти-salvage (timeout-killed прогон не продвигает стадию).
|
||||
|
||||
## 2. Задействованные модули / пути
|
||||
|
||||
| Путь | Действие |
|
||||
|------|----------|
|
||||
| `src/agents/launcher.py` | изменить — стамп `model` в `_spawn` рядом с `effort` (≈ стр. 559–573); проверка `_resolve_timeout` обслуживает override `developer`/`reviewer` (≈ стр. 661–679) |
|
||||
| `src/config.py` | изменить — config для поднятого тайм-аута `developer`/`reviewer` (выделенные ключи и/или дефолт `agent_timeout_overrides_json`); обновить комментарии-паспорт (≈ стр. 115–126); проверить/при необходимости поднять `reaper_max_running_s` (≈ стр. 494–499) |
|
||||
| `src/usage.py` | проверить/зафиксировать тестом — `record_usage` (`model=COALESCE(?, model)`) НЕ затирает launch-стамп при `model=None` (≈ стр. 207–230); `_extract_model` (≈ стр. 95–118) |
|
||||
| `src/notifications.py` | проверить (правка, вероятно, не нужна) — `_stage_line` рендерит `· {model} · {effort}` из `agent_runs` для строки с `exit_code=-9` (≈ стр. 360–373, 498–542) |
|
||||
| `src/db.py` | НЕ менять схему — `agent_runs.model` TEXT уже есть; проверить, что `get_running_agents` (≈ стр. 1370–1405) отдаёт launch-стампнутую модель для running-job |
|
||||
| `src/stage_engine.py` | проверить — путь продвижения стадии не advance'ит прогон с `exit_code != 0` (guard FR-5); правка только если найден разрыв |
|
||||
| `.env.example` | обновить — задокументировать ключи тайм-аута `developer`/`reviewer` (BR-6) |
|
||||
| `tests/test_orch109_timeout_model.py` (новый) | создать — покрытие FR-1…FR-5 |
|
||||
| `CHANGELOG.md`, `CLAUDE.md` (паспорт), `docs/architecture/README.md` (модель/эффорт-секция) | обновить в том же PR (правило агентов №2) |
|
||||
|
||||
## 3. Функциональные требования
|
||||
|
||||
### FR-1 — Launch-time стамп модели (BR-1)
|
||||
В `launcher._spawn`, после `model = resolve_agent_model(agent, project_id)`, резолвенное значение
|
||||
записывается в `agent_runs.model` для текущего `run_id` **в момент launch**, по образцу стампа
|
||||
эффорта (ORCH-087):
|
||||
- Запись в той же открытой `conn`, что и стамп эффорта (допустимо объединить в один
|
||||
`UPDATE agent_runs SET model=?, effort=? WHERE id=?` — решение реализации).
|
||||
- Пустой резолв (`model == ""`, CLI-дефолт без `--model`) → пишется `NULL` (как эффорт: `effort or None`),
|
||||
чтобы суффикс модели в трекере корректно опускался.
|
||||
- **Инвариант:** значение `agent_runs.model` присутствует с момента launch и не зависит от исхода
|
||||
прогона.
|
||||
- **never-raise (NFR-2):** сбой записи изолирован `try/except` + WARNING; launch продолжается.
|
||||
|
||||
### FR-2 — Постфактум-enrich сохраняет launch-стамп (BR-2)
|
||||
`usage.record_usage` остаётся источником обогащения (токены/стоимость/модель из usage-JSON), но:
|
||||
- При `usage is None` или `usage.get("model") is None` (оборванный/малформный JSON) launch-стамп
|
||||
модели **не затирается** (текущая семантика `model=COALESCE(?, model)` это уже обеспечивает —
|
||||
требование зафиксировать тестом, не регрессировать).
|
||||
- При наличии непустой модели в JSON enrich **уточняет** значение (например, полный
|
||||
provider-prefixed id или фактический fallback-model) — допустимая перезапись непустым на непустое.
|
||||
- Семантика парсинга `_extract_model` (приоритет `modelUsage` → top-level `model`) — без изменений.
|
||||
|
||||
### FR-3 — Конфигурируемый поднятый тайм-аут `developer`/`reviewer` (BR-3)
|
||||
- `_resolve_timeout(agent)` возвращает поднятый бюджет для `agent in {"developer","reviewer"}`,
|
||||
конфигурируемый, **детерминированный**, и **не затрагивающий** прочие роли (они продолжают
|
||||
получать глобальный `agent_timeout_seconds`, если для них нет override).
|
||||
- Механизм: либо документированный дефолт `agent_timeout_overrides_json`, либо выделенные ключи
|
||||
(например `agent_timeout_developer_s`/`agent_timeout_reviewer_s`) — выбор архитектора; контракт
|
||||
FR-3 — резолв per-agent поднятого бюджета.
|
||||
- **never-break (NFR-2):** малформный/невалидный конфиг → откат на глобальный дефолт + WARNING
|
||||
(поведение `_resolve_timeout` сохраняется).
|
||||
- **Кросс-инвариант (NFR-4):** итоговый `max(резолвенный тайм-аут)` + `agent_kill_grace_seconds`
|
||||
обязан оставаться `< reaper_max_running_s`; при нарушении — синхронно поднять `reaper_max_running_s`.
|
||||
|
||||
### FR-4 — Телеметрия timeout-killed прогона (BR-4)
|
||||
Для прогона с `exit_code != 0` без финального usage-JSON (timeout-kill, `_record_kill` стампит -9):
|
||||
- Строка стадии трекера (`notifications._stage_line`) рендерит `· {short_model} · {effort}` с
|
||||
реальными значениями (модель **не** `null`), т.к. оба стампнуты на launch (FR-1 + ORCH-087).
|
||||
- `db.get_running_agents` (источник `GET /metrics`/`GET /queue`) отдаёт launch-стампнутую модель и
|
||||
для **running**-job (in-flight видимость, NFR-6).
|
||||
- Изменения `notifications.py`, вероятно, не требуются (рендер уже читает `model`); требование —
|
||||
верифицировать тестом, что при стампе на launch значение долетает.
|
||||
|
||||
### FR-5 — Guard анти-salvage timeout-killed прогона (BR-5)
|
||||
- Timeout-killed прогон (`exit_code != 0`, в т.ч. -9/-15/143) `developer`/`reviewer` **не продвигает**
|
||||
стадию (`development → review`, `review → testing`) автоматически.
|
||||
- Существующий контракт (advance только при чистом exit-коде + зелёный exit-гейт; иначе
|
||||
`attempts<max → queued`, иначе `failed` + Telegram — `launcher._monitor_agent`/`queue_worker`/
|
||||
`job_reaper`) реализует это структурно.
|
||||
- **Требование:** анализ подтверждает достаточность существующей гарантии; поведение фиксируется
|
||||
**регресс-тестом**. Отдельный guard в коде добавляется **только если тест выявит разрыв**.
|
||||
- **salvage-режим НЕ вводится** (вне объёма) — задача гарантирует не-продвижение, не возобновление.
|
||||
|
||||
### FR-6 — Документация конфига (BR-6)
|
||||
- Комментарий-паспорт в `config.py` (блок ORCH-7, строки ~115–126) расширяется описанием поднятых
|
||||
бюджетов `developer`/`reviewer` и ссылкой на reaper-инвариант (NFR-4).
|
||||
- `.env.example` несёт соответствующие ключи с дефолтами = боевым значениям (канон ORCH-101).
|
||||
- Сквозная документация (`CLAUDE.md`, `docs/architecture/README.md` — таблица «модель/эффорт по
|
||||
ролям») обновляется в том же PR.
|
||||
|
||||
## 4. Изменения API
|
||||
|
||||
Нет. Ни одного нового/изменённого endpoint'а. `GET /metrics` и `GET /queue` отдают тот же контракт
|
||||
(`schema_version: 1`) — поле `agents[].model` лишь **начинает заполняться** для running-job
|
||||
(аддитивное улучшение данных, не контракта; sidecar обязан толерировать, ORCH-099 NFR-6).
|
||||
|
||||
## 5. Изменения схемы БД
|
||||
|
||||
Нет. Колонка `agent_runs.model` (`TEXT`, NULLABLE) уже существует (`db._ensure_column`, инициализация
|
||||
`init_db`). Никаких `CREATE`/`ALTER`/новых таблиц. Меняется только **момент** и **частота** записи в
|
||||
существующую колонку (launch + опциональный постфактум-enrich).
|
||||
|
||||
## 6. Требования к новым/изменённым QG checks
|
||||
|
||||
Нет. `QG_CHECKS` / `check_*` / `_parse_*` / machine-verdict ключи — не трогаются. Задача целиком вне
|
||||
слоя Quality Gate (подсистема launch/телеметрия/конфиг). FR-5 опирается на **существующий**
|
||||
exit-code-контракт продвижения, не на новый гейт.
|
||||
|
||||
## 7. Совместимость / регресс
|
||||
|
||||
- **Обратная совместимость:** стамп модели аддитивен; при пустом timeout-override поведение
|
||||
байт-в-байт прежнее (NFR-1). Никаких kill-switch не требуется — изменение не вводит новых ветвей
|
||||
риска (стамп модели всегда безопасен; тайм-аут конфигурируем и fail-safe на глобальный дефолт).
|
||||
- **Область раската:** стамп модели — все репо/роли (безопасно). Поднятый тайм-аут — только
|
||||
`developer`/`reviewer` (все репо, т.к. тайм-аут глобален per-agent); прочие роли неизменны.
|
||||
- **Обратимость:** вернуть тайм-аут — снять override-конфиг (откат на 1800s). Launch-стамп модели
|
||||
отката не требует (чистое улучшение телеметрии).
|
||||
- **Кросс-каттинг (NFR-4):** при поднятии бюджета выше `reaper_max_running_s − grace` синхронно
|
||||
поднять `reaper_max_running_s` (ORCH-065). Sanity-тест конфига стережёт инвариант.
|
||||
- **never-raise (NFR-2):** обе правки изолированы; сбой не роняет launch и не падает на старте при
|
||||
плохом env.
|
||||
- **Self-hosting (NFR-5):** ни рестарта прода, ни изменения deploy-пути; общий инстанс безопасен.
|
||||
- **Полный регресс `tests/`** остаётся зелёным; coverage-гейт (ORCH-027) удовлетворён новым
|
||||
тест-файлом (изменения в `src/` минимальны и покрыты).
|
||||
139
docs/work-items/ORCH-109/03-acceptance-criteria.md
Normal file
139
docs/work-items/ORCH-109/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
work_item: ORCH-109
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-14
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-109 — timeout budgets + launch-time model telemetry
|
||||
|
||||
Work Item: **ORCH-109** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL**
|
||||
(что считается провалом). Reviewer/tester проверяет их буквально по файлам репозитория и тестам.
|
||||
|
||||
---
|
||||
|
||||
## AC-1 — Модель стампится в `agent_runs.model` в момент launch
|
||||
|
||||
**Условие:** запуск любого агента через `launcher._spawn` записывает резолвенную модель в
|
||||
`agent_runs.model` строки прогона ДО завершения процесса.
|
||||
- **PASS:** после стампа на launch (`UPDATE agent_runs SET model=…`/объединённый с effort)
|
||||
`SELECT model FROM agent_runs WHERE id=<run_id>` возвращает `resolve_agent_model(agent)` (непустую
|
||||
модель для текущей конфигурации — `claude-opus-4-8`); при пустом резолве — `NULL`. Запись
|
||||
происходит рядом со стампом эффорта (`launcher._spawn`).
|
||||
- **FAIL:** модель пишется только в `usage.record_usage` (постфактум); строка прогона имеет
|
||||
`model IS NULL` до завершения; стамп не изолирован и роняет launch при ошибке БД.
|
||||
|
||||
---
|
||||
|
||||
## AC-2 — Постфактум-enrich не затирает launch-стамп при оборванном JSON
|
||||
|
||||
**Условие:** `usage.record_usage` с отсутствующей/`None`-моделью не обнуляет launch-стампнутую модель.
|
||||
- **PASS:** `record_usage(run_id, None)` и `record_usage(run_id, {... "model": None})` для строки с
|
||||
launch-стампнутой моделью → `model` остаётся прежним непустым (семантика `COALESCE(?, model)`);
|
||||
`record_usage(run_id, {... "model": "claude-opus-4-8"})` → модель проставлена/уточнена.
|
||||
- **FAIL:** оборванный/малформный JSON приводит к `model = NULL`; enrich затирает корректный
|
||||
launch-стамп.
|
||||
|
||||
---
|
||||
|
||||
## AC-3 — Тайм-аут `developer`/`reviewer` поднят и конфигурируем без влияния на прочие роли
|
||||
|
||||
**Условие:** `launcher._resolve_timeout(agent)` возвращает поднятый бюджет для `developer`/`reviewer`
|
||||
и неизменный глобальный дефолт для остальных.
|
||||
- **PASS:** при сконфигурированном override `_resolve_timeout("developer")` и
|
||||
`_resolve_timeout("reviewer")` возвращают поднятые значения; `_resolve_timeout("analyst")`,
|
||||
`("architect")`, `("tester")`, `("deployer")` возвращают `settings.agent_timeout_seconds` (1800 по
|
||||
умолчанию). Конфигурация описана в `config.py` и `.env.example`.
|
||||
- **FAIL:** изменён бюджет роли вне `{developer, reviewer}`; значение захардкожено; бюджет не
|
||||
настраивается через config.
|
||||
|
||||
---
|
||||
|
||||
## AC-4 — Малформный timeout-конфиг → безопасный откат (never-break)
|
||||
|
||||
**Условие:** невалидный/малформный конфиг тайм-аутов не роняет прогон и не ломает старт.
|
||||
- **PASS:** при малформном `agent_timeout_overrides_json` (или невалидном выделенном ключе)
|
||||
`_resolve_timeout(...)` возвращает глобальный дефолт + пишет WARNING; процесс не падает.
|
||||
- **FAIL:** исключение пробрасывается; прогон/старт падает на плохом env.
|
||||
|
||||
---
|
||||
|
||||
## AC-5 — Reaper-инвариант сохранён
|
||||
|
||||
**Условие:** `reaper_max_running_s > max(резолвенный тайм-аут любого агента) + agent_kill_grace_seconds`.
|
||||
- **PASS:** с применённой конфигурацией бюджетов sanity-тест подтверждает неравенство для всех ролей
|
||||
(`developer`/`reviewer` включительно); при необходимости `reaper_max_running_s` поднят синхронно.
|
||||
- **FAIL:** поднятый бюджет `developer`/`reviewer` + grace ≥ `reaper_max_running_s` → job-reaper может
|
||||
реапнуть здоровый долгий прогон.
|
||||
|
||||
---
|
||||
|
||||
## AC-6 — Строка стадии трекера показывает модель+эффорт при timeout/kill
|
||||
|
||||
**Условие:** для прогона с `exit_code = -9` (timeout-kill) с launch-стампнутыми model+effort строка
|
||||
стадии рендерит оба значения.
|
||||
- **PASS:** `notifications`-рендер строки стадии (`_stage_line`) для такого `agent_runs`-ряда содержит
|
||||
` · <short_model> · <effort>` (например `· opus-4-8 · xhigh`); модель **не** `null`/пустая.
|
||||
- **FAIL:** при `exit_code=-9` строка показывает стоимость без модели (суффикс модели опущен), т.к.
|
||||
`model IS NULL`.
|
||||
|
||||
---
|
||||
|
||||
## AC-7 — In-flight видимость модели в `/metrics` и `/queue`
|
||||
|
||||
**Условие:** `db.get_running_agents` отдаёт модель для **running** job'а (до завершения прогона).
|
||||
- **PASS:** для running-job с launch-стампнутой моделью `get_running_agents()[i]["model"]` непуст;
|
||||
`GET /metrics` `agents[].model` непуст для активного агента.
|
||||
- **FAIL:** `model` остаётся `null` для running-job до завершения прогона.
|
||||
|
||||
---
|
||||
|
||||
## AC-8 — Timeout-killed прогон не продвигает стадию (анти-salvage)
|
||||
|
||||
**Условие:** прогон `developer`/`reviewer` с `exit_code != 0` (timeout-kill) не вызывает переход
|
||||
`development → review` / `review → testing`.
|
||||
- **PASS:** регресс-тест подтверждает, что прогон с `exit_code = -9` не продвигает стадию
|
||||
автоматически (следует retry/fail-пути; advance — только при чистом exit + зелёный exit-гейт).
|
||||
Salvage-режим отсутствует.
|
||||
- **FAIL:** убитый по тайм-ауту прогон «протекает» в следующую стадию без явного решения; либо введён
|
||||
неявный auto-salvage.
|
||||
|
||||
---
|
||||
|
||||
## AC-9 — Неприкосновенность контрактов и схемы
|
||||
|
||||
**Условие:** задача не трогает машину стадий, гейты и схему БД.
|
||||
- **PASS:** диффы НЕ содержат изменений `STAGE_TRANSITIONS`, реестра `QG_CHECKS`, `check_*`/`_parse_*`,
|
||||
machine-verdict ключей, `CREATE TABLE`/`ALTER TABLE`. `agent_runs.model` используется как есть.
|
||||
- **FAIL:** любое из перечисленного изменено.
|
||||
|
||||
---
|
||||
|
||||
## AC-10 — Документация и регресс
|
||||
|
||||
**Условие:** конфиг задокументирован, полный регресс зелёный.
|
||||
- **PASS:** комментарий-паспорт в `config.py` (блок ORCH-7) и `.env.example` описывают бюджеты
|
||||
`developer`/`reviewer`; `CHANGELOG.md`/`CLAUDE.md`/`docs/architecture/README.md` обновлены в том же
|
||||
PR; `pytest tests/ -q` зелёный; новые тесты ORCH-109 проходят.
|
||||
- **FAIL:** конфиг не задокументирован; документация рассинхронизирована с кодом; регресс красный.
|
||||
|
||||
---
|
||||
|
||||
## Сводная матрица AC ↔ FR/BR
|
||||
|
||||
| AC | Покрывает |
|
||||
|----|-----------|
|
||||
| AC-1 | BR-1 / FR-1 |
|
||||
| AC-2 | BR-2 / FR-2 |
|
||||
| AC-3 | BR-3 / FR-3 |
|
||||
| AC-4 | BR-3 / FR-3 / NFR-2 |
|
||||
| AC-5 | NFR-4 / FR-3 |
|
||||
| AC-6 | BR-4 / FR-4 |
|
||||
| AC-7 | BR-4 / FR-4 / NFR-6 |
|
||||
| AC-8 | BR-5 / FR-5 |
|
||||
| AC-9 | NFR-1 / NFR-3 / FR-5 |
|
||||
| AC-10 | BR-6 / FR-6 / NFR-1 |
|
||||
94
docs/work-items/ORCH-109/04-test-plan.yaml
Normal file
94
docs/work-items/ORCH-109/04-test-plan.yaml
Normal file
@@ -0,0 +1,94 @@
|
||||
work_item: ORCH-109
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-14
|
||||
model_used: claude-opus-4-8
|
||||
title: "Timeout budgets + launch-time model telemetry для developer/reviewer"
|
||||
framework: pytest
|
||||
scope: >
|
||||
Покрывает: launch-time стамп модели в agent_runs.model (FR-1), сохранение launch-стампа
|
||||
постфактум-enrich'ем (FR-2), конфигурируемый поднятый тайм-аут developer/reviewer без влияния
|
||||
на прочие роли (FR-3) + never-break на малформном конфиге, reaper-инвариант (NFR-4), видимость
|
||||
модели+эффорта в строке трекера при timeout-kill (FR-4) и in-flight в get_running_agents (NFR-6),
|
||||
guard анти-salvage — timeout-killed прогон не продвигает стадию (FR-5). Вне покрытия: model-routing,
|
||||
salvage недоделанной работы, изменения STAGE_TRANSITIONS/QG_CHECKS/схемы (их и не должно быть).
|
||||
notes: >
|
||||
Тесты детерминированы, без сети/LLM/subprocess Claude CLI: используют временную SQLite-БД и
|
||||
синтетические agent_runs-ряды; настройки подменяются через monkeypatch/override settings.
|
||||
Полный регресс tests/ должен оставаться зелёным; новый файл tests/test_orch109_timeout_model.py.
|
||||
Любой найденный разрыв в FR-5 закрывается guard'ом + тестом; если разрыва нет — TC-08 фиксирует
|
||||
существующую гарантию как анти-регресс.
|
||||
|
||||
tests:
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: "_resolve_timeout('developer') и ('reviewer') возвращают поднятый сконфигурированный бюджет"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: "_resolve_timeout для analyst/architect/tester/deployer возвращает глобальный agent_timeout_seconds (1800) — прочие роли не затронуты"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: "Малформный/невалидный timeout-конфиг -> _resolve_timeout откатывается на глобальный дефолт + WARNING, без исключения (never-break)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-04
|
||||
type: integration
|
||||
description: "Launch стампит agent_runs.model: после стамп-блока _spawn строка прогона имеет model == resolve_agent_model(agent) (непустую), рядом с effort"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: "Стамп модели изолирован: сбой записи (битый conn) не пробрасывает исключение из launch-пути (never-raise, NFR-2)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: "record_usage(run_id, None) и record_usage с model=None НЕ затирают launch-стампнутую модель (COALESCE preserve, FR-2)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: "record_usage с непустой model в usage-JSON уточняет/проставляет agent_runs.model (enrich по-прежнему работает)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: "Sanity reaper-инварианта: reaper_max_running_s > max(резолвенный тайм-аут всех ролей) + agent_kill_grace_seconds (NFR-4)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-09
|
||||
type: integration
|
||||
description: "Строка стадии трекера (_stage_line) для agent_runs с exit_code=-9 и launch-стампнутыми model+effort рендерит ' · <short_model> · <effort>' (model не null)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-10
|
||||
type: integration
|
||||
description: "get_running_agents отдаёт непустую model для running-job с launch-стампнутой моделью (in-flight видимость /metrics /queue, NFR-6)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-11
|
||||
type: integration
|
||||
description: "Анти-salvage: прогон developer/reviewer с exit_code=-9 не продвигает стадию (development->review / review->testing) автоматически; следует retry/fail-пути"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-12
|
||||
type: integration
|
||||
description: "Анти-регресс контрактов: STAGE_TRANSITIONS/QG_CHECKS/check_* и схема agent_runs не изменены (модель пишется в существующую колонку, миграции нет)"
|
||||
module: tests/test_orch109_timeout_model.py
|
||||
expected: PASS
|
||||
Reference in New Issue
Block a user