Files
orchestrator/docs/work-items/ORCH-103/12-review.md

161 lines
14 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-103
stage: review
author_agent: reviewer
status: approved
created_at: 2026-06-11
model_used: claude-opus-4-8
type: review
work_item_id: ORCH-103
version: 1
---
# Review ORCH-103 — ORCH-10b Bundled-тираж: весь стек одним комплектом + bootstrap-скрипт
> Машинный вердикт читается ТОЛЬКО из `verdict:` во frontmatter.
## Summary
PR закрывает Type B эпика ORCH-10 строго по ТЗ и ADR-001 (D1D11): новый каталог
`deploy/bundled/` (самодостаточный compose 16 сервисов, project name `orchestrator-bundle`,
пиннинг неподвижными тегами, одна bridge-сеть, только человеческие порты, мина
`GITEA__webhook__ALLOWED_HOST_LIST` закрыта), `scripts/bootstrap_bundle.py` (stdlib-only,
`plan`-дефолт/`apply`/`verify`, step-движок check→ensure, exit 0/2/1, ноль delete-операций),
конфиг-канон `deploy/bundled/.env.example` (ни одного дефолтного пароля),
`docs/deployment/BUNDLED_SETUP.md` (14 разделов канона D10) и три содержательных
анти-дрейф тест-модуля. Рантайм байт-в-байт: `git diff main` не содержит `src/**`,
корневого `docker-compose.yml`, `Dockerfile`, `.gitea/workflows/**` (AC-6 подтверждён
diff-stat'ом). Полный регресс: **`pytest tests/ -q` → 1844 passed, 0 failed** (AC-5);
существующие анти-дрейф тесты (`test_lite_setup_doc.py`, `test_no_host_hardcodes.py`,
канон ORCH-009) не правились. Документация обновлена в том же PR по всем точкам §8 ТЗ.
Findings — только P2/P3, блокеров нет → **APPROVED**.
## Оси проверки
### 1. Соответствие ТЗ (02-trz.md, 03-acceptance-criteria.md)
- **FR-1** ✅ — отдельный bundle-compose; состав = ADR D1/D3 (тест
`test_bundle_has_exactly_the_adr_service_set` фиксирует множество); пиннинг всех сторонних
образов литералом (TC-03); тома — именованные с префиксом проекта + bind строго внутри
project dir (TC-04); достижимость в обе стороны — сервис-DNS (D4) + `ALLOWED_HOST_LIST`;
карта портов в доке §2, конфликт порта → отказ preflight; staging-контура нет вовсе.
- **FR-2** ✅ — последовательность шагов 19 ТЗ воспроизведена 1:1 в `APPLY_STEPS`
(тест `test_apply_steps_match_normative_plan` держит соответствие нормативному плану);
идемпотентность — check→ensure/skip (AC-8 покрыт unit'ами resume/«противоречивое
состояние»); exit-контракт 0/2/1 (`test_exit_code_contract`); манифест manual-step честный:
инструкция → подтверждение → API-верификация, без TTY — немедленный exit 2.
- **FR-3** ✅ — webhook-секреты строго субпроцессом `gen_secrets.py` (структурный тест);
bundle-креды — stdlib `secrets` (token_hex ≥16 байт, unit проверяет длину); в репо только
пустые плейсхолдеры (`test_bundle_secrets_in_example_are_empty_placeholders`); права 600;
без перетирания без `--force-secrets` (`test_merge_missing_secrets_never_overwrites_without_force`).
- **FR-4** ✅ — BUNDLED_SETUP.md: все 14 разделов в порядке маршрута, fenced-команды +
«Проверка:»/PASS/FAIL в каждом исполняемом разделе, общие шаги ссылками на
LITE_SETUP §5§8 / ONBOARDING / REPLICATION §4 (форк канона отсутствует), fail-closed имена
`Confirm Deploy`/`STOP` и «22 статуса» — сверкой импорта `plane_sync._PLANE_NAME_TO_KEY`.
- **FR-5** ✅ — три модуля без docker/сети/LLM; FORBIDDEN — импорт из
`test_no_host_hardcodes.py` (один источник истины); секрет-эвристика hex≥32/alnum≥40 с
негативным самочеком (не-evergreen); key-set-sync `${VAR}` ⊆ bundle-канона; заморозка
корневого compose зеркалом ассерта ORCH-102.
- **FR-6** ✅ — smoke = REPLICATION §4 поверх bundle (§11 дока, без форка), минимальный
сигнал «артефакты 0104 в ветке» зафиксирован.
- **AC-матрица:** AC-4…AC-9 в файловой/структурной части — PASS (TC-01…TC-12 зелёные).
AC-1/AC-2/AC-3/AC-8(повторный прогон) в e2e-части — **ручная приёмка** на чистом хосте/VM
по рамке самих AC (в CI docker/LLM не гоняются) — остаётся за стадией приёмки, см. P2-2.
### 2. Соответствие ADR (06-adr/ADR-001, adr-0038)
- D1D11 реализованы без отклонений; сквозной `adr-0038-bundled-replication-canon.md` заведён.
- **Прогрессивная автоматизация webhook (D7)** — единственное место, где реализация глубже
буквы ADR: `step_plane_webhook` регистрирует workspace-webhook прямой записью в Postgres
инсталляции. Сверено: это **не новый канон, а переиспользование** документированного
«пути Б» LITE_SETUP §5.4 (тот же INSERT-контракт, колонка-в-колонку), контракт чекпоинта
сохранён (верификация SELECT'ом; при отказе — fallback на честный manual-step с той же
проверкой), схема стабильна благодаря пину `v0.23.1`. D7 явно разрешает такую замену
manual-step → ensure «без правки ADR». Нарушения нет; в доке §7 чекпоинт 4 описан честно.
- **Трассировка (TRACEABILITY):** правка чужого маркированного блока одна — строка Type B в
`REPLICATION.md` §1 (артефакт ORCH-101); это запланированная точка расширения, прямо
предписанная ТЗ FR-6/§8 — инвариант ORCH-101 не сломан. Остальные изменения аддитивны
(новые файлы / новые секции CLAUDE.md, README архитектуры, CHANGELOG).
- Нормативы предшественников соблюдены: branch protection НЕ настраивается (D10 ORCH-009 /
INV-4 — явно в D6 и §14.6 дока), compose не форкается (adr-0037), «дефолт = боевое» не
нарушен (корневой `.env.example` не тронут).
### 3. Качество кода
- `pytest tests/ -q`: **1844 passed, 0 failed** (66s); новые тесты содержательные — unit'ы
чистых функций покрывают позитив/негатив/resume/противоречивое состояние, эвристики имеют
негативный самочек, ast-скан stdlib-allowlist реально закрыт.
- Бизнес-логика скрипта аккуратная: never-print секретов в stdout (только имена ключей),
маскированный лог при падении clone (токен в URL не утекает в вывод), `_psql` через stdin
(секрет не в argv), fail-fast preflight до любых мутаций, диагностика «кто не дождался +
хвост логов». Не багфикс-трек (feature) — требование регресс-теста-фиксатора BR-4/ORCH-019
неприменимо.
- Замечания P2/P3 — ниже; ни одно не ломает инварианты конвейера и не относится к рантайму
платформы (скрипт исполняется только оператором на целевом хосте).
### 4. Документация — обязательная проверка
Выполнена явно, см. раздел «Документация» ниже. Обновлено всё требуемое в том же PR.
## Findings
### P0 — Blocker
- (нет)
### P1 — Must fix
- (нет)
### P2 — Should fix
- [ ] **P2-1. Секреты в argv субпроцессов** (`scripts/bootstrap_bundle.py`):
`step_init_gitea` передаёт `GITEA_ADMIN_PASSWORD` аргументом `--password` в
`docker compose exec gitea gitea admin user create …`, а `step_agent_git` — токен в
clone-URL аргументом `git clone http://oauth2:<token>@…`. Значения видимы в `ps` хоста на
время исполнения. Формально AC-8 («секрет виден в stdout/логе») не нарушен, но это против
духа NFR-3 (ТЗ FR-2) и непоследовательно с собственной argv-гигиеной скрипта (`_psql`
прогоняет секреты через stdin с явным комментарием «секреты не попадают в argv, NFR-3»).
Рекомендация: для clone — использовать `git -c credential.helper`/`GIT_ASKPASS` либо
дописать компромисс в TR-7 (10-tech-risks) и шапку скрипта; для `gitea admin user create`
альтернатив CLI мало — минимум зафиксировать окно экспозиции в TR-7. Не блокер: разовая
операция оператора на одноарендном целевом хосте, угроза-модель совпадает с уже
зафиксированным компромиссом TR-7.
- [ ] **P2-2. Замер цифр «Требований к хосту» отложен на приёмку** (AC-4): BUNDLED_SETUP §2
декларирует «подтверждаются замером приёмочного развёртывания» — на момент ревью цифры
(8 GB / 40 GB / 4 vCPU) синхронизированы с константами preflight структурным тестом, но
фактический замер ещё не зафиксирован. По рамке 03-acceptance-criteria (e2e — ручная
приёмка вне CI) это допустимо, однако при ручной приёмке AC-1/AC-2 результат замера нужно
зафиксировать (13-test-report / 15-staging-log или правка §2), иначе FAIL-условие AC-4
«цифры с потолка» останется формально незакрытым.
### P3 — Nice to have
- [ ] **P3-1.** `step_plane_webhook`: `slug`/`secret` интерполируются в SQL-строку без
экранирования одинарных кавычек. Секрет — hex от `gen_secrets` (безопасен), slug —
операторский ввод; кавычка в slug уронит INSERT. Риск минимален (ON_ERROR_STOP +
fail-safe fallback на manual-step), но дешёвое `value.replace("'", "''")` сняло бы класс
целиком.
- [ ] **P3-2.** `run_verify`: при одновременном health-FAIL и onboard `exit 2` функция
возвращает `EXIT_MANUAL` (2), маскируя ошибку (ожидался бы приоритет `1`). Поведенческая
мелочь read-only режима.
## Документация
| Артефакт | Статус |
|----------|--------|
| `CLAUDE.md` | ✅ новый раздел «Bundled-тираж (ORCH-103)» (паттерн ORCH-101/102) |
| `docs/architecture/README.md` | ✅ блок Type B — Bundled рядом с 10-common/Lite |
| `CHANGELOG.md` | ✅ запись `feat: ORCH-103` (детальная, D1D11) |
| `docs/operations/REPLICATION.md` §1 | ✅ Type B → ✅ ORCH-103 + ссылка на BUNDLED_SETUP.md |
| `docs/deployment/BUNDLED_SETUP.md` | ✅ создан, 14 разделов канона D10, держится тестом |
| ADR | ✅ work-item `06-adr/ADR-001-…` + сквозной `adr-0038-bundled-replication-canon.md` |
| `07-infra-requirements.md`, `10-tech-risks.md` (TR-1…TR-9) | ✅ заведены архитектором, кросс-рефы из кода сходятся |
| `.gitignore` | ✅ `deploy/bundled/repos/` (NFR-3) |
| `README.md` «Известные ограничения» (ORCH-079) | ✅ проверено явно: PR не закрывает ни один из 3 открытых пунктов (Telegram 48h / intra-repo deps / пакетный автоном) — обновление витрины не требуется |
`src/**` не изменён (PR — deploy/scripts/docs/tests), поэтому P0-правило «`src/` изменён без
документации» неприменимо; документация при этом обновлена полностью.
## Итог
`verdict: APPROVED` — P0/P1 отсутствуют; P2-1/P2-2 и P3 рекомендуется снять follow-up'ом или
при ручной приёмке Bundled-развёртывания (AC-1/AC-2/AC-3/AC-8 e2e-часть).