Files
orchestrator/docs/work-items/ORCH-123/12-review.md
claude-bot 820e534e77
All checks were successful
CI / test (push) Successful in 1m14s
CI / test (pull_request) Successful in 1m12s
reviewer(ET): auto-commit from reviewer run_id=751
2026-06-16 08:52:22 +03:00

154 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
verdict: APPROVED
work_item: ORCH-123
stage: review
author_agent: reviewer
status: approved
created_at: 2026-06-16
model_used: claude-opus-4-8
type: review
work_item_id: ORCH-123
version: 1
---
# Review ORCH-123
## Summary
Фикс инцидента **ORCH-116**: детерминированный staging-runner (ORCH-115) исполнял `docker exec`
**изнутри** прод-контейнера `orchestrator`, где нет docker CLI (`Dockerfile:11` — только
`openssh-client git curl`) → `FileNotFoundError` → постоянный environment-дефект ложно
маршрутизировался как код-фейл-откат `deploy-staging → development` (с расходом developer-retry).
Задача — `Bug`, эскалирована `escalate: full-cycle` (полный пакет analysis+architecture+ADR).
Изменение точное и минимальное: 9 файлов в реальном скоупе (`65c17d8..HEAD`) —
`src/staging_runner.py` (+343), `src/config.py` (новый флаг), `src/main.py` (preflight),
`.env.example`, `CHANGELOG.md`, `CLAUDE.md`, `docs/architecture/README.md`,
`docs/operations/INFRA.md`, + тесты `tests/test_orch123_staging_runner_exec.py` (503),
`tests/test_orch115_staging_runner.py` (anti-drift).
Реализация байт-в-байт соответствует ADR-001 / adr-0049 (D1 host-side ssh-wrap, D3 трёхсторонняя
`classify_staging_outcome`, D4 инвариант «инфра ≠ код-фейл» = infra-HOLD, D5 preflight, D6 флаг,
D7 сохранность контракта, D8 наблюдаемость). Все четыре оси проверки пройдены; P0/P1 findings нет.
**Проверено фактически:**
- Полный прогон `pytest tests/ -q`**2131 passed** (AC-11).
- Целевой набор `test_orch123_staging_runner_exec.py` + `test_orch115_staging_runner.py` +
`test_llm_determinization_docs.py`**53 passed**.
- Структурный анти-регресс `test_system_docs.py` + `test_no_host_hardcodes.py` +
`test_config.py`**61 passed**.
## Соответствие ТЗ (02-trz.md / 03-acceptance-criteria.md)
Все функциональные требования и критерии приёмки реализованы и покрыты тестами:
| AC | FR/BR | Статус | Где проверено |
|----|-------|--------|---------------|
| AC-1, AC-2 — сюита реально исполняется host-side | FR-1 | ✅ | `build_staging_command` ssh-wrap; TC-02, TC-05 |
| AC-3 — env/tool-error ≠ код-фейл-откат | FR-2/FR-3 | ✅ | `classify_staging_outcome` + `_handle_permanent_env`/`_handle_transient_infra`; TC-01, TC-03 |
| AC-4 — анти-over-tolerance (исполнившаяся упавшая сюита → откат) | FR-2/BR-3 | ✅ | check 3 «trust recognised exit-code first»; TC-04, TC-04-exit1-marker |
| AC-5 — prod-like preflight | FR-4 | ✅ | `preflight()` в `main.lifespan` (best-effort, never-blocks); TC-07 |
| AC-6 — обязательный регресс red→green | FR-1/FR-2 | ✅ | TC-01 (in-container shape + spawn-error → permanent-env, без advance/defer/retry) |
| AC-7 — гейт/контракт/стадии/схема без регресса | NFR-1 | ✅ | TC-10, TC-11 (`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_staging_status`/схема БД целы) |
| AC-8 — self-hosting safety | NFR-2 | ✅ | TC-08 (нет `compose`/`--build`/`8500`/`force`/`push`/`.env`/`restart` в argv) |
| AC-9 — security-review socket/CLI | NFR-3 | ✅ | ADR D2: выбран host-side ssh, docker CLI/SDK НЕ добавлен, `docker.sock` не используется изнутри |
| AC-10 — kill-switch/область | NFR-4/NFR-6 | ✅ | TC-09 (`staging_runner_exec_host_side` default True; enduro no-op) |
| AC-11 — never-raise / очередь не клинится | NFR-5 | ✅ | TC-12; полный suite зелёный |
| AC-12 — документация границы исполнения | BR-5/FR-6 | ✅ | TC-13 (INFRA.md + README.md содержат host-side/ssh/ORCH-123) |
Регресс R-2 (held `deploy-staging`-задача не откатывается reconciler'ом) — отдельный тест
`test_r2_held_deploy_staging_not_rolled_back`: при отсутствии `15-staging-log.md` (infra-HOLD)
`advance_if_gate_passed` отдаёт `None`, задача держится на `deploy-staging`, `advance_stage` не
зовётся. Закрывает риск реинтродукции дефекта (10-tech-risks R-2).
## Соответствие ADR (06-adr/ADR-001 + сквозной adr-0049)
- **D1** — `build_staging_command` оборачивает ту же `docker exec … staging_check.py … --mode stub`
в `ssh -o StrictHostKeyChecking=no <user@host> '<inner>'` (зеркало `self_deploy.build_deploy_command`/
`image_freshness.image_revision`); inner-команда `shlex.quote`-ится; fallback на in-container при
выключенном флаге ИЛИ пустом ssh-target. ✅
- **D3** — `classify_staging_outcome` различает три класса с корректным порядком проверок:
env-маркер в stderr → `permanent-env` (дизамбигуация `exit=1` коллизии «No such container» vs
реальный фейл, R-3); 126/127 → `permanent-env`; распознанный int≠255 без маркера → `suite-ran`
(проверяется **до** channel-guards → реальный фейл не маскируется, BR-3); timeout/255 →
`transient-infra`; нет ssh-target/`rc=None``permanent-env`; неизвестно → fail-safe
`transient-infra`. Pure, never-raise. ✅
- **D4** — инвариант «инфра ≠ код-фейл»: `permanent-env` → немедленный infra-HOLD (без `_advance`,
без FAILED-лога, без developer-retry); `transient-infra` → bounded DEFER, на исчерпании — тоже
infra-HOLD (суперсед терминала ORCH-115 D5 fail-closed-FAILED+rollback). ✅
- **D5** — `preflight()` зондирует host-side канал коротким bounded ssh-probe, пишет
`_PREFLIGHT_STATE`, алертит; вызван best-effort из `main.lifespan` после lease-recovery,
никогда не блокирует старт. ✅
- **D6** — `staging_runner_exec_host_side: bool = True` (env `ORCH_STAGING_RUNNER_EXEC_HOST_SIDE`),
дефолт = боевое; откат двумя флагами. ✅
- **D7** — `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_staging_status`/`_parse_staging_status`/
machine-verdict `staging_status:`/схема БД — байт-в-байт (TC-10/TC-11). ✅
- **D8** — `snapshot()` различает три не-успешных класса (`failed`=code-fail / `deferred`=transient /
`permanent_env`=infra-HOLD) + `exec_host_side` + `preflight`. ✅
**Трассировка маркеров (CLAUDE.md §9 / TRACEABILITY.md):** ORCH-123 правит блоки ORCH-115
(staging-runner, прямой объект), ORCH-036 (self_deploy ssh-паттерн), ORCH-110 (proc_group/classify
/infra-tolerance), ORCH-058 (host-side docker). ADR явно сверяется с их инвариантами и не ломает
их; анти-дрейф ORCH-115 — TC-14 (`should_intercept`/`map_exit_code_to_status` shared-контракт,
kill-switch → LLM) зелёный.
## Качество кода
- Чистый leaf-стиль по образцу `merge_gate`/`self_deploy`/`image_freshness`; на импорте только
`config`/`shlex`/`subprocess`, тяжёлое — лениво. Все публичные функции с docstrings.
- `never-raise` соблюдён на всех путях (classify, preflight, snapshot, run_staging_gate, alert'ы).
- Старое имя `_handle_tool_error` чисто переименовано в `_handle_transient_infra` — висячих ссылок
нет (grep пуст).
- Тесты содержательные (поведенческие, не тривиальные): мокается subprocess/advance/notifications,
spawn-error воспроизводится несуществующим бинарём — без живого ssh/docker/сети.
- **Багфикс-трек (ORCH-019 BR-4):** обязательный регресс-тест-фиксатор дефекта присутствует —
TC-01 красный на коде до фикса (нет класса/счётчика `permanent_env`, путь advance'ит + DEFER),
зелёный после. Требование выполнено.
## Документация
`src/` изменён → документация обновлена **в том же PR** (golden source):
- `docs/operations/INFRA.md` — новый раздел «Граница исполнения docker-операций — host-side» +
правило 4 для агентов (FR-6 / AC-12). ✅
- `docs/architecture/README.md` — секция Staging-runner дополнена host-side стратегией +
трёхсторонней классификацией + ссылкой на adr-0049. ✅
- `CLAUDE.md` — раздел ORCH-123 (host-side exec + классификация environment-дефекта). ✅
- `CHANGELOG.md` — запись `fix(staging)`. ✅
- `.env.example` — новый ключ `ORCH_STAGING_RUNNER_EXEC_HOST_SIDE=true` + описание. ✅
- ADR: `06-adr/ADR-001-…` + сквозной `docs/architecture/adr/adr-0049-…`. ✅
**Витрина системы `docs/overview/` (ORCH-011)** — обновление НЕ требуется: канал исполнения
(host-side vs in-container) — внутренняя деталь реализации; машинно-проверяемые факты витрины
(детерминированный раннер заменил LLM-`deployer`, kill-switch, скоуп) ORCH-123 не меняет;
`test_system_docs.py` зелёный.
**Обзорные доки `README.md` «Известные ограничения» (ORCH-079)** — обновление НЕ требуется:
ORCH-123 закрывает внутренний регресс ORCH-116, а не задокументированное ограничение; релевантного
пункта в списке нет.
## Findings
### P0 — Blocker
- Нет.
### P1 — Must fix
- Нет.
### P2 — Should fix
- Нет.
### P3 — Nice-to-have (не блокирует)
- `preflight()` использует прямой `subprocess.run(timeout=20)` вместо `proc_group` — для короткого
read-only ssh-зонда приемлемо (тот же паттерн, что у host-side probe'ов `image_freshness`);
при таймауте локальный ssh-клиент завершается subprocess'ом без tree-kill, что для 20-секундного
inspect незначимо. Можно унифицировать в будущем, не обязательно.
- `_channel_ssh_configured()` вызывается дважды в `run_staging_gate` (для classify и для лог-строки
`permanent-env`) — микро-избыточность, never-raise, влияния нет.
## Вердикт
**APPROVED.** Реализация полно и точно покрывает ТЗ/AC, соответствует ADR-001/adr-0049, не трогает
гейты/стадии/контракт/схему БД, безопасна для self-hosting, документация (включая витрину-проверку
и обзорные доки) в порядке, обязательный багфикс-регресс присутствует и зелёный, полный suite
(2131) зелёный. P0/P1 отсутствуют.