192 lines
18 KiB
Markdown
192 lines
18 KiB
Markdown
---
|
||
work_item: ORCH-090
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-09
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 02 — ТЗ (TRZ): ORCH-090 — Механизм отмены задачи: статус STOP в Plane (остановка + полный сброс)
|
||
|
||
Work Item: **ORCH-090** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
> ТЗ описывает **что** и **где** должно измениться (модули/контракты/артефакты), выведенное из BRD
|
||
> и фактического кода. **Как** (хранилище статуса отмены, точка безопасного прерывания merge/deploy,
|
||
> удаление vs архив ветки, точные точки врезки) — решает архитектор в `06-adr/`. ТЗ фиксирует
|
||
> требования и границы, не предлагает архитектурное решение.
|
||
|
||
---
|
||
|
||
## 1. Сводка изменения
|
||
|
||
Ввести обработку нового Plane-статуса **STOP** как сигнала отмены задачи. При его получении
|
||
оркестратор: (1) останавливает активного агента (graceful SIGTERM через существующий каскад),
|
||
(2) отменяет все job'ы задачи и исчерпывает ретраи, (3) снимает таймеры/мониторы, (4) удаляет/
|
||
архивирует рабочую ветку+worktree и сбрасывает незавершённый прогресс в БД до состояния «отменена»
|
||
(durable), сохраняя docs-артефакты. Параллельно закрывается **дыра релонча**: ручной перевод в
|
||
промежуточный рабочий статус больше не запускает агента — единственный вход к запуску пайплайна
|
||
остаётся «To Analyse» (`start_pipeline`). Всё — аддитивно, под kill-switch, never-raise,
|
||
restart-safe. `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*` и семантика существующих статусов —
|
||
**не меняются**.
|
||
|
||
---
|
||
|
||
## 2. Задействованные модули / пути
|
||
|
||
| Путь | Действие |
|
||
|------|----------|
|
||
| `src/webhooks/plane.py` | изменить: добавить распознавание/маршрутизацию STOP (`handle_issue_updated`) → новый обработчик `handle_stop` (имя — на усмотрение архитектора); **загейтить/убрать релонч агента** в `handle_status_start` (промежуточные статусы не запускают агента; пайплайн — только из `To Analyse`/`start_pipeline`) |
|
||
| `src/agents/launcher.py` | изменить: предоставить/переиспользовать остановку активного процесса задачи (SIGTERM каскад `_watchdog`; `jobs.pid`), пометку «не релончить» (исчерпание `max_attempts`/запрет авто-requeue для отменённой задачи) |
|
||
| `src/queue_worker.py` / `src/db.py` | изменить: отмена job'ов задачи (queued/running → терминальный «cancelled»-исход); claim не выбирает отменённые; helper'ы выборки job'ов задачи; (возможно) новый терминальный статус job `cancelled` ИЛИ переиспользование `failed`+флаг — выбор архитектора; durable-пометка задачи «отменена» в `tasks` |
|
||
| `src/git_worktree.py` | изменить/переиспользовать: удаление/архив рабочей ветки и worktree отменённой задачи (`remove_worktree`; удаление/архив Gitea-ветки) — never-raise |
|
||
| `src/plane_sync.py` | изменить: маппинг Plane-статуса STOP (`_PLANE_NAME_TO_KEY` / `_DEFAULT_STATES`); переиспользовать группу `cancelled` для терминал-скипа; сеттер статуса (best-effort) |
|
||
| `src/stages.py` | при необходимости — терминальная трактовка отменённой задачи (НЕ менять exit-гейты рёбер; добавление `cancelled`-стадии — решение архитектора, см. §5) |
|
||
| `src/reconciler.py` | переиспользовать терминал-скип `done`/`cancelled` (`_is_terminal_state`) — отменённая задача не реконсилируется/не релончится |
|
||
| `src/job_reaper.py` | согласовать: reaper не «оживляет» отменённые job'ы (терминальный исход не requeue'ится) |
|
||
| `src/stage_engine.py` | согласовать: снятие таймеров/мониторов (post-deploy monitor, brd-review clock) и безопасное прерывание merge/deploy при STOP |
|
||
| `src/notifications.py` | переиспользовать `send_telegram`/`update_task_tracker` для алерта/карточки отмены (never-raise, кликабельный номер) |
|
||
| `src/config.py` | изменить: новый kill-switch `stop_status_enabled` (+ при необходимости область репо/доп-флаги) по образцу `serial_gate_enabled` |
|
||
| `src/main.py` | изменить: read-only блок наблюдаемости отмены в `GET /queue` (аддитивно) |
|
||
| `docs/architecture/README.md`, `CLAUDE.md`, `CHANGELOG.md` | обновить в том же PR (golden source) |
|
||
| `tests/` | добавить тесты (см. `04-test-plan.yaml`) |
|
||
|
||
> Чистую логику распознавания/решения по STOP желательно вынести в leaf-модуль (по образцу
|
||
> `src/serial_gate.py` / `src/labels.py`, never-raise) — окончательно решает архитектор.
|
||
|
||
---
|
||
|
||
## 3. Функциональные требования
|
||
|
||
### FR-1 — Распознавание и маршрутизация STOP (BR-1, BR-5)
|
||
- `handle_issue_updated` (`webhooks/plane.py`) распознаёт перевод задачи в логический статус STOP
|
||
(через `_PLANE_NAME_TO_KEY`/группа `cancelled`) и маршрутизирует в обработчик отмены.
|
||
- Обработчик идемпотентен: если задача уже отменена / `done` / отсутствует → no-op (BR-5).
|
||
- Контракт — never-raise: ошибка обработки STOP логируется, вебхук-поток не падает (NFR-5).
|
||
|
||
### FR-2 — Остановка активного агента (BR-1a)
|
||
- Для running-job'а задачи послать активному процессу SIGTERM (graceful) через существующий
|
||
каскад `launcher._watchdog` (SIGTERM → grace `agent_kill_grace_seconds` → SIGKILL); PID берётся
|
||
из `jobs.pid`.
|
||
- Если активного процесса нет (idle/queued) — шаг no-op.
|
||
|
||
### FR-3 — Отмена job'ов и исчерпание ретраев (BR-1b, BR-1c)
|
||
- Все job'ы задачи (`status IN (queued, running)`) переводятся в **терминальный отменённый исход**
|
||
так, что `claim_next_job` их больше не выбирает и `_finalize_*`/reaper не делает авто-requeue.
|
||
- Запрет авто-requeue: после STOP `attempts` считаются исчерпанными (либо отдельный терминальный
|
||
статус job `cancelled`, либо `failed`+маркер — выбор архитектора). Reaper (`job_reaper.py`) и
|
||
`_finalize_permanent` не должны возвращать отменённый job в `queued`.
|
||
|
||
### FR-4 — Снятие таймеров и мониторов (BR-1d)
|
||
- При STOP снимаются/обнуляются связанные с задачей таймеры и фоновые наблюдатели: post-deploy
|
||
monitor (ORCH-021), brd-review clock (ORCH-087), отложенные defer'ы merge-lease/serial-gate.
|
||
- Терминал-скип `done`/`cancelled` (`reconciler._is_terminal_state`, ORCH-068/086) применяется к
|
||
отменённой задаче, чтобы реконсилятор/мониторы её не трогали (NFR-4).
|
||
|
||
### FR-5 — Полный сброс прогресса (BR-2)
|
||
- Рабочая ветка и worktree задачи удаляются/архивируются (`git_worktree.remove_worktree` + удаление/
|
||
архив Gitea-ветки; never-raise). `main` не трогается, force-push в `main` запрещён.
|
||
- Незавершённый прогресс задачи в БД приводится к durable-состоянию «отменена» так, что повторный
|
||
запуск возможен ТОЛЬКО через `start_pipeline` с нуля (новая ветка от свежего `origin/main`, новый
|
||
analyst). Конкретика «очистить строку vs пометить cancelled» — архитектору; инвариант:
|
||
возобновления «с середины» не происходит.
|
||
- **Docs-артефакты задачи (`01..17`) сохраняются/бэкапятся** — не удаляются вместе с прогрессом.
|
||
|
||
### FR-6 — Закрытие дыры релонча (BR-3, BR-4)
|
||
- `handle_status_start` (или эквивалентная точка) **не должен релончить агента текущей стадии** при
|
||
ручном переводе в промежуточный рабочий статус (Architecture/Development/Review/Testing/
|
||
Deploying/Awaiting Deploy/Monitoring/…).
|
||
- Запуск пайплайна остаётся возможен **только** через статус «To Analyse» → `start_pipeline`
|
||
(создание ветки + docs + enqueue analyst). Любой намеренный сценарий «вернуть задачу в работу»
|
||
(например, после Needs Input) должен быть пересмотрен так, чтобы НЕ опираться на авто-релонч
|
||
агента сменой рабочего статуса (точный заменяющий механизм — архитектору).
|
||
|
||
### FR-7 — Безопасное прерывание критичных операций (BR-6, NFR-3)
|
||
- STOP во время merge/deploy не оставляет `main` в half-merged состоянии и не рестартит/не роняет
|
||
прод-контейнер. Если необратимый шаг (detached self-deploy / слияние PR) уже запущен — STOP не
|
||
«разрывает» его с порчей: допускается дать необратимому шагу завершиться/зафиксировать честный
|
||
исход, после чего применить отмену. Точка безопасного прерывания и обработка merge-lease — ADR.
|
||
|
||
### FR-8 — Наблюдаемость (BR-8)
|
||
- Каждое срабатывание STOP: `logger.info/warning` (что остановлено/сброшено), Telegram-алерт
|
||
(`send_telegram`, кликабельный номер `plane_issue_link`), Plane-коммент (best-effort), обновление
|
||
live-карточки (`update_task_tracker`, never-raise), read-only блок отмены в `GET /queue`.
|
||
|
||
---
|
||
|
||
## 4. Изменения API
|
||
|
||
- **Новых обязательных публичных endpoint'ов нет.** Триггер STOP — смена статуса Plane (webhook),
|
||
не REST. (По аналогии с ORCH-088 возможен опциональный админ-эндпоинт принудительной отмены —
|
||
на усмотрение архитектора; если вводится, описать в ADR и таблице API README.)
|
||
- `GET /queue` — **аддитивно**: новый read-only блок (например `stop`/`cancel`) — флаг `enabled`,
|
||
счётчик отменённых задач/job'ов, последние отмены. Существующие ключи не меняются; never-raise.
|
||
- Внешний контракт вебхука `POST /webhook/plane` — не меняется (новая ветка обработки статуса
|
||
внутри `handle_issue_updated`).
|
||
|
||
---
|
||
|
||
## 5. Изменения схемы БД
|
||
|
||
> Только **аддитивные, идемпотентные** миграции (общая прод-БД; enduro не трогать).
|
||
> `CREATE TABLE IF NOT EXISTS` / `_ensure_column`.
|
||
|
||
- **Статус job «отменён» (FR-3):** требуется терминальный исход, который не requeue'ится. Варианты
|
||
(выбор — архитектор): новый статус `jobs.status='cancelled'` ИЛИ переиспользование `failed` +
|
||
аддитивный маркер. Требование к выбранному варианту: claim/finalize/reaper не возвращают его в
|
||
`queued`; restart-safe.
|
||
- **Состояние задачи «отменена» (FR-5, NFR-4):** durable-признак, что задача отменена и не
|
||
возобновляется с середины. Варианты: добавление терминальной стадии `cancelled` в `tasks.stage`
|
||
(учитывается терминал-скипом `done`/`cancelled`, уже поддержан reconciler'ом) ИЛИ аддитивная
|
||
колонка/таблица. `STAGE_TRANSITIONS` (exit-гейты рёбер) при этом **не меняются** — отмена это
|
||
терминальное состояние, не новое ребро конвейера.
|
||
- `QG_CHECKS`, `check_*`, `job_deps`, `agent_runs`-контракт, `repo_freeze` — **без изменений**.
|
||
|
||
---
|
||
|
||
## 6. Требования к новым/изменённым QG checks
|
||
|
||
- **Новых QG-проверок не вводить.** STOP — это решение диспетчера статусов/планировщика (отмена),
|
||
а не Quality Gate стадии. Реестр `QG_CHECKS` и `check_*` не меняются (по образцу `task_deps`
|
||
ORCH-026 и `serial_gate` ORCH-088 — логика в обработчике/claim, не новый QG).
|
||
|
||
---
|
||
|
||
## 7. Совместимость / регресс
|
||
|
||
- **Kill-switch:** новый флаг `stop_status_enabled` (env `ORCH_STOP_STATUS_ENABLED`) по образцу
|
||
`serial_gate_enabled`; `False` → STOP-обработка и закрытие дыры релонча ведут себя нейтрально
|
||
(поведение строго как сейчас, нулевая регрессия). При необходимости — область репо
|
||
(`stop_status_repos`, CSV) с дефолтом «все репо» (отмена осмысленна и для enduro).
|
||
- **Аддитивность БД (NFR-2):** только идемпотентные миграции; enduro при выключенном/неприменимом
|
||
флаге не затрагивается.
|
||
- **Инварианты (не нарушать):** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, exit-коды
|
||
deploy-хука, merge-gate (ORCH-043), merge-verify (ORCH-071/073), image-freshness (ORCH-058),
|
||
post-deploy контракт (ORCH-021), serial-gate (ORCH-088), auto-label (ORCH-089), семантика
|
||
Rejected/Approved/Confirm Deploy — **без изменений**.
|
||
- **Self-hosting safety (NFR-3):** STOP не рестартит/не роняет прод-контейнер; не push/force-push в
|
||
`main`; merge/deploy прерываются fail-safe (без half-merge).
|
||
- **never-raise (NFR-5):** обработчик STOP и закрытие релонча не валят вебхук-поток; ошибка на
|
||
единице работы изолирована.
|
||
- **Артефакты pipeline (создать/обновить в том же PR):** `docs/work-items/ORCH-090/06-adr/ADR-001-…`
|
||
(решение архитектора), `docs/architecture/README.md` (раздел «STOP / отмена задачи (ORCH-090)»,
|
||
обновление описания `GET /queue`, раздела статусной модели и при новой таблице/колонке — раздела
|
||
«База данных»), `CLAUDE.md` (абзац о STOP в статусной модели), `CHANGELOG.md` (`feat:`); при новой
|
||
таблице/колонке — `docs/work-items/ORCH-090/08-data-requirements.md`; при админ-эндпоинте — таблица
|
||
API в README.
|
||
|
||
---
|
||
|
||
## 8. Открытые вопросы для архитектора (не блокируют анализ)
|
||
|
||
- OQ-1: Имя Plane-статуса — отдельный «STOP» (новый key) vs переиспользование существующего
|
||
«Cancelled» (key `cancelled` уже в `_PLANE_NAME_TO_KEY`). Влияет на маппинг и группу терминал-скипа.
|
||
- OQ-2: Статус отменённого job — новый `cancelled` vs `failed`+маркер.
|
||
- OQ-3: Состояние отменённой задачи — терминальная стадия `cancelled` vs аддитивная колонка/таблица.
|
||
- OQ-4: Сброс прогресса — удалить строку task (полный re-create через To Analyse) vs пометить
|
||
cancelled и при To Analyse создавать новую задачу.
|
||
- OQ-5: Удаление vs архив рабочей ветки (и Gitea-ветки) — что безопаснее для аудита.
|
||
- OQ-6: Точка безопасного прерывания merge/deploy (FR-7) и обработка удерживаемого merge-lease.
|
||
- OQ-7: Чем заменить легитимный «resume после Needs Input», который сейчас опирается на релонч в
|
||
`handle_status_start` (FR-6), чтобы не сломать намеренный сценарий возврата к работе.
|