Files
orchestrator/docs/work-items/ORCH-126/02-trz.md
claude-bot 453c5b7d04
All checks were successful
CI / test (push) Successful in 1m12s
analyst(ET): auto-commit from analyst run_id=773
2026-06-17 11:07:33 +03:00

8.9 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used, track
work_item stage author_agent status created_at model_used track
ORCH-126 analysis analyst ready-for-review 2026-06-17 claude-opus-4-8 bug

02 — ТЗ (TRZ): ORCH-126 — гигиена run-ownership queued-job + диагностика невозможных состояний

Work Item: ORCH-126 · Repo: orchestrator · Стадия: analysis · Трек: Bug

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. «Как именно» (точка сброса, форма диагностики) — в пределах FR; финальный выбор — за developer. Архитектурного решения/ADR не требуется (укороченный маршрут ORCH-019).

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

Сбрасывать run-ownership строки jobs (run_id, pid) во всех путях возврата job'а в queued и/или в момент claim до _spawn, чтобы (а) queued-job никогда не нёс протухший run_id/pid, (б) свежеклеймленный-ещё-не-заспавненный job не нёс stale pid, который job-reaper примет за чужой живой/мёртвый процесс. Плюс — детектор «невозможного» queued-состояния: авто-санация при старте/реапе и наблюдаемость (лог + счётчик в GET /queue). Схема БД и контракты гейтов не меняются.

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

Путь Действие
src/db.py изменить: requeue_running_jobs / mark_job('queued') / mark_job_transient / reap_running_job('queued') — добавить сброс run_id=NULL, pid=NULL; claim_next_jobсброс stale pidrun_id) при флипе в running
src/agents/launcher.py проверить окно _spawn (claim→run_id/started_atPopenpid): убедиться, что при провале _spawn до строки 711 job не остаётся со stale pid (опирается на сброс в claim/mark_job)
src/job_reaper.py (опц., по выбору developer) Tier-1 анти-false-positive: pid IS NULL у свежего running уже трактуется как «нет pid → не реапить»; добавить авто-санацию/счётчик невозможных queued-строк, если фикс на стороне reaper
src/main.py (опц.) при старте после requeue_running_jobs — лог/санация обнаруженных невозможных queued-состояний (наблюдаемость BR-4)
tests/test_orch126_queued_stale_run.py создать: регресс + покрытие FR-1..FR-4

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

FR-1 — Сброс run-ownership на всех путях возврата в queued (BR-2)

Каждый путь, переводящий job в queued, обязан выставить run_id=NULL и pid=NULL той же UPDATE-транзакцией, что уже чистит started_at/finished_at: db.requeue_running_jobs() (restart-recovery), db.mark_job(status='queued'), db.mark_job_transient(), db.reap_running_job(status='queued'). Инвариант: status='queued' ⇒ run_id IS NULL AND pid IS NULL AND started_at IS NULL. Атомарные guard'ы по status (reap_running_job ... WHERE status='running') — сохранить байт-в-байт.

FR-2 — Чистый claim (BR-1, BR-3)

db.claim_next_job при флипе queued→running не должен оставлять stale pidrun_id) от прошлой попытки: либо сбросить их в том же UPDATE (pid=NULL, run_id=NULL), либо опираться на FR-1 (тогда queued-job их уже не несёт). Defense-in-depth (оба) — предпочтительно. SELECT-гейт (status='queued' AND available_at<=now + dep/serial-gate) — не трогать (NFR-2 offline). Результат: между claim и стампом pid в _spawn job имеет pid IS NULL (не чужой pid).

FR-3 — Безопасность окна _spawn (BR-3)

Если _spawn падает до стампа pid (launcher.py:711) — ensure_worktree/ _materialize_deferred_branch/_write_task_file, — обработчик queue_worker._drain_once (mark_job('queued'|'failed')) обязан, по FR-1, оставить job без stale pid. Проверить, что повторный claim после такого провала стартует штатно (а не оседает «частично стартовавшим»).

FR-4 — Детект и обработка невозможного состояния (BR-4)

«Невозможное» queued-состояние = status='queued' AND (run_id IS NOT NULL OR pid IS NOT NULL OR started_at IS NOT NULL). Поведение:

  • Авто-санация при старте (main.lifespan после requeue_running_jobs) и/или при реап-тике — привести такие строки к чистому queued (FR-1) идемпотентно, never-raise.
  • Наблюдаемость — структурный WARNING с job_id/run_id/pid + read-only счётчик в блоке очереди GET /queue (например queue.impossible_queued или поле в существующем снимке worker'а).

FR-5 — Корректность reaper-liveness (BR-1, NFR-5)

После FR-1..FR-3 job-reaper (ORCH-065) на свежеклеймленном running видит pid IS NULL → Tier-1 не копит dead-тики против чужого pid и не реапит легитимный старт; фантомный «живой» pid не блокирует очередь. Инварианты ORCH-065/113/114 (Tier-2 finalize-grace, finalizer-liveness, transition-lease) — не нарушать.

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

Нет новых эндпоинтов. Расширение наблюдаемости read-only снимка GET /queue — добавить счётчик/индикатор обнаруженных и санированных невозможных queued-состояний (BR-4); существующие поля снимка не переименовывать.

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

Нет. Колонки jobs.run_id / jobs.pid / jobs.started_at уже существуют; фикс — корректное заполнение/сброс. Никаких _ensure_column/новых таблиц/индексов.

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

Нет. STAGE_TRANSITIONS / реестр QG_CHECKS / check_* / machine-verdict-ключи — байт-в-байт. Дефект — свойство гигиены данных планировщика, не Quality Gate.

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

  • Обратная совместимость: для не-stale job'ов поведение байт-в-байт (они и так не несут run_id/pid в queued); фикс лишь нормализует аномальные строки.
  • Область: общий планировщик/очередь (не self-hosting-scoped leaf) — затрагивает все проекты, но семантически нейтрально (приведение к уже-документированному инварианту «queued = без run-ownership»).
  • Kill-switch: правка — исправление инварианта данных, не новая фича; отдельный флаг не требуется. Опциональную авто-санацию/диагностику (FR-4) допустимо закрыть под флаг, если developer сочтёт нужным (дефолт = включено), но базовый сброс FR-1..FR-3 — безусловен.
  • Обратимость: изменения локальны (UPDATE-наборы в db.py); откат — ревертом PR.
  • Миграция: не требуется; существующие аномальные строки санируются при первом старте (FR-4).
  • Трассировка (CLAUDE.md §9): перед правкой pid/run_id-логики прочитать ADR ORCH-065 (reaper Tier-1), ORCH-113 (finalizer-liveness), ORCH-114 (transition-lease/recover_on_startup), ORCH-099 (/metrics); не сломать их инварианты.