---
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 |
