architect(ET): auto-commit from architect run_id=497
All checks were successful
CI / test (push) Successful in 30s

This commit is contained in:
2026-06-09 20:37:21 +03:00
parent 610619edc2
commit 269cbde3e3
7 changed files with 609 additions and 1 deletions

View File

@@ -278,6 +278,45 @@ Phase A ждёт ручного `Confirm Deploy`, ORCH-059). ORCH-089 снима
`docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`,
`docs/work-items/ORCH-089/07-infra-requirements.md`.
### STOP / отмена задачи: терминал `cancelled` + закрытие дыры релонча (ORCH-090 — design)
До ORCH-090 не было штатного способа отменить задачу (ручная хирургия по БД/процессам) и
существовала **дыра релонча**: `handle_status_start` при существующей задаче без активного job
безусловно релончил агента текущей стадии на той же ветке. ORCH-090 вводит Plane-статус **STOP**
как единый декларативный сигнал отмены: остановка агента + **полный сброс** прогресса. Аддитивно,
под kill-switch, never-raise, restart-safe; `STAGE_TRANSITIONS` (exit-гейты) / `QG_CHECKS` /
`check_*`**без изменений**.
- **Новое системное терминальное состояние `cancelled`** (adr-0026) — `tasks.stage='cancelled'` +
`jobs.status='cancelled'`, равноправное `done`. Предикат «задача незавершена» расширяется
`stage != 'done'``stage NOT IN ('done','cancelled')` в `serial_gate` (ORCH-088) и `task_deps`
(ORCH-026), приводя их в соответствие с уже существующим терминал-скипом реконсилятора
(`stage in ("done","cancelled")`, ORCH-086 D2). Иначе отменённая задача заклинила бы очередь репо.
- **Распознавание (fail-closed):** новый ключ `stop` в `_PLANE_NAME_TO_KEY` (`"STOP" → "stop"`);
**не** в `_DEFAULT_STATES` (по образцу `confirm_deploy`/ORCH-059) → нет статуса = нет отмены, без
`KeyError`. `handle_issue_updated` маршрутизирует `stop` → новый `handle_stop`
`stage_engine.cancel_task`.
- **Каскад отмены:** graceful SIGTERM активному агенту (переиспользование каскада
`launcher._watchdog` по `jobs.pid`); `cancel_jobs_for_task` (queued/running → `cancelled`,
не реквью'ятся); снятие таймеров/мониторов (brd-clock, post-deploy monitor, defer'ы);
`remove_worktree` + never-raise удаление **только feature-ветки** Gitea (`main` неприкосновенен,
без force-push); **тумбстон** `plane_id`/`work_item_id` (`#cancelled-<id>`) → повторный
«To Analyse» создаёт задачу с нуля; docs-артефакты (`01..17`) сохраняются.
- **Безопасное прерывание merge/deploy:** STOP в критическом окне (self-deploy `INITIATED`-sentinel
ORCH-036, держание merge-lease ORCH-043/071) → **отложенная отмена** (durable
`cancel_requested_at`, отмена только `queued`-job'ов, алерт); необратимый шаг доводится до
честного исхода; `main`/прод-контейнер не трогаются (NFR-3).
- **Закрытие дыры релонча:** relaunch в `handle_status_start` ограничен стадией `analysis`
(единственный владелец Needs-Input, ORCH-066) — тихий релонч середины пайплайна на старой ветке
устранён; единственный вход к запуску — «To Analyse» (`start_pipeline`).
- **Флаги/наблюдаемость:** kill-switch `stop_status_enabled` + `stop_status_repos` (CSV, пусто →
все репо); leaf `src/cancel.py` (never-raise); read-only блок `stop` в `GET /queue`; лог +
Telegram (кликабельный номер) + Plane-коммент + live-карточка. При выключенном флаге — нулевая
регрессия (enduro не затронут).
Подробнее: [adr-0026](adr/adr-0026-stop-cancel-task.md), детально —
`docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`,
`docs/work-items/ORCH-090/08-data-requirements.md`.
### Исполняемый самодеплой стадии `deploy` (ORCH-36)
`deploy` перестаёт быть «бумажной»: для self-hosting (`is_self_hosting_repo`) стадия
РЕАЛЬНО деплоит прод (8500) через хост-хук `scripts/orchestrator-deploy-hook.sh`,

View File

@@ -0,0 +1,106 @@
---
work_item: ORCH-090
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---
# ADR-0026: Системное терминальное состояние `cancelled` — STOP-отмена задачи
Сквозной (cross-cutting) ADR. Детальное решение задачи —
`docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`.
## Статус
Proposed
## Контекст
ORCH-090 вводит Plane-статус **STOP** — единый декларативный механизм отмены задачи (остановка
агента + полный сброс прогресса). Самое́ кросс-каттинговое следствие — появление **нового
системного терминального состояния `cancelled`** (стадия `tasks.stage='cancelled'` + терминальный
job-статус `jobs.status='cancelled'`). До ORCH-090 «терминальность задачи» в горячем планировщике
была захардкожена как **`stage == 'done'`** (единственный сток в `STAGE_TRANSITIONS`), и это
определение разъехалось между подсистемами:
- `src/reconciler.py` **уже** трактует `stage in ("done","cancelled")` как терминал-скип
(ORCH-086 D2 предвосхитил `cancelled`; стр. 196) и `_is_terminal_state` по группе Plane
`{completed, cancelled}` (ORCH-068, стр. 398415).
- `src/serial_gate.py` (ORCH-088) и `src/task_deps.py` (ORCH-026) считают задачу «незавершённой»
по `stage != 'done'`**без** `cancelled`. Если ввести `cancelled`-стадию, не тронув их,
отменённая задача навсегда будет «активной»/«незавершённой зависимостью» и **заклинит очередь
репо**.
Этот ADR фиксирует `cancelled` как первоклассное терминальное состояние, равноправное `done`, и
перечисляет ВСЕ точки, где системный предикат терминальности должен его признавать.
## Решение
### Инвариант
**«Задача терминальна» ⇔ `stage ∈ {done, cancelled}`.** Это единое определение для всех
подсистем планировщика/мониторинга. `cancelled` — терминальный **сток** (не новое ребро
конвейера): exit-гейты рёбер `STAGE_TRANSITIONS` и реестр `QG_CHECKS`/`check_*` **не меняются**.
### Точки, признающие `cancelled` терминальным (исчерпывающе)
1. `src/stages.py::STAGE_TRANSITIONS` — добавить сток
`"cancelled": {"next": None, "agent": None, "qg": None}` (параллельно `done`).
2. `src/serial_gate.py``repo_has_other_unfinished` и claim-фрагмент `t2.stage != 'done'`,
snapshot: `stage != 'done'``stage NOT IN ('done','cancelled')`. **(маркер ORCH-088)**
3. `src/task_deps.py` — dep-gate и `is_task_ready`: `stage != 'done'`
`stage NOT IN ('done','cancelled')`. **(маркер ORCH-026)**
4. `src/reconciler.py` — уже покрыто скипом `stage in ("done","cancelled")` (стр. 196);
`get_active_tasks_for_reconcile` опционально сузить до `NOT IN ('done','cancelled')`.
5. `src/job_reaper.py` / `src/queue_worker.py` — перед авто-requeue dead/running-job'а сверять
терминал задачи: `stage in ("done","cancelled")` → job помечается `cancelled`, не реквью'ится.
6. `src/post_deploy.py` / `stage_engine.run_post_deploy_monitor` — монитор не тикает по
отменённой задаче (терминал-проверка/маркер `done`).
### Новые терминальные исходы
- **Job:** `jobs.status='cancelled'` — нигде не реквью'ится; `claim_next_job` выбирает только
`status='queued'` (изменений в claim нет). `mark_job` стампит `finished_at` для `cancelled`.
- **Задача:** `tasks.stage='cancelled'` + аддитивные колонки `cancelled_at`,
`cancel_requested_at` (отложенная отмена в критическом окне merge/deploy). Натуральные ключи
`plane_id`/`work_item_id` тумбстонятся (`#cancelled-<id>`) для переиспользования «To Analyse»
с нуля; `plane_issue_id` сохраняется (аудит). Детали — 08-data-requirements.md.
### Точки врезки STOP (компоненты)
- `plane.py` — маршрут `stop` (fail-closed, не в `_DEFAULT_STATES`) → `handle_stop`; гейт релонча
ограничен стадией `analysis`.
- `stage_engine.cancel_task` — оркестрация отмены (graceful SIGTERM, cancel-jobs, worktree+branch,
tombstone, notify); безопасное прерывание merge/deploy (D7 локального ADR).
- leaf `src/cancel.py` — чистая логика (`applies`/`in_critical_window`/`snapshot`), never-raise.
- `src/gitea.py``delete_remote_branch` (never-raise; только feature-ветка, `main` неприкосновенен).
- `GET /queue` — read-only блок `stop`.
### Флаги / совместимость
- Kill-switch `stop_status_enabled` + scope `stop_status_repos` (CSV, пусто → все репо).
- При `stop_status_enabled=False`: STOP-обработка и гейт релонча инертны; расширение
терминал-набора `cancelled` безвредно при отсутствии отменённых задач → **нулевая регрессия**.
- `STAGE_TRANSITIONS` (exit-гейты) / `QG_CHECKS` / `check_*` / семантика
Approved/Rejected/Confirm Deploy / merge-gate (ORCH-043) / merge-verify (ORCH-071/073) /
image-freshness (ORCH-058) / post-deploy (ORCH-021) / serial-gate FIFO (ORCH-088) / auto-label
(ORCH-089) — **без изменений**.
- Миграции БД — только аддитивные/идемпотентные (`_ensure_column`); enduro не затронут (NFR-2).
## Последствия
- **+** Единое, консистентное определение терминальности — устранён латентный рассинхрон
`done`-only между планировщиком и реконсилятором.
- **+** STOP безопасен для self-hosting: не трогает `main`/прод, отложенная отмена в критическом
окне.
- **** Терминальность теперь читается из набора `{done, cancelled}`, а не из скаляра `'done'`
будущие подсистемы обязаны использовать набор. Митигейшн: этот ADR + маркер `ORCH-090` в
изменённых местах + тесты.
- **Откат:** `stop_status_enabled=False`; полный revert — снять врезки и вернуть предикаты к
`stage != 'done'`.
## Эволюция маркеров `cancelled`-терминала
Места, признающие `cancelled` терминальным (см. список выше), несут маркер `ORCH-090`. Правка
любого из них — сверяться с этим ADR (анти-археология: 3+ маркеров → одна ссылка сюда,
TRACEABILITY.md).
## Ссылки
- Детальный ADR: `docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`
- Data: `docs/work-items/ORCH-090/08-data-requirements.md`
- Связанные: adr-0017 (serial-gate), adr-0015 (task-deps), adr-0007 (self-deploy),
adr-0006 (merge-gate), adr-0018 (auto-label)

View File

@@ -61,9 +61,15 @@ STAGE_TRANSITIONS = {
testing: → deploy-staging (agent: deployer, QG: check_tests_passed)
deploy-staging: → deploy (agent: deployer, QG: check_staging_status)
deploy: → done (agent: None, QG: None)
cancelled: → None (agent: None, QG: None) # ORCH-090: терминал-сток отмены
}
```
**Терминальные стоки (ORCH-090):** `done` и `cancelled` — равноправные терминальные состояния
(`{"next": None, "agent": None, "qg": None}`). `cancelled` — это **не новое ребро** (exit-гейты
рёбер не меняются), а терминал STOP-отмены. Системный предикат «задача завершена» —
`stage ∈ {done, cancelled}` (синхронно в `reconciler`/`serial_gate`/`task_deps`; adr-0026).
### 3. Quality Gates (`src/qg/checks.py`)
| Check | Метод проверки |
@@ -329,7 +335,7 @@ webhook (plane/gitea) background thread (queue_worker)
| Колонка | Назначение |
|--------|------------|
| `status` | `queued``running``done` \| `failed` |
| `status` | `queued``running``done` \| `failed` \| `cancelled` (ORCH-090: терминальный исход STOP-отмены, не реквью'ится) |
| `attempts` / `max_attempts` | счётчик попыток (инкремент при claim) / лимит ретраев (default 2) |
| `run_id` | FK на `agent_runs.id` после старта |
| `pid` | (ORCH-065) pid агентского процесса (`proc.pid` из `_spawn`); liveness-сигнал для job-reaper. Добавляется `_ensure_column` (idempotent) |