architect(ET): auto-commit from architect run_id=778
All checks were successful
CI / test (push) Successful in 1m13s

This commit is contained in:
2026-06-17 12:55:28 +03:00
parent 432da2c4ed
commit 1fcbe06df5
4 changed files with 397 additions and 0 deletions

View File

@@ -650,6 +650,32 @@ ORCH-027 вводит детерминированный (без LLM) **гейт
`docs/work-items/ORCH-088/08-data-requirements.md`,
`docs/work-items/ORCH-124/06-adr/ADR-001-serial-gate-pause-without-blocking.md`.
### Открытые вопросы аналитика → Needs Input (ORCH-120 — design, [adr-0053](adr/adr-0053-analyst-open-questions-needs-input-flow.md))
При неоднозначном бизнес-запросе у аналитика не было рабочего канала уточнения — он **фабриковал**
требования, чтобы сдать обязательные 4 файла. Механизм «вопросы → Needs Input» в
`_handle_analysis_approved_flow` (`src/stage_engine.py`) существовал, но был **мёртв** (контракт не в
промпте; ветка `files_ok` имела приоритет; Needs Input клинил serial-gate; нет гигиены устаревшего
`01-questions.md`). ORCH-120 **активирует и достраивает** путь — аддитивно, под kill-switch, скоуп
self-hosting, never-raise; `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict/схема БД —
**байт-в-байт не тронуты** (поток — pre-gate-ветка движка, **не** QG; `01-questions.md` — сигнальный
артефакт, **не** machine-verdict).
- **Контракт + канон.** `.openclaw/agents/analyst.md` документирует «блокирующие вопросы →
`01-questions.md`, НЕ фабриковать deliverables»; `01-questions.md` стандартизирован как
`when-applicable` сигнальный артефакт (скелет `docs/_templates/` + строка `PIPELINE_DOCS.md`).
- **Приоритет «вопросы активны» > «файлы готовы»** в `_handle_analysis_approved_flow` (DQ-3) →
блокирующие вопросы достигают Needs Input даже при частичных/сфабрикованных deliverables.
- **Авто-park (DQ-1)** через ось «пауза» ORCH-124 (`db.set_task_paused` при Needs Input) → задача
исключается из «активного» предиката serial-gate, FIFO репо не клинит, пока ждём человека;
**resume + unpark** в `handle_status_start` (analysis-ветка, `clear_task_paused`).
- **Устаревание (DQ-2)** — детерминированный offline freshness-supersede по mtime (вопросы активны,
пока пакет неполон ИЛИ `01-questions.md` не старше всех 4 deliverables) → полный свежий пакет
supersedeит старый файл без зависимости от LLM.
- **Флаги** (`config.py`): `analyst_questions_gate_enabled` (kill-switch) / `analyst_questions_gate_repos`
(CSV; **пусто → self-hosting only**) / `analyst_needs_input_autopause_enabled` (независимый тумблер
авто-park/unpark; `False` → operator-park `POST /serial-gate/pause`). off/out-of-scope → байт-в-байт
как до ORCH-120 (enduro не затронут). ORCH-066 (Needs Input только у аналитика) не расширяется.
Детали — `docs/work-items/ORCH-120/06-adr/ADR-001-analyst-open-questions-needs-input.md`.
### Авто-режим по лейблам: autoApprove + autoDeploy (ORCH-089 — реализовано)
Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон (эпик
ORCH-088): гейт BRD (`analysis`: ждёт ручного `Approved`) и гейт прод-деплоя (`deploy`:

View File

@@ -0,0 +1,82 @@
---
work_item: ORCH-120
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# ADR-0053: Поток «открытые вопросы аналитика → Needs Input» (приоритет + пауза + resume)
Сквозной (cross-cutting) ADR. Детальное решение задачи —
`docs/work-items/ORCH-120/06-adr/ADR-001-analyst-open-questions-needs-input.md`.
Статус: **Proposed** · Дата: 2026-06-17 · Источник: **ORCH-120** (bug → escalate full-cycle)
## Контекст
Конвейер обязывает аналитика выпустить 4 файла (`01-brd`/`02-trz`/`03-acceptance-criteria`/
`04-test-plan.yaml`), иначе exit-гейт `analysis` не пройдёт. При неоднозначном бизнес-запросе
(классика — `Description: TBD`) у аналитика нет рабочего канала уточнения → он **фабрикует**
требования. Механизм «вопросы → Needs Input» в `_handle_analysis_approved_flow`
(`src/stage_engine.py`) **существует, но мёртв** из-за четырёх смежных дефектов: контракт не
доведён до промпта; ветка `files_ok` имеет приоритет над веткой вопросов; Needs Input клинит
serial-gate репо (ORCH-088); нет гигиены устаревшего `01-questions.md`.
Поток пересекает несколько подсистем, поэтому фиксируется сквозным ADR (анти-археология ORCH-078:
блок `_handle_analysis_approved_flow` несёт 3+ маркера — ORCH-066/088/089/124):
- **ORCH-066** — Needs Input принадлежит **только** аналитику (слой B индикации ≠ слой A стадий).
- **ORCH-088** — per-repo serial-gate: «активная задача» по `tasks.stage NOT IN ('done','cancelled')`.
- **ORCH-124** (adr-0051) — ортогональная ось «пауза» (`tasks.paused_at`): исключает задачу из
«активного» предиката, **не** обходя оси `task_deps`/`repo_freeze`/терминал.
- **ORCH-089** — autoApprove (человеческий BRD-гейт по лейблу) в той же ветке `files_ok`.
## Решение
**Активировать мёртвый путь четырьмя согласованными изменениями** — аддитивно, под kill-switch,
скоуп self-hosting, never-raise:
1. **Контракт промпта + канон артефакта.** `.openclaw/agents/analyst.md` документирует канал
«блокирующие вопросы → `01-questions.md`, НЕ фабриковать deliverables»; `01-questions.md`
стандартизирован как **сигнальный** when-applicable артефакт (скелет `docs/_templates/` +
строка манифеста `PIPELINE_DOCS.md`) — **не** machine-verdict (гейтом не парсится, BR-6).
2. **Приоритет «вопросы активны» > «файлы готовы».** В `_handle_analysis_approved_flow` предикат
активных вопросов проверяется **до** ветки `files_ok` → блокирующие вопросы надёжно достигают
Needs Input даже при частичных/сфабрикованных deliverables.
3. **Авто-park через ось «пауза» ORCH-124.** Переход в Needs Input вызывает `db.set_task_paused`
→ задача исключается из «активного» предиката serial-gate → следующая задача репо входит в
`analysis`, пока первая ждёт человека (не клинит FIFO неопределённо долго).
4. **Resume + unpark.** `handle_status_start` (analysis-resume) снимает паузу (`clear_task_paused`)
и перезапускает аналитика; relaunch-guard ORCH-090 (только `analysis`) не ослаблен.
**Устаревание `01-questions.md` (детерминированно, offline):** freshness-gated supersede по mtime —
вопросы «активны», пока пакет неполон ИЛИ `01-questions.md` не старше всех 4 deliverables; полный
свежий пакет supersedeит старый файл (выбор механизма и отвергнутые альтернативы — ADR-001 DQ-2).
## Инварианты (нормативно)
- **Поток — pre-gate-ветка движка, НЕ Quality Gate.** `STAGE_TRANSITIONS` / реестр и имена
`QG_CHECKS` / семантика `check_analysis_complete`/`check_analysis_approved` / machine-verdict-ключи
/ схемы существующих таблиц — **байт-в-байт не тронуты**.
- **Без схемы БД:** переиспользуется `tasks.paused_at` (ORCH-124); новых таблиц/колонок нет.
- **ORCH-066 не расширяется:** Needs Input остаётся **только** у аналитика.
- **ORCH-124 не регрессирует:** пауза ортогональна — оси `task_deps`/`repo_freeze`/терминал
`{done,cancelled}` `paused_at` **не читают**; анти-stale-base ORCH-088 цел (нормальная задача
`paused_at IS NULL` держит гейт; свежесть базы на resume — существующими механизмами).
- **Self-hosting-безопасность:** поток только меняет Plane-статус/паузу/коммент и читает worktree —
не деплоит, не рестартит прод-контейнер, не пушит в `main`, не трогает detached-процессы.
- **never-raise / обратимость:** все врезки изолированы и деградируют к прежнему поведению;
3 флага (`analyst_questions_gate_enabled` / `analyst_questions_gate_repos` /
`analyst_needs_input_autopause_enabled`) с безопасными дефолтами → off/out-of-scope = байт-в-байт
как до ORCH-120 (enduro не затронут).
## Последствия
Конвейер перестаёт строить решения поверх домыслов; serial-gate не клинит на задаче, ждущей
человека (поддержка автономного пакетного прогона ORCH-088); аналитик получает легитимный канал
уточнения. Цена — узкое связывание индикации с осью планировщика при авто-park (смягчено флагом +
узким триггером + never-raise) и зависимость supersede от mtime (смягчено: полный прогон всегда
пишет свежие deliverables + контракт промпта). Детали, альтернативы и риски —
`docs/work-items/ORCH-120/06-adr/ADR-001-analyst-open-questions-needs-input.md`,
`docs/work-items/ORCH-120/10-tech-risks.md`.

View File

@@ -0,0 +1,248 @@
---
work_item: ORCH-120
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# ADR-001: Открытые вопросы аналитика → Needs Input (приоритет, неблокирование serial-gate, resume)
Work Item: **ORCH-120** · Repo: **orchestrator** (self-hosting) · Стадия: architecture
Связь: BRD `01-brd.md`, ТЗ `02-trz.md`, AC `03-acceptance-criteria.md`, план тестов `04-test-plan.yaml`, риски `10-tech-risks.md`.
Сквозная регистрация: `docs/architecture/adr/adr-0053-analyst-open-questions-needs-input-flow.md`.
## Статус
Proposed
---
## Контекст
Задача — баг (`BUG:` в заголовке), **эскалированный в полный цикл** (`escalate: full-cycle`,
ADR-001 D5 ORCH-019): фикс требует архитектурных решений, поэтому выпущен полный analysis-пакет и
задача идёт через стадию `architecture`. Аналитик передал 4 открытых проектных вопроса (BRD §6:
DQ-1…DQ-4), которые и решаются этим ADR.
**Корень (верифицировано в коде).** Механизм «аналитик задаёт блокирующие вопросы → задача в
Needs Input» в движке **уже есть, но мёртв**: `src/stage_engine.py::_handle_analysis_approved_flow`
(ветка `01-questions.md`, стр. 769786) читает файл и вызывает `set_issue_needs_input(...)` +
коммент в Plane + Telegram. Четыре смежных дефекта делают его нерабочим:
1. **Контракт не доведён до аналитика.** `.openclaw/agents/analyst.md` нигде не упоминает
`01-questions.md` (ни в `<task>`, ни в `<deliverables>`); скелета `docs/_templates/01-questions.md`
нет; в `docs/_standards/PIPELINE_DOCS.md` артефакт не описан. Аналитик не знает о канале и
**домысливает** требования, чтобы сдать обязательные 4 файла.
2. **Ошибка приоритета веток.** Ветка `files_ok` (`check_analysis_complete` — все 4 файла на месте,
стр. 711767) проверяется **первой** и делает `return`; ветка `01-questions.md` (стр. 769)
достижима только если 4 файла НЕ полны. Сфабрикованный полный пакет уходит в `In Review`,
игнорируя блокирующие вопросы.
3. **Needs Input клинит serial-gate репо.** Задача в Needs Input остаётся в `stage='analysis'`
(Plane-статус — слой B индикации, ORCH-066, **не** меняет `tasks.stage`) и `paused_at IS NULL`.
По правилу ORCH-088 такая «активная» задача держит FIFO репо закрытым до ответа человека
(часы/дни) — ни одна следующая задача `orchestrator` не входит в `analysis`.
4. **Нет гигиены устаревшего `01-questions.md`.** После ответа заказчика `handle_status_start`
(`src/webhooks/plane.py:261`, analysis-resume) перезапускает аналитика; если тот выпускает
полный пакет, старый `01-questions.md` остаётся в ветке. Без правила supersede он либо
игнорируется, либо вечно роняет задачу в Needs Input.
**Что НЕ нужно строить с нуля** (BRD §6 «Допущение»): `set_issue_needs_input`, чтение файла,
ось «пауза» (`tasks.paused_at` + `db.set_task_paused`/`clear_task_paused`/`is_task_paused`,
ORCH-124), эндпоинты `POST /serial-gate/pause|resume`**уже существуют**. ORCH-120 **активирует
и достраивает** путь, а не изобретает его.
**Предусловия выполнены вне объёма:** ORCH-124 (ось «пауза без блокировки» — фундамент BR-3),
ORCH-126 (queued-job не застревает со stale `run_id`/`pid` — гарантирует claim перезапущенного
analyst-job). Plane-статус **Needs Input** существует (ключ `needs_input` в `_DEFAULT_STATES`).
---
## Решение (сводка)
Активировать мёртвый путь четырьмя согласованными изменениями, **аддитивно, под kill-switch,
скоуп self-hosting, never-raise**: (D1) контракт промпта + (D2) канон артефакта `01-questions.md`;
(D3) **приоритет** ветки вопросов над `files_ok` с детерминированным supersede; (D4) **авто-park**
при Needs Input через ось «пауза» ORCH-124; (D5) **resume + unpark** в `handle_status_start`.
`STAGE_TRANSITIONS` / реестр и имена `QG_CHECKS` / семантика `check_analysis_complete` /
`check_analysis_approved` / machine-verdict-ключи / схемы существующих таблиц — **байт-в-байт не
тронуты** (поток — pre-gate-ветка движка, **не** Quality Gate; артефакт — сигнальный, **не**
machine-verdict). Это решение DQ-1…DQ-4.
---
## Решения по открытым вопросам (DQ-1…DQ-4)
### DQ-1 — авто-park vs operator-park при Needs Input → **авто-park, config-gated**
**Решение.** В момент перехода в Needs Input движок вызывает `db.set_task_paused(task_id)`
**автоматически** (под под-флагом `analyst_needs_input_autopause_enabled`, скоуп self-hosting),
а на resume — `db.clear_task_paused(task_id)` (D5). Operator-park (`POST /serial-gate/pause`)
сохраняется как ручной fallback, но **не** обязателен для BR-3.
**Обоснование.** BR-3 и весь эпик ORCH-088 («1020 задач за ночь», автономный пакетный прогон)
требуют, чтобы Needs Input **не** клинил очередь. Operator-park вводит человеческий шаг в каждую
итерацию — это противоречит цели автономности: пока оператор не нажал pause, FIFO репо закрыт.
Авто-park снимает блокировку детерминированно и без человека.
**Снятие риска связывания индикации (слой B) с осью планировщика** (явный риск из BRD §6 DQ-1):
- Триггер узкий и детерминированный — **только** переход аналитика в Needs Input в
`_handle_analysis_approved_flow`, а не общее правило «статус → пауза».
- Отдельный под-флаг `analyst_needs_input_autopause_enabled` (независимый от questions-gate),
дефолт `True` в self-hosting-области → откат до operator-park одним флагом.
- never-raise: `set_task_paused` уже возвращает `False` на ошибке; сбой park **не** отменяет
переход в Needs Input (деградация к operator-park) и не роняет `advance_stage`.
- Симметричный unpark на resume (D5) исключает «застрявшую паузу».
**Инвариант ORCH-124 не нарушен:** `paused_at` исключает задачу только из оси «активная задача»
serial-gate; оси `task_deps`/`repo_freeze`/терминал `{done,cancelled}` `paused_at` **не читают**
пауза их не обходит (`src/serial_gate.py:33` нормативно).
### DQ-2 — устаревание `01-questions.md` (BR-4) → **freshness-gated supersede (mtime), детерминированно и offline**
**Решение.** Ввести чистый предикат `_questions_active(worktree_path, work_item_id, files_ok) -> bool`
в `stage_engine` (или leaf-хелпер), детерминированный и offline (только filesystem, без сети/git):
- `01-questions.md` отсутствует → **не активны** (`False`).
- Пакет **неполон** (`files_ok == False`) и файл присутствует → **активны** (`True`): вопросы есть,
deliverables нет — приоритет вопросов (AC-2).
- Пакет **полон** (`files_ok == True`) и файл присутствует → сверка свежести:
**superseded ⇔ все 4 deliverables строго новее `01-questions.md`** (по `os.path.getmtime`).
`superseded == True`**не активны** (`False`, → In Review, AC-6); иначе (вопросы не старше
новейшего deliverable) → **активны** (`True`, → Needs Input, AC-1).
**Почему mtime-freshness, а не альтернативы.** Кандидаты и причины отказа:
- **Удаление файла аналитиком** — у аналитика нет Delete-tool (только Write `docs/...`); полагаться
на «забыл удалить» нельзя; AC-6 явно допускает «файл **мог остаться** нетронутым».
- **Контент-маркер `status: resolved`** — требует, чтобы аналитик **переписал** файл; при
«файл остался нетронутым» (AC-6) маркера нет → ложный Needs Input. Плюс вводит парсинг
машинного ключа в сигнальный артефакт (трение с BR-6).
- **Git-recency коммитов** — надёжно в проде, но юнит-тесты (`04-test-plan.yaml`: временный worktree
с plain-файлами, паттерн `test_auto_approve_brd.py`) **не коммитят** → нетестируемо/хрупко.
- **mtime-freshness** — единственный механизм, который (а) тестируем на plain-файлах (тест задаёт
порядок записи/mtime), (б) offline и без нового парсинга, (в) **не зависит от действия LLM**:
на полном прогоне аналитик в любом случае пишет 4 deliverables (свежий mtime), поэтому
оставленный нетронутым старый `01-questions.md` автоматически superseded — ровно сценарий AC-6.
**Контракт промпта (D1) дополняет, не заменяет** механизм: на resume аналитик, у которого остались
блокеры, **перезаписывает** `01-questions.md` (свежий mtime → снова активен); при полном ответе —
просто пишет 4 deliverables, freshness supersedeит старые вопросы. Так оба слоя согласованы.
**Направление fail (never-raise, NFR-1) для DQ-2:**
- Ошибка `getmtime`/сверки при **доказанно существующем** `01-questions.md` → считать вопросы
**активными** (Needs Input) — безопасно для цели «не строить на домыслах».
- Катастрофический сбой (не можем определить даже наличие файла) → деградация к **прежнему**
поведению (`files_ok` → In Review) + WARNING.
### DQ-3 — точное правило приоритета в `_handle_analysis_approved_flow` → **вопросы проверяются ДО `files_ok`, под kill-switch**
**Решение.** Реструктурировать тело (только при `applies(repo)` и `analyst_questions_gate_enabled`):
```
files_ok, _ = QG_CHECKS["check_analysis_complete"](repo, work_item_id, branch)
questions_active = _questions_active(worktree, work_item_id, files_ok) # D2, never-raise
if questions_active:
set_issue_needs_input(work_item_id)
<Plane-коммент с текстом вопросов> + <Telegram> # как сейчас (стр. 770-784)
if analyst_needs_input_autopause_enabled and applies(repo):
db.set_task_paused(task_id) # D4 авто-park, never-raise
result.note = "analysis-needs-input"
return
if files_ok:
<ветка In Review + autoApprove ORCH-089> # существующая, байт-в-байт
return
<ветка "ни файлов, ни вопросов"> # существующая
```
- **Приоритет вопросов > «файлы на месте»** (AC-1): `questions_active` проверяется первым.
- **Happy-path** (нет файла вопросов): `questions_active == False` → ветка `files_ok` → In Review,
включая autoApprove ORCH-089 — **байт-в-байт** (AC-3).
- **Kill-switch / out-of-scope** (`analyst_questions_gate_enabled == False` ИЛИ репо вне области):
исполняется **исходный** порядок (`files_ok` первым; затем плоский `os.path.isfile(questions_path)`
— существующая ветка) — **байт-в-байт как до ORCH-120**, включая для enduro (AC-9).
### DQ-4 — коллизия номера `01-questions.md` с `01-brd.md` → **сохранить путь `01-questions.md`**
**Решение.** Движок уже читает именно `docs/work-items/<wid>/01-questions.md`
(`stage_engine.py:771`) — это **рабочий** контракт; смена пути = код-изменение + поломка читателя
без выгоды. Путь **сохраняется**. В `PIPELINE_DOCS.md` (D2) фиксируется нормативно: префикс `01-`
общий для артефактов стадии `analysis` аналитика; `01-brd.md` — обязательный deliverable,
`01-questions.md`**сигнальный** when-applicable артефакт того же владельца/стадии; коллизии нет,
т.к. файлы разноимённые, гейт `check_analysis_complete` проверяет ровно `01-brd.md`/`02`/`03`/`04`
(`01-questions.md` им не парсится).
---
## Изменения (карта, нормативно)
| Путь | Действие | Примечание |
|------|----------|------------|
| `.openclaw/agents/analyst.md` | изменить | D1: контракт «блокирующие вопросы → `01-questions.md`, НЕ фабриковать 4 deliverables» в `<task>`+`<deliverables>`+поведение на resume; **сохранить канон 52d** (5 секций, 6 полей, без `model:`). |
| `docs/_templates/01-questions.md` | создать | D2: скелет (контекст / список блокирующих вопросов с вариантами / «что разблокирует анализ»). |
| `docs/_standards/PIPELINE_DOCS.md` | изменить | D2: строка манифеста §2 (`01-questions.md`, владелец `analyst`, `when-applicable`, стадия `analysis`, механизм «ветка Needs Input в `_handle_analysis_approved_flow`», machine-key — «нет, сигнальный») + примечание о префиксе `01-` (DQ-4). |
| `src/stage_engine.py` | изменить | D3: приоритет + `_questions_active` (D2) + авто-park (D4); всё never-raise, под kill-switch. |
| `src/webhooks/plane.py` | изменить (точечно) | D5: analysis-resume ветка `handle_status_start` (стр. 317381) — `clear_task_paused(task_id)` (под autopause-флагом + `applies`), never-raise. |
| `src/config.py` | изменить | 3 новых ключа (ниже), безопасные дефолты. |
| `src/db.py` | переиспользовать | `set_task_paused`/`clear_task_paused` (ORCH-124) — **новых колонок нет**. |
| `src/serial_gate.py` | **не менять кодом** | ось «пауза» уже исключает `paused_at NOT NULL` (ORCH-124); ORCH-120 лишь корректно её триггерит (D4/D5). |
**Флаги (`src/config.py`), дефолты безопасны:**
| Ключ | Env | Дефолт | Назначение |
|------|-----|--------|------------|
| `analyst_questions_gate_enabled` | `ORCH_ANALYST_QUESTIONS_GATE_ENABLED` | `True` | kill-switch приоритета+supersede (D3). `False` → исходный порядок байт-в-байт. |
| `analyst_questions_gate_repos` | `ORCH_ANALYST_QUESTIONS_GATE_REPOS` | `""` | CSV; **пусто → self-hosting only** (`is_self_hosting_repo`, как ORCH-35/43/58). enduro не затронут. |
| `analyst_needs_input_autopause_enabled` | `ORCH_ANALYST_NEEDS_INPUT_AUTOPAUSE_ENABLED` | `True` | независимый под-тумблер авто-park/unpark (D4/D5). `False` → operator-park (`POST /serial-gate/pause`). |
`applies(repo)` (локально, без сети) проверяется **первым** — выключенный флаг / вне области
стоит ноль сетевых вызовов и даёт байт-в-байт прежнее ветвление (зеркало `serial_gate.applies`).
---
## Изменения схемы БД
**Нет.** Колонка `tasks.paused_at` введена ORCH-124 (`src/db.py:160`, `_ensure_column`). Новых
таблиц/колонок/индексов ORCH-120 не вводит. Поэтому отдельный `08-data-requirements.md` не выпускается.
## Инфраструктурные требования
**Нет нового.** Plane-статус **Needs Input** уже на доске (ключ `needs_input` в
`plane_sync._DEFAULT_STATES`); эндпоинты `POST /serial-gate/pause|resume` существуют (ORCH-124).
Поэтому отдельный `07-infra-requirements.md` не выпускается.
## QG / стадии
**Не тронуты.** `01-questions.md` — сигнальный, не machine-verdict (BR-6). `check_analysis_complete`/
`check_analysis_approved`/`_parse_*`/`STAGE_TRANSITIONS`/`QG_CHECKS` — байт-в-байт (NFR-2). Поток
вопросов остаётся pre-gate-веткой движка.
---
## Последствия
**Плюсы.**
- Аналитик получает легитимный канал «не знаю — спрошу» вместо принуждения к фабрикации (BR-1);
конвейер перестаёт строить решения поверх домыслов.
- Блокирующие вопросы надёжно достигают Needs Input даже при частичных deliverables (BR-2/AC-1).
- serial-gate репо не клинит на задаче, ждущей человека (BR-3/AC-4) — автономный пакетный прогон
(ORCH-088) не стопорится.
- Resume-петля корректна: ответ → unpark → claim перезапущенного analyst-job (BR-4/AC-5).
- Устаревший `01-questions.md` детерминированно supersedeится без зависимости от LLM (AC-6).
- Полная обратимость: 3 флага с безопасными дефолтами; вне области/выключено → байт-в-байт (AC-9).
**Минусы / ограничения.**
- Авто-park связывает индикацию (слой B) с осью планировщика — смягчено узким триггером,
отдельным флагом, never-raise и симметричным unpark (DQ-1).
- mtime-freshness теоретически хрупок при экзотической re-материализации worktree — на практике
устойчив (полный прогон всегда пишет свежие deliverables); смягчён fail-в-сторону-Needs-Input и
контрактом промпта (DQ-2, см. `10-tech-risks.md` TR-2).
- Needs Input по-прежнему **только** у аналитика (ORCH-066 BR-10 не расширяется) — намеренно.
**Трассировка маркеров (ORCH-078).** Правки в `_handle_analysis_approved_flow`/`handle_status_start`
сверены с ADR ORCH-066 (Needs-Input владение), ORCH-088/124 (serial-gate/пауза), ORCH-089
(autoApprove), ORCH-090 (relaunch-guard) — инварианты не сломаны. Блок `_handle_analysis_approved_flow`
несёт 3+ маркера → эволюция агрегирована в сквозном `adr-0053`.
## Соответствие AC
AC-1 → D3 (приоритет) + D2 (active при files_ok). AC-2 → D3 (questions при !files_ok). AC-3 → D3
(happy-path байт-в-байт). AC-4 → D4 (авто-park → `paused_at` исключает из активного предиката).
AC-5 → D5 (resume unpark). AC-6 → D2 (freshness supersede). AC-7 → D1 (контракт промпта). AC-8 → D2
(скелет + манифест). AC-9 → флаги/скоуп (байт-в-байт off/enduro). AC-10 → never-raise во всех врезках.
AC-11 → снапшот `STAGE_TRANSITIONS`/`QG_CHECKS` не изменён.

View File

@@ -0,0 +1,41 @@
---
work_item: ORCH-120
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# 10 — Технические риски: ORCH-120 — Открытые вопросы аналитика → Needs Input
Work Item: **ORCH-120** · Repo: **orchestrator** (self-hosting) · Стадия: architecture
> Информационный (гейтом не парсится). Перечисляет риски реализации и их митигейшн.
## Реестр рисков
| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| TR-1 | **Связывание индикации (слой B) с осью планировщика** при авто-park (DQ-1): непреднамеренный park из-за широкого/ошибочного триггера. | Низ. | Сред. | Триггер узкий — **только** переход аналитика в Needs Input в `_handle_analysis_approved_flow`, не общее «статус→пауза». Отдельный под-флаг `analyst_needs_input_autopause_enabled` (откат к operator-park). never-raise: сбой `set_task_paused` не отменяет Needs Input. Симметричный unpark на resume (D5) исключает «застрявшую паузу». |
| TR-2 | **Хрупкость mtime-freshness** (DQ-2): re-материализация worktree выставляет близкие mtime → ложный supersede (AC-1 ломается) или ложная активность (AC-6 ломается). | Низ. | Сред. | Полный прогон аналитика **всегда** пишет 4 deliverables свежим mtime → старый нетронутый `01-questions.md` детерминированно старше (AC-6 устойчив). Сверка строгая (`>` для всех 4). Контракт промпта (D1): на resume с блокерами аналитик **перезаписывает** вопросы → свежий mtime (AC-1 устойчив). Fail-в-сторону-Needs-Input при ошибке `getmtime` на существующем файле. Покрыто TC-01/TC-06. |
| TR-3 | **Регресс serial-gate** (ORCH-088/124): неаккуратная интеграция паузы обходит freeze/deps или ломает анти-stale-base. | Низ. | Выс. | `serial_gate.py`/`task_deps.py`/`stages.py` **кодом не трогаются** — пауза уже ортогональна (ORCH-124: `paused_at` не читают оси freeze/deps/терминал). Анти-stale-base цел: нормальная задача `paused_at IS NULL` держит гейт; на resume свежесть базы дают существующие механизмы (отложенный срез / `auto_rebase_onto_main`). Снапшот-тест serial-gate + TC-04. |
| TR-4 | **Бесконечная петля Needs Input** при устаревшем `01-questions.md` (если supersede не сработал). | Низ. | Сред. | Детерминированный freshness supersede (DQ-2) + обязательный регресс TC-06 (полный пакет без свежих вопросов → In Review, не повторный Needs Input). Контракт промпта подкрепляет. |
| TR-5 | **Застрявшая пауза на resume** (task остаётся `paused_at NOT NULL` после ответа → семантика «активна, но помечена paused»). | Низ. | Низ. | D5: `clear_task_paused` на analysis-resume ветке `handle_status_start` (под autopause-флагом + `applies`), идемпотентно/never-raise. Ручной fallback — `POST /serial-gate/resume`. Покрыто TC-05. |
| TR-6 | **Нарушение never-raise** новой логики (чтение файла/park/приоритет) роняет `advance_stage`/launcher → встаёт конвейер всех проектов (self-hosting). | Низ. | Выс. | Все врезки в `try/except` с деградацией к **прежнему** поведению + WARNING (паттерн `serial_gate`/`labels`/`cancel`). `set_task_paused`/`clear_task_paused` уже never-raise (→ `False`). Покрыто TC-09. |
| TR-7 | **Регресс enduro / нулевой-флаг** (поведение отличается при выключенном kill-switch или вне self-hosting). | Низ. | Сред. | `applies(repo)` первым; off/out-of-scope → исходный порядок (`files_ok` первым + плоский isfile) **байт-в-байт**. enduro-аналитик `01-questions.md` не эмитит. Покрыто TC-10 + снапшот TC-11. |
| TR-8 | **Дрейф промпта** (контракт вопросов не задокументирован или сломан канон 52d). | Низ. | Низ. | Анти-дрейф `tests/test_agent_prompts_canon.py` (5 секций, 6 полей, без `model:`) + новый assert наличия контракта вопросов (TC-07); канон артефакта — TC-08. |
## Сводный вывод
Доминирующий класс — **корректность интеграции с serial-gate (ORCH-088/124) и never-raise на
self-hosting горячем пути** (TR-3/TR-6: высокое влияние, низкая вероятность). Оба полностью
структурно нейтрализованы: serial-gate кодом не трогается (пауза уже ортогональна), все врезки
изолированы и деградируют к прежнему поведению. Остальные риски — низкого влияния и покрыты
обязательными регресс-тестами (TC-01/TC-04/TC-05/TC-06/TC-09/TC-10).
Остаточный риск для прод-конвейера (self-hosting) — **низкий**: изменение аддитивно, под тремя
флагами с безопасными дефолтами (байт-в-байт откат), не деплоит / не рестартит прод / не пушит в
`main` / не трогает detached-процессы (NFR-4). Эскалация `arch:major-change` **не требуется**
(нет новой стадии/QG/компонента/смены БД); возврат в анализ **не требуется** (ТЗ удовлетворимо без
нарушения принципов архитектуры).