Files

8.3 KiB
Raw Permalink Blame History

BRD — ORCH-065: zombie jobs + залипший merge-lease

Work Item ID: ORCH-065 Тип: BUG (P0) Репозиторий: orchestrator (self-hosting) Эпик: блокер ORCH-54 (полностью автономный self-deploy)

1. Контекст и проблема

Оркестратор — единый инстанс с общей БД и общей очередью (jobs, max_concurrency=1 для self-hosting), обслуживающий несколько проектов. Финальная автономность self-deploy упирается в два связанных класса отказов, оба сводящиеся к «процесс умер/завершился, а состояние осталось захваченным навсегда»:

Проблема A — zombie jobs (строка jobs навсегда running)

Агент (deployer/developer/reviewer) завершается или умирает (краш, OOM, рестарт контейнера в ходе self-deploy, гибель monitor-потока), но строка в таблице jobs остаётся в статусе running. Финализация статуса job выполняется только в _monitor_agent_finalize_job внутри того же процесса; если этот поток/процесс не доживает до финализации — job «зомбирован».

  • Единственная имеющаяся защита — requeue_running_jobs() в main.lifespan, срабатывающая исключительно на старте процесса. Зомби, возникший без рестарта (умер дочерний процесс/monitor-поток, а сервис жив), не реанимируется никогда.
  • При max_concurrency=1 одна зомби-строка running блокирует claim всех последующих job (count_running_jobs() >= max_concurrency → claim не происходит) → встаёт конвейер всех проектов.

Проблема B — залипший merge-lease

Merge-gate (ORCH-043) берёт файловый lease <repos_dir>/.merge-lease-<repo>.json ПЕРЕД rebase+re-test и держит его до фактического merge PR в main. Если процесс умирает на финальном merge с зажатым lease:

  • Реклейм lease реализован лениво и только по возрасту (age >= merge_lock_timeout_s) и только в момент acquire_merge_lease другой задачей. Проактивного освобождения (на старте / периодически) нет; liveness держателя по pid не проверяется (хотя pid в lease пишется).
  • Пострадавшая задача сама re-drive не получает: merge не финализируется → задача висит, lease мешает чужим merge до истечения TTL.

Проблема C — неидемпотентная финализация merge

Если rebase+re-test прошли зелёно (ветка догнана и проверена), но процесс умер до завершения слияния PR — повторного «докатывания» merge нет. Задача застревает в полу-выполненном состоянии, хотя вся дорогая работа (rebase+re-test) уже сделана.

2. Бизнес-последствия

  • Это ПОСЛЕДНЯЯ ручная точка автономного деплоя. Без фикса ни одна self-hosting задача не доезжает до прода без оператора (cancel zombie + ручной merge PR + ручной --deploy).
  • Прямой блокер эпика ORCH-54.
  • Доказанные инциденты (07.06): ORCH-58/60/61/21 — каждый раз после успешного deployer-прохода job оставался running; jobs 236/239/242/254 — зомби, прод-merge/deploy доводились вручную.
  • Групповой риск: зомби в общей очереди при concurrency=1 останавливает конвейер enduro-trails и всех прочих проектов.

3. Цель

Сделать так, чтобы смерть процесса/потока на любой стадии (включая self-restart во время deploy) НЕ оставляла навсегда захваченных ресурсов — ни строки jobs в running, ни merge-lease. Конвейер должен самовосстанавливаться без оператора, при этом сохраняя все инварианты self-hosting (не ронять прод-контейнер, не трогать main, fail-closed на реальных ошибках).

4. Объём (Scope)

В объёме

  1. Job-reaper — фоновый watchdog (паттерн reconciler/queue_worker), детектирующий «мёртвый» running-job и приводящий его строку в корректный терминальный/повторный статус (done/failed/queued) детерминированно, без LLM. Restart-safe и работающий без рестарта процесса.
  2. Проактивный реклейм stale merge-lease — освобождение lease, чей держатель мёртв (pid не жив) ИЛИ возраст превысил TTL — на старте и периодически (reaper/ reconciler), а не только лениво при чужом acquire.
  3. Идемпотентная финализация merge — если rebase+re-test зелёные, но merge не состоялся, операция повторяется/докатывается без потери уже сделанной работы.

Вне объёма

  • Переход на внешний брокер очередей / смену схемы блокировок merge на БД-lock.
  • Полный авто-approve деплоя (ORCH-54) — отдельная задача; здесь только снятие технического блокера.
  • Изменение конвейера стадий (STAGE_TRANSITIONS) и реестра гейтов как контрактов.

5. Заинтересованные стороны

  • Owner оркестратора (self-hosting автономность).
  • Все проекты на общем инстансе (enduro-trails и пр.) — страдают от блокировки общей очереди.

6. Допущения и ограничения

  • max_concurrency=1 для self-hosting сохраняется.
  • Self-hosting safety (CLAUDE.md): нельзя ронять/рестартить прод-контейнер в рамках задачи; нельзя пушить/форс-пушить main; реклейм lease не должен прерывать легитимно работающий merge.
  • Никаких ложных реанимаций: живой, но долгий job не должен помечаться зомби (нужен порог/грейс «N тиков» + проверка реальной смерти, а не просто долготы).
  • Контракт never-raise для всей новой фоновой логики (как у reconciler/merge_gate).
  • Kill-switch на каждый новый механизм (как reconcile_enabled / merge_gate_enabled).

7. Критерий успеха (бизнес-уровень)

После фикса воспроизводимый сценарий «успешный deployer-проход + смерть процесса/ self-restart» НЕ оставляет зомби-job и зажатого lease: задача либо корректно доезжает до done сама, либо откатывается по штатному контракту — без участия оператора. Регресс-тест на jobs-зомби и stale-lease зелёный.