diff --git a/docs/work-items/ORCH-044/01-brd.md b/docs/work-items/ORCH-044/01-brd.md new file mode 100644 index 0000000..54697cb --- /dev/null +++ b/docs/work-items/ORCH-044/01-brd.md @@ -0,0 +1,90 @@ +# 01 — Business Requirements Document (BRD) + +**Work Item:** ORCH-044 +**Title:** Надёжность запуска агента: preflight ловит auth+битый флаг, --effort фикс +**Приоритет:** Высокий (надёжность конвейера) +**Автор запроса:** Слава, 05.06 («почему перед стартом аналитика не прошла проверка?») + +## 1. Контекст и инцидент (05.06) +Задача **ORCH-17** застряла на стадии Analysis ~30 минут. Аналитик-агент стартовал и +мгновенно «умирал»: run-лог — **пустой файл (0 байт)**, а job в очереди оставался в +состоянии `running` (вечное зависание без сигнала). + +Корневые причины (две, наложились): +1. **`claude` Not logged in** после ребилда контейнера — токен/сессия не поднялись. +2. **Флаг `--effort`** в связке с `--print --output-format json` (CLI 2.1.142) **гасил весь + stdout** — claude завершался с пустым выводом. + +**Главная системная проблема:** preflight-проверка пропустила обе битые задачи в работу — +она слепа к авторизации и не ловит «битый флаг → пустой вывод». + +## 2. Проблема (как есть) +- **P1. Дыра в preflight (главное).** `src/preflight.py` сознательно проверяет только + (a) `os.path.exists(CLAUDE_BIN)` и (b) `claude --version` (timeout 5s, без токенов). + Но `--version` отвечает успешно **даже когда claude НЕ залогинен** (версия — локальная + информация). Итог: `preflight=ok`, а реальный запуск падает `Not logged in`. Preflight + слеп к авторизации и пропускает заведомо нерабочие задачи в очередь. +- **P2. `--effort` ломает вывод.** Флаг `--effort ` совместно с + `--print`/`--output-format json` в CLI 2.1.142 даёт **пустой stdout** — агент молча + умирает. Сейчас effort **отключён в проде** хотфиксом (`.env`: `ORCH_AGENT_EFFORT_*=""`), + но дефолты в `src/config.py` всё ещё `high`/`medium`, а документация (INFRA.md, + internals.md, ORCH-41) описывает effort как рабочую фичу. Несоответствие кода/доков/прода. +- **P3. Пустой лог ≠ провал.** Агент с пустым run-логом (0 байт) и `exit 0` трактуется как + **успех** (`_finalize_job` → `done`, авто-advance стадии) либо вечно висит `running`. + Ни watchdog, ни ретрай не срабатывают. Нет сигнала об инциденте. + +## 3. Бизнес-последствия +- Любой сбой авторизации или несовместимости флага → **тихое зависание** задачи без алерта. +- Блокируется конвейер **всех** проектов (общий инстанс/очередь, self-hosting) — как было с + ORCH-17 (30 мин простоя, ручное вмешательство). +- Деградация доверия к автономности оркестратора: «проверка перед стартом» не работает. + +## 4. Цель +Сделать запуск агента **отказоустойчивым по входу и по выходу**: +1. Preflight ловит отсутствие/протухание авторизации **дёшево и без траты токенов** до того, + как job будет заклеймлен. +2. Разобраться с `--effort` и привести код/доки/прод к одному непротиворечивому состоянию. +3. Пустой/невалидный результат запуска трактуется как **провал** (job → `failed`), чтобы + сработали watchdog/ретрай и алерт, а не вечное зависание. + +## 5. Заинтересованные стороны +- **Owner/Слава** — инициатор, требует «проверки перед стартом». +- **Все проекты на инстансе** (enduro-trails и self-hosting ORCH) — страдают от простоя. +- **Агенты конвейера** — analyst/architect/... — все запускаются через единый launcher. + +## 6. Объём (Scope) +**В объёме:** +- Дешёвая token-free проверка авторизации в preflight. +- Расследование и решение по `--effort` (вернуть корректно ИЛИ задокументировать как + unsupported и убрать из кода/дефолтов/доков). +- Детекция «пустой лог / нет валидного result-JSON» как провала job с корректным + переводом в `failed` и срабатыванием ретрая/алерта. +- Обновление документации (INFRA.md / internals.md / CHANGELOG) в том же PR. + +**Вне объёма:** +- Prompt-ping (ping→pong) — **запрещено** (жжёт rate limit). Только локальные/дешёвые проверки. +- Реформа circuit breaker / backoff-логики (используем существующие механизмы). +- Изменение схемы стадий/конвейера. +- Автоматический re-login claude (восстановление авторизации) — отдельная задача. + +## 7. Бизнес-правила +- BR-1: Preflight **не тратит токены** и не делает сетевых вызовов к API модели. +- BR-2: Протухшая/нечитаемая авторизация → `preflight=fail` → job **не клеймится** (остаётся + `queued`), пишется warning, при необходимости — алерт/брейкер. +- BR-3: Пустой run-лог ИЛИ отсутствие валидного result-JSON при `exit 0` → job `failed` + (никогда не `done` и не вечный `running`). +- BR-4: Никаких `--no-verify`/обхода хуков без явного одобрения Owner. +- BR-5: Код, дефолты `config.py`, прод `.env` и документация по `--effort` должны быть + взаимно непротиворечивы после задачи. + +## 8. Критерии успеха (бизнес-уровень) +- Симуляция «не залогинен» → preflight ловит до клейма, job не стартует впустую. +- Симуляция «пустой лог + exit 0» → job становится `failed`, срабатывает ретрай/алерт. +- Состояние `--effort` однозначно: либо работает с json-форматом, либо удалён из активного + пути и доков (без «мёртвого» флага в дефолтах). +- Инцидент класса ORCH-17 больше не приводит к тихому 30-минутному зависанию. + +## 9. Связанные материалы +- `src/preflight.py`, `src/queue_worker.py`, `src/agents/launcher.py`, `src/config.py` +- `docs/history/LESSONS_ORCH-017.md`, `docs/history/LESSONS_2026-06-05.md` +- ORCH-41 (effort/model resolver), ORCH-1 (очередь/resilience), ORCH-7 (watchdog) diff --git a/docs/work-items/ORCH-044/02-trz.md b/docs/work-items/ORCH-044/02-trz.md new file mode 100644 index 0000000..049a031 --- /dev/null +++ b/docs/work-items/ORCH-044/02-trz.md @@ -0,0 +1,129 @@ +# 02 — Техническое задание (ТЗ) + +**Work Item:** ORCH-044 +**Основано на:** 01-brd.md + +> Примечание: ТЗ фиксирует **что** должно измениться и **наблюдаемое поведение**. +> Выбор конкретной реализации (например, формат проверки `.credentials.json` vs парсинг +> маркера в логе) — за архитектором (стадия architecture, ADR). Где описаны варианты — +> это границы допустимого решения, а не предписание. + +## 1. Задействованные модули `src/` +| Модуль | Текущее место | Изменение | +|--------|---------------|-----------| +| `src/preflight.py` | `_run_version`, `_compute`, `check` | Добавить дешёвую token-free проверку авторизации (P1) | +| `src/config.py` | блок ORCH-41 effort (стр. 98–108), новый блок настроек preflight-auth | Настройки auth-проверки; решение по effort-дефолтам (P2) | +| `src/agents/launcher.py` | `_spawn` (effort_flag, стр. 290–292, 303–311), `_monitor_agent` (стр. 460–615), `_finalize_job` (стр. 630–667) | Решение по `--effort` (P2); детекция пустого лога / отсутствия result-JSON (P3) | +| `src/queue_worker.py` | `_drain_once` claim-gating (стр. 158–165) | Учесть новый auth-fail preflight в гейтинге клейма (P1) — при необходимости | +| `src/db.py` | `mark_job` | Использование существующего перевода job → `failed` (P3); новых колонок не требуется | + +Новых файлов модулей не предполагается обязательно; допускается выделение хелпера +(например, `_check_auth()` в `preflight.py`) — на усмотрение архитектора. + +## 2. Требования по проблемам + +### P1 — Preflight ловит авторизацию (token-free) +- **TR-1.1.** Preflight ДОЛЖЕН, помимо `os.path.exists(bin)` и `claude --version`, выполнять + **дешёвую проверку авторизации без обращения к API модели и без prompt-ping**. +- **TR-1.2.** Допустимые подходы (выбор — за архитектором, ADR): + - (a) Проверка существования и читаемости файла учётных данных + `~/.claude/.credentials.json` (HOME агента — `/home/slin`, см. launcher env, стр. 326) + и валидности OAuth-токена по дате истечения внутри + (`claudeAiOauth.expiresAt`, epoch ms) — `expiresAt <= now` ⇒ протух ⇒ fail; + - (b) Парсинг реального run-вывода на маркер `Not logged in` (и подобные) с переводом + job в провал и размыканием/учётом circuit breaker. + - Подход (a) предпочтителен как **проактивный** (ловит ДО клейма); (b) — как защитная + сетка постфактум. Допускается комбинация. +- **TR-1.3.** Путь к файлу учётных данных ДОЛЖЕН резолвиться согласованно с тем HOME, + под которым launcher реально спавнит claude (`/home/slin`), а не из окружения процесса + оркестратора (аналогично тому, как `_claude_bin()` следует за реально исполняемым путём). +- **TR-1.4.** Результат auth-проверки кешируется тем же механизмом, что и version-check + (`preflight_cache_ttl`), чтобы не читать файл на каждый тик воркера. +- **TR-1.5.** При `auth=fail`: `check()` возвращает `(False, reason)` с **информативным + reason** (например, `claude not logged in: credentials missing` / `OAuth token expired at + `). Job НЕ клеймится (поведение `_drain_once` уже корректно при `ok=False`). +- **TR-1.6.** Граница ответственности: preflight остаётся **локальным** (BR-1). Сетевая + валидация токена у провайдера — вне объёма. +- **TR-1.7.** Поведение при «всё хорошо» не меняется: залогинен + валидный токен ⇒ `ok=True`. + +### P2 — Решение по `--effort` +- **TR-2.1.** Провести расследование (стадия architecture/development): причина пустого + stdout при `--effort` + `--print --output-format json` в CLI 2.1.142 — несовместимость + с json-форматом, иной синтаксис флага, или баг CLI. Зафиксировать вывод в ADR/`10-tech-risks.md`. +- **TR-2.2.** По итогам выбрать **ровно один** исход и привести к нему код+доки+дефолты: + - **Вариант A (вернуть effort):** найден корректный способ (например, иной синтаксис или + несовместимость только с конкретным output-format) — `--effort` снова формируется в + `_spawn` корректно; прод-хотфикс `ORCH_AGENT_EFFORT_*=""` снимается; добавить + регресс-тест, что вывод не пустой. + - **Вариант B (unsupported):** effort несовместим — **убрать `--effort` из активного пути + запуска** (`_spawn` не формирует `effort_flag`), убрать/нейтрализовать дефолты effort в + `config.py`, обновить ORCH-41-доки (INFRA.md, internals.md) пометив фичу как unsupported + на данной версии CLI. `resolve_agent_effort` либо удаляется, либо документированно + оставляется заглушкой (решение — ADR). +- **TR-2.3.** Независимо от A/B: **не должно остаться «мёртвого» флага**, который тихо гасит + вывод. После задачи запуск с дефолтной конфигурацией прода ДОЛЖЕН давать непустой + result-JSON. +- **TR-2.4.** Изменение дефолтов/удаление флага не должно ломать `resolve_agent_model` + (модель — независимая фича ORCH-41) и существующие тесты `test_resolve_agent_effort.py` + (их допустимо обновить под новый контракт). + +### P3 — Пустой лог / нет result-JSON ⇒ провал +- **TR-3.1.** В `_monitor_agent`/`_finalize_job`: при `exit_code == 0` ДОЛЖНА выполняться + **проверка валидности результата** перед тем как считать job успешным: + - run-лог **непустой** (размер > 0 и/или содержит непустой текст), И + - из него извлекается **валидный result-JSON** (тот же контракт, что использует + `usage._extract_last_json_object` / `parse_usage_from_log`). +- **TR-3.2.** Если результат невалиден (пустой лог ИЛИ нет валидного JSON) при `exit_code==0`, + job ДОЛЖЕН трактоваться как **провал**: + - НЕ переводиться в `done`; + - попасть в путь ретрая/провала (`attempts < max_attempts` ⇒ requeue, иначе `failed`), + аналогично permanent-ветке `_finalize_permanent`, с информативным `error` + (например, `empty run log / no result JSON (run_id=...)`); + - сгенерировать алерт (Telegram), как прочие провалы; + - НЕ выполнять авто-advance стадии (`_try_advance_stage`) и НЕ постить «успешный» + status-коммент. +- **TR-3.3.** Классификация такого провала: по умолчанию — **permanent** (это не 429/overload). + Если в логе присутствует transient-маркер (через `error_classifier`) — допускается + transient-путь. Auth-провал (`Not logged in`) — на усмотрение архитектора: может + маршрутизироваться как сигнал брейкеру (P1/TR-1.2b). +- **TR-3.4.** Никогда не оставлять job в `running` навечно из-за пустого результата: либо + `done` (валидно), либо `failed`/`queued`(retry). (Watchdog ORCH-7 продолжает закрывать + случай таймаута; здесь закрывается случай «быстрая смерть с exit 0».) +- **TR-3.5.** Защитность: вся проверка обёрнута так, что её собственная ошибка не роняет + монитор (как и прочий код `_monitor_agent`); при сомнении — fail-safe в сторону провала job. + +## 3. Изменения API +Нет новых/изменённых HTTP-endpoint'ов. Допускается обогащение поля `preflight_reason` в +`/queue` (через существующий `worker.status()` / `QueueWorker.last_preflight_reason`) более +информативным auth-сообщением — без изменения схемы ответа. + +## 4. Изменения схемы БД +Нет. Используются существующие колонки `jobs` (`status`, `error`, `attempts`, +`max_attempts`, `transient_attempts`) и `agent_runs`. Новых таблиц/колонок не требуется. + +## 5. Требования к новым QG checks +Новых Quality Gate проверок не требуется — изменения в слое запуска/preflight, не в гейтах +стадий. Реестр `QG_CHECKS` не меняется. + +## 6. Конфигурация (env / config.py) +- Возможные новые настройки preflight-auth (имена — на усмотрение архитектора), например: + - `ORCH_PREFLIGHT_CHECK_AUTH` (bool, default true) — включение auth-проверки; + - путь к credentials, если не выводится из HOME автоматически. +- Решение по effort-дефолтам (`agent_effort_*`) согласно TR-2.2 (нейтрализовать при варианте B). +- Все новые настройки документируются в `config.py` docstring и в INFRA.md (env-карта). + +## 7. Артефакты pipeline (обязательны к созданию/обновлению) +- `06-adr/ADR-NNN-*.md` — решение по подходу preflight-auth (a/b/комбо) и по effort (A/B). +- `10-tech-risks.md` — риск ложноположительной auth-проверки, риск регрессии effort, риск + fail-safe-провала на легитимных пустых выводах. +- `12-review.md`, `13-test-report.md` — по стадиям. +- Обновить `docs/operations/INFRA.md` и `docs/architecture/internals.md` (effort-секции), + `CHANGELOG.md`. Документация = golden source (правило агентов №2). + +## 8. Ограничения и запреты +- ❌ Prompt-ping в preflight (жжёт rate limit) — запрещено (BR-1, комментарий в preflight.py). +- ❌ Сетевые вызовы к API модели в preflight. +- ❌ Оставлять job в `running` без таймаута при пустом результате. +- ❌ `--no-verify`/обход хуков без одобрения Owner. +- ⚠️ Self-hosting: не ронять прод-контейнер `orchestrator`; проверка изменений — через + staging (8501) перед прод-деплоем (см. CLAUDE.md, INFRA.md). diff --git a/docs/work-items/ORCH-044/03-acceptance-criteria.md b/docs/work-items/ORCH-044/03-acceptance-criteria.md new file mode 100644 index 0000000..cd553e2 --- /dev/null +++ b/docs/work-items/ORCH-044/03-acceptance-criteria.md @@ -0,0 +1,119 @@ +# 03 — Критерии приёмки (Acceptance Criteria) + +**Work Item:** ORCH-044 +Каждый критерий — однозначное PASS/FAIL. Привязка к TR из `02-trz.md`. + +## P1 — Preflight ловит авторизацию + +### AC-1 — Не залогинен ⇒ preflight FAIL (TR-1.1, TR-1.2, TR-1.5) +- **Дано:** бинарь claude существует, `claude --version` отвечает успешно, НО учётные + данные отсутствуют/нечитаемы (нет `.credentials.json`). +- **Когда:** вызывается `preflight.check(force=True)`. +- **Тогда:** возвращается `(False, reason)`, где `reason` упоминает авторизацию + (например, «not logged in» / «credentials»). +- **FAIL если:** возвращается `(True, ...)` (как сейчас — слепота к auth). + +### AC-2 — Протухший OAuth-токен ⇒ preflight FAIL (TR-1.2a) +- **Дано:** `.credentials.json` существует и читаем, но `claudeAiOauth.expiresAt` в прошлом. +- **Когда:** `preflight.check(force=True)`. +- **Тогда:** `(False, reason)` с указанием на истечение токена. +- *(N/A, если архитектор выбрал чистый вариант (b) без чтения файла — тогда покрывается AC-9.)* + +### AC-3 — Валидный логин ⇒ preflight OK без регрессии (TR-1.7) +- **Дано:** bin есть, `--version` ок, `.credentials.json` читаем, `expiresAt` в будущем. +- **Когда:** `preflight.check(force=True)`. +- **Тогда:** `(True, ...)`. +- **FAIL если:** залогиненный валидный кейс даёт FAIL (ложное срабатывание). + +### AC-4 — Auth-fail блокирует клейм job (TR-1.5, BR-2) +- **Дано:** preflight возвращает `(False, ...)` из-за auth; в очереди есть `queued` job. +- **Когда:** `QueueWorker._drain_once()` выполняет тик. +- **Тогда:** job **не клеймится** (остаётся `queued`), в `worker.last_preflight_ok=False`, + пишется лог-warning; claude не спавнится. +- **FAIL если:** job переходит в `running` / спавнится агент. + +### AC-5 — Token-free и локально (BR-1, TR-1.6) +- **Дано:** auth-проверка. +- **Тогда:** она НЕ делает prompt-ping и НЕ обращается к API модели (никаких httpx/сетевых + вызовов к провайдеру в пути проверки; проверяется по коду/моку — сетевой вызов не + происходит). +- **FAIL если:** проверка отправляет запрос к модели/жжёт токены. + +### AC-6 — Кеширование auth-проверки (TR-1.4) +- **Дано:** `preflight_cache_ttl` > 0, первый `check()` выполнен. +- **Когда:** повторные `check()` в пределах TTL. +- **Тогда:** дорогая часть (чтение файла/процесс) не повторяется чаще TTL (как у version-check). +- **FAIL если:** файл/процесс дёргается на каждый тик внутри TTL. + +## P2 — Решение по `--effort` + +### AC-7 — Расследование задокументировано (TR-2.1) +- **Тогда:** в ADR (`06-adr/`) и/или `10-tech-risks.md` зафиксирована причина пустого stdout + при `--effort` + `--print --output-format json` (несовместимость/синтаксис/баг CLI). +- **FAIL если:** изменения внесены без объяснения первопричины. + +### AC-8 — Однозначный исход A или B, без «мёртвого» флага (TR-2.2, TR-2.3) +- **Тогда:** реализован ровно один из вариантов: + - **A:** `--effort` формируется и запуск с ним даёт **непустой** result-JSON; прод-хотфикс + `ORCH_AGENT_EFFORT_*=""` более не требуется; есть регресс-тест на непустой вывод; ИЛИ + - **B:** `--effort` **не формируется** в активном пути `_spawn`; дефолты `agent_effort_*` + нейтрализованы; ORCH-41-доки помечают effort как unsupported на текущем CLI. +- **FAIL если:** в коде остаётся путь, где дефолтная конфигурация добавляет `--effort` и + гасит вывод; ИЛИ код/доки/дефолты противоречат друг другу. + +### AC-9 — Дефолтный запуск даёт непустой результат (TR-2.3, перекликается с P3) +- **Дано:** конфигурация по умолчанию после задачи (без ручного хотфикса в `.env`). +- **Когда:** агент запускается стандартным путём `_spawn`. +- **Тогда:** результат запуска — непустой run-лог с валидным result-JSON (проверяемо + модульно через построение cmd и/или интеграционно на моке claude). +- **FAIL если:** дефолтный путь воспроизводит пустой stdout инцидента. + +## P3 — Пустой лог / нет result-JSON ⇒ провал + +### AC-10 — Пустой лог + exit 0 ⇒ job НЕ done (TR-3.1, TR-3.2) +- **Дано:** агент завершился `exit_code=0`, но run-лог пустой (0 байт). +- **Когда:** отрабатывает `_monitor_agent`/`_finalize_job`. +- **Тогда:** job НЕ переходит в `done`; переходит в `failed` (или `queued` при наличии + retry-бюджета) с информативным `error`; шлётся алерт. +- **FAIL если:** job становится `done`, либо остаётся `running` навсегда. + +### AC-11 — Нет валидного result-JSON + exit 0 ⇒ job НЕ done (TR-3.1, TR-3.2) +- **Дано:** run-лог непустой, но не содержит валидного result-JSON (мусор/обрезок). +- **Когда:** финализация job. +- **Тогда:** job трактуется как провал (как AC-10). +- **FAIL если:** job становится `done`. + +### AC-12 — Нет авто-advance и нет «успешного» коммента при провале результата (TR-3.2) +- **Дано:** кейс AC-10/AC-11. +- **Тогда:** `_try_advance_stage` НЕ вызывается (стадия не двигается), «успешный» + status-коммент агента НЕ постится. +- **FAIL если:** стадия продвинулась/запостился успех при пустом результате. + +### AC-13 — Валидный результат не регрессирует (TR-3.1) +- **Дано:** `exit_code=0` и непустой run-лог с валидным result-JSON. +- **Когда:** финализация job. +- **Тогда:** job → `done`, авто-advance и usage-коммент работают как раньше. +- **FAIL если:** легитимный успешный запуск теперь ошибочно помечается провалом. + +### AC-14 — Никогда не вечный `running` (TR-3.4, BR-3) +- **Тогда:** для любого завершившегося процесса (любой exit_code, включая 0 с пустым логом) + job завершается в терминальном/ретраябельном состоянии (`done`/`failed`/`queued`), не + остаётся `running`. +- **FAIL если:** существует путь, оставляющий job `running` после выхода процесса. + +## Сквозные + +### AC-15 — Документация обновлена в том же PR (правило агентов №2, №6) +- **Тогда:** обновлены `docs/operations/INFRA.md` (env-карта preflight-auth и/или effort), + `docs/architecture/internals.md` (effort-секция), `CHANGELOG.md`; заведён ADR. +- **FAIL если:** функционал изменён, доки/CHANGELOG/ADR не обновлены (reviewer → REQUEST_CHANGES). + +### AC-16 — Тесты зелёные (test-plan) +- **Тогда:** все тесты из `04-test-plan.yaml` проходят; `pytest tests/ -q` зелёный. +- **FAIL если:** хотя бы один тест плана FAIL или существующие тесты сломаны без обоснованного + обновления контракта. + +### AC-17 — Self-hosting безопасность (CLAUDE.md) +- **Тогда:** изменения не требуют рестарта/падения прод-контейнера `orchestrator` в рамках + задачи; проверка прошла через staging (8501). +- **FAIL если:** задача ломает/рестартует прод-инстанс, останавливая конвейер других проектов. diff --git a/docs/work-items/ORCH-044/04-test-plan.yaml b/docs/work-items/ORCH-044/04-test-plan.yaml new file mode 100644 index 0000000..517ab08 --- /dev/null +++ b/docs/work-items/ORCH-044/04-test-plan.yaml @@ -0,0 +1,145 @@ +work_item: ORCH-044 +title: "Надёжность запуска агента: preflight auth + --effort фикс + пустой лог = провал" +notes: > + Реальный claude/Popen НЕ спавнится: subprocess и launcher мокаются (паттерн + tests/test_resilience.py). БД — свежий per-test sqlite (fixture fresh_db). + Файлы учётных данных создаются во временном каталоге (tmp_path) и путь + мокается. Сетевые вызовы запрещены — проверяются моками/отсутствием httpx. + +tests: + # ---------------- P1: preflight ловит авторизацию ---------------- + - id: TC-01 + type: unit + description: "Нет .credentials.json при рабочем --version -> preflight.check() = (False, reason про auth)" + module: tests/test_preflight_auth.py + covers: [AC-1, TR-1.1, TR-1.2] + expected: PASS + + - id: TC-02 + type: unit + description: "Протухший OAuth (claudeAiOauth.expiresAt в прошлом) -> preflight FAIL про истечение токена" + module: tests/test_preflight_auth.py + covers: [AC-2, TR-1.2a] + expected: PASS + + - id: TC-03 + type: unit + description: "Валидный логин (credentials читаемы, expiresAt в будущем) -> preflight OK, без регрессии" + module: tests/test_preflight_auth.py + covers: [AC-3, TR-1.7] + expected: PASS + + - id: TC-04 + type: unit + description: "Нечитаемый/битый .credentials.json (невалидный JSON) -> preflight FAIL, не падает исключением" + module: tests/test_preflight_auth.py + covers: [AC-1, TR-1.2a, TR-3.5] + expected: PASS + + - id: TC-05 + type: unit + description: "Auth-проверка token-free: при check() не происходит сетевого вызова к API модели (мок httpx/urlopen не вызван)" + module: tests/test_preflight_auth.py + covers: [AC-5, BR-1, TR-1.6] + expected: PASS + + - id: TC-06 + type: unit + description: "Auth-результат кешируется: повторные check() в пределах preflight_cache_ttl не перечитывают credentials" + module: tests/test_preflight_auth.py + covers: [AC-6, TR-1.4] + expected: PASS + + - id: TC-07 + type: unit + description: "Путь к credentials резолвится от HOME агента (/home/slin), а не от окружения процесса оркестратора" + module: tests/test_preflight_auth.py + covers: [TR-1.3] + expected: PASS + + - id: TC-08 + type: integration + description: "QueueWorker._drain_once при preflight auth-fail не клеймит job: job остаётся queued, claude не спавнится, last_preflight_ok=False" + module: tests/test_preflight_auth.py + covers: [AC-4, BR-2, TR-1.5] + expected: PASS + + # ---------------- P2: решение по --effort ---------------- + - id: TC-09 + type: unit + description: "Вариант B: при дефолтной конфигурации построенная cmd в _spawn НЕ содержит '--effort' (флаг не гасит вывод). При варианте A — тест адаптируется на корректное формирование effort" + module: tests/test_effort_flag.py + covers: [AC-8, TR-2.2, TR-2.3] + expected: PASS + + - id: TC-10 + type: unit + description: "resolve_agent_effort согласован с принятым решением (B: нейтрализован/пусто по дефолту; A: валидное значение). Существующий test_resolve_agent_effort обновлён под новый контракт и зелёный" + module: tests/test_resolve_agent_effort.py + covers: [AC-8, TR-2.4] + expected: PASS + + - id: TC-11 + type: integration + description: "Дефолтный путь запуска (мок claude, отдающий валидный result-JSON) даёт непустой лог с валидным JSON — воспроизведение инцидента (пустой stdout) не происходит" + module: tests/test_effort_flag.py + covers: [AC-9, TR-2.3] + expected: PASS + + # ---------------- P3: пустой лог / нет result-JSON = провал ---------------- + - id: TC-12 + type: integration + description: "exit_code=0 + пустой run-лог (0 байт) -> job НЕ done; помечается failed (или queued при retry-бюджете) с информативным error; алерт вызван" + module: tests/test_empty_log_failure.py + covers: [AC-10, TR-3.1, TR-3.2] + expected: PASS + + - id: TC-13 + type: integration + description: "exit_code=0 + лог без валидного result-JSON (мусор) -> job трактуется как провал, не done" + module: tests/test_empty_log_failure.py + covers: [AC-11, TR-3.1] + expected: PASS + + - id: TC-14 + type: integration + description: "При провале по пустому результату _try_advance_stage НЕ вызывается и успешный usage-коммент НЕ постится" + module: tests/test_empty_log_failure.py + covers: [AC-12, TR-3.2] + expected: PASS + + - id: TC-15 + type: integration + description: "exit_code=0 + непустой лог с валидным result-JSON -> job done, авто-advance и usage-коммент работают (нет регрессии)" + module: tests/test_empty_log_failure.py + covers: [AC-13, TR-3.1] + expected: PASS + + - id: TC-16 + type: integration + description: "Любой выход процесса не оставляет job в 'running': пустой лог+exit0 завершается терминально (done/failed/queued)" + module: tests/test_empty_log_failure.py + covers: [AC-14, BR-3, TR-3.4] + expected: PASS + + - id: TC-17 + type: unit + description: "Классификация пустого результата по умолчанию permanent; transient-маркер в логе уводит в transient-путь (error_classifier)" + module: tests/test_empty_log_failure.py + covers: [TR-3.3] + expected: PASS + + # ---------------- Регрессия / сквозное ---------------- + - id: TC-18 + type: unit + description: "Регресс: существующие preflight-кейсы (bin missing, --version ok) из test_resilience.py остаются зелёными после добавления auth-слоя" + module: tests/test_resilience.py + covers: [AC-3, TR-1.7] + expected: PASS + + - id: TC-19 + type: integration + description: "Полный прогон 'pytest tests/ -q' зелёный — ни один существующий тест не сломан без обоснованного обновления контракта" + module: tests/ + covers: [AC-16] + expected: PASS