Merge pull request 'feat(ORCH-058): staging-image provenance before BUILD-ONCE prod retag (INV-FRESH)' (#57) from feature/ORCH-058-self-deploy-retag-staging into main
This commit was merged in pull request #57.
This commit is contained in:
@@ -35,7 +35,7 @@ created → analysis → architecture → development → review → testing →
|
||||
| deploy | — | `check_deploy_status` | 14-deploy-log.md (`deploy_status:`) |
|
||||
| done | — | — | — |
|
||||
|
||||
**Реестр QG** (`QG_CHECKS`): check_analysis_approved, check_analysis_complete, check_architecture_done, check_ci_green, check_review_approved, check_tests_passed, check_reviewer_verdict, check_tests_local, check_deploy_status, check_staging_status, check_branch_mergeable (ORCH-043).
|
||||
**Реестр QG** (`QG_CHECKS`): check_analysis_approved, check_analysis_complete, check_architecture_done, check_ci_green, check_review_approved, check_tests_passed, check_reviewer_verdict, check_tests_local, check_deploy_status, check_staging_status, check_branch_mergeable (ORCH-043), check_staging_image_fresh (ORCH-058).
|
||||
|
||||
**Канон гейтов:** машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в `origin/main` отдельным PR; гейт читает из `origin/main`.
|
||||
|
||||
@@ -80,6 +80,34 @@ terminal-sync, merge-gate, exit-code-контракт хука. Restart-safe с
|
||||
sentinel-файлы (`<repos_dir>/.deploy-state-<repo>/<wi>/`), без миграции БД.
|
||||
Подробнее: [adr-0007](adr/adr-0007-executable-self-deploy.md), детально —
|
||||
`docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md`.
|
||||
|
||||
### Свежесть артефакта BUILD-ONCE: провенанс staging-образа (ORCH-058 — реализовано)
|
||||
BUILD-ONCE retag (ORCH-36) промоутит `SOURCE_IMAGE=orchestrator-orchestrator-staging` в прод
|
||||
**без rebuild**, полагаясь на «staging-образ свеж и провалидирован». Этой гарантии нет:
|
||||
конвейер нигде не пересобирает staging-образ из провалидированного коммита → retag мог тихо
|
||||
промоутнуть УСТАРЕВШИЙ образ (инцидент LESSONS_ORCH-036 п.4 — зелёный деплой молча
|
||||
откатывал прод). ORCH-058 обеспечивает инвариант `INV-FRESH` **двумя слоями** (defense in
|
||||
depth), только для self-hosting:
|
||||
- **A — пересборка (liveness):** детерминированный QG-под-чек `check_staging_image_fresh` на
|
||||
ребре `deploy-staging → deploy` ПОСЛЕ merge-gate и ДО Phase A пересобирает
|
||||
`orchestrator-orchestrator-staging` из worktree валидированного коммита
|
||||
(`--build-arg GIT_SHA=<sha>`, OCI-лейбл `org.opencontainers.image.revision`), пересоздаёт
|
||||
8501 и прогоняет `staging_check` против свежего образа → валидируем и промоутим один
|
||||
артефакт. FAIL → откат на `development` (как merge-gate). Сборки/recreate — ТОЛЬКО staging.
|
||||
- **B — fail-closed guard (safety):** хук шагом 2b ПЕРЕД `docker tag` сверяет лейбл `revision`
|
||||
у `SOURCE_IMAGE` с `EXPECTED_REVISION` (пробрасывает `build_deploy_command`). Несовпадение
|
||||
/ пустой лейбл / пустой ожидаемый SHA / ошибка inspect → `exit 1` → FAILED (БАГ-8 откат),
|
||||
прод не трогается. Делает тихий промоут устаревшего образа структурно невозможным даже при
|
||||
отключённой/проигравшей гонку A.
|
||||
|
||||
Якорь «провалидированного коммита» — `git rev-parse HEAD` worktree ПОСЛЕ merge-gate (один
|
||||
helper `validated_revision` питает и штамп A, и `EXPECTED_REVISION` B). Единый kill-switch
|
||||
`image_freshness_enabled` включает A+B **как целое** (нет «B без A» = вечного fail-fast);
|
||||
`image_freshness_repos` (пусто → self-hosting). `STAGE_TRANSITIONS`, exit-code хука (0/1/2),
|
||||
`check_deploy_status`, БАГ-8, merge-gate, схема БД — НЕ меняются (под-гейт ребра + лейбл
|
||||
образа, без миграций). Подробнее: [adr-0008](adr/adr-0008-staging-image-provenance.md),
|
||||
детально — `docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md`.
|
||||
|
||||
### Reconciler: реконсиляция потерянных webhook (ORCH-053 — реализовано)
|
||||
Конвейер продвигается только входящими webhook; потерянное событие (502 на ребилде,
|
||||
нет ретраев у Plane/Gitea, неразрезолвленный `sha→branch`) → задача застревает молча
|
||||
@@ -166,5 +194,4 @@ never-raise на единицу работы; тишина при синхрон
|
||||
Схема БД, потоки данных, resilience-слой, детали Dockerfile — [internals.md](internals.md).
|
||||
|
||||
---
|
||||
*Актуально на 2026-06-06. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. ORCH-043: merge-gate — design (см. adr-0006), реализация в ветке feature/ORCH-043. ORCH-036: исполняемый самодеплой стадии `deploy` — design (см. adr-0007), реализация в ветке feature/ORCH-036.*
|
||||
*Актуально на 2026-06-06. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. ORCH-043: merge-gate — design (см. adr-0006), реализация в ветке feature/ORCH-043. ORCH-053: reconciler — реализовано (см. adr-0007, src/reconciler.py).*
|
||||
*Актуально на 2026-06-07. Обновлять при изменении src/stages.py, src/qg/checks.py, src/main.py. Статусы доработок: ORCH-036 (исполняемый самодеплой `deploy`, adr-0007) — реализовано; ORCH-043 (merge-gate, adr-0006) — design, ветка feature/ORCH-043; ORCH-053 (reconciler, adr-0007, src/reconciler.py) — реализовано; ORCH-058 (провенанс staging-образа: check_staging_image_fresh + staging_check свежего образа + хук-guard, adr-0008) — реализовано в ветке feature/ORCH-058 (обновлять также при изменении src/image_freshness.py, scripts/orchestrator-deploy-hook.sh, Dockerfile).*
|
||||
|
||||
77
docs/architecture/adr/adr-0008-staging-image-provenance.md
Normal file
77
docs/architecture/adr/adr-0008-staging-image-provenance.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# ADR-0008: Провенанс staging-образа перед BUILD-ONCE retag в прод (ORCH-058)
|
||||
|
||||
## Статус
|
||||
Accepted (design) — реализация в ветке `feature/ORCH-058-self-deploy-retag-staging`.
|
||||
Метка: `arch:major-change`.
|
||||
|
||||
> Примечание о нумерации: в `adr/` исторически два файла `adr-0007-*`
|
||||
> (`executable-self-deploy`, `reconciler`) — пред-существующая коллизия. Этот ADR берёт
|
||||
> следующий свободный номер **0008**; коллизию 0007 не трогаем (вне объёма ORCH-058).
|
||||
|
||||
## Контекст
|
||||
|
||||
ORCH-36 (`adr-0007-executable-self-deploy`) сделал стадию `deploy` исполняемой для
|
||||
self-hosting: Phase B запускает host-хук, который шагом **2b** (BUILD-ONCE) делает
|
||||
`docker tag $SOURCE_IMAGE → $TARGET_IMAGE` **без rebuild** — «прод = ровно тот артефакт,
|
||||
что прошёл staging». Предпосылка: staging-образ свеж и собран из провалидированного кода.
|
||||
|
||||
**Этой гарантии нет.** Конвейер нигде не пересобирает `orchestrator-orchestrator-staging`
|
||||
из провалидированного коммита; `deploy-staging` лишь гоняет `staging_check.py` против уже
|
||||
работающего 8501. Инцидент (LESSONS_ORCH-036 п.4): staging-образ не пересобрали → проверка
|
||||
прошла против старого кода → retag промоутнул СТАРЫЙ образ → прод **молча** откатился на
|
||||
2-дневный код. Зелёный гейт = ложный позитив. Самый опасный из 4 багов: не падает, а тихо
|
||||
откатывает инструмент, обслуживающий все проекты.
|
||||
|
||||
## Решение
|
||||
|
||||
Гарантировать `INV-FRESH`: в прод промоутится только образ, собранный из коммита,
|
||||
провалидированного `deploy-staging` для данной задачи; иначе fail-fast (`FAILED` → откат на
|
||||
`development`, БАГ-8), прод не трогается. Достигается **двумя взаимодополняющими слоями**
|
||||
(defense in depth), только для self-hosting (условность как ORCH-35/36/43):
|
||||
|
||||
- **A — пересборка (liveness).** На ребре `deploy-staging → deploy`, ПОСЛЕ merge-gate и ДО
|
||||
Phase A, детерминированный QG-под-чек `check_staging_image_fresh` пересобирает
|
||||
`orchestrator-orchestrator-staging` из worktree валидированного коммита
|
||||
(`--build-arg GIT_SHA=<sha>`, лейбл `org.opencontainers.image.revision`), пересоздаёт 8501
|
||||
и прогоняет `staging_check`. FAIL → откат на `development`. Так валидируемый и промоутимый
|
||||
артефакт — один и тот же; гарантирует наличие зелёного пути (нет вечного fail-fast).
|
||||
- **B — fail-closed guard (safety).** Хук шагом 2b ПЕРЕД `docker tag` сверяет лейбл
|
||||
`revision` образа `SOURCE_IMAGE` с `EXPECTED_REVISION` (пробрасывает `build_deploy_command`).
|
||||
Несовпадение / пустой лейбл / пустой ожидаемый SHA / ошибка inspect → `exit 1` → FAILED.
|
||||
Делает тихий промоут устаревшего образа структурно невозможным даже при отключённой/
|
||||
проигравшей гонку A.
|
||||
|
||||
**Якорь провалидированного коммита** — `git rev-parse HEAD` в worktree ПОСЛЕ merge-gate
|
||||
(post-rebase tree, который ре-тестирован и сольётся в `main`). Один helper
|
||||
`validated_revision(repo, branch)` питает и штамп сборки (A), и `EXPECTED_REVISION` (B).
|
||||
|
||||
**Условность и kill-switch:** единый `image_freshness_enabled` (вкл/выкл A+B как целое,
|
||||
чтобы не было «B без A» = вечный fail-fast), `image_freshness_repos` (CSV; пусто →
|
||||
self-hosting). Все настройки с префиксом `ORCH_`.
|
||||
|
||||
### Что НЕ меняется
|
||||
`STAGE_TRANSITIONS` (набор стадий — под-гейт ребра, не стадия), exit-code хука (0/1/2),
|
||||
`map_exit_code_to_status`, `check_deploy_status`/`_parse_deploy_status`, БАГ-8, terminal-sync,
|
||||
merge-gate, Phase A/B/C. Схема БД — без миграций (провенанс в лейбле образа, не в БД).
|
||||
|
||||
### Что добавляется (сквозное)
|
||||
- QG `check_staging_image_fresh` в реестре `QG_CHECKS` (+ snapshot-тест), wired через
|
||||
`_handle_image_freshness` в `stage_engine` (рядом с merge-gate).
|
||||
- Режим хука `--build-staging` (build из worktree + recreate 8501; STAGING-safe дефолты).
|
||||
- OCI-лейбл `org.opencontainers.image.revision` в `Dockerfile` (`ARG GIT_SHA`).
|
||||
- Helpers `validated_revision` / `rebuild_staging_image` в `self_deploy.py` (never-raise).
|
||||
|
||||
## Последствия
|
||||
|
||||
- Класс «тихого регресса прод» закрыт структурно (B); валидный деплой всегда доходит до
|
||||
зелёного (A) — устранён ручной bootstrap-разрыв пересборки staging.
|
||||
- Латентность ребра растёт (build + recreate + повторный staging_check); `staging_check`
|
||||
гоняется дважды (soft pre-check агента + авторитетный код) — плата за «валидируем =
|
||||
промоутим».
|
||||
- Все сборки/recreate — ТОЛЬКО staging (8501); прод (8500) не трогается; `main` не пушится.
|
||||
Новая под-компонента → `arch:major-change`.
|
||||
|
||||
## Связанные ADR
|
||||
`adr-0007-executable-self-deploy` (BUILD-ONCE, Phase A/B/C), `adr-0006-merge-gate` (образец
|
||||
edge-под-гейта), `adr-0003-staging-gate` (условность self-hosting), `adr-0005`
|
||||
(run-as-host-uid). Детальный per-work-item: `docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md`.
|
||||
@@ -9,6 +9,7 @@
|
||||
1. **Захват текущего образа** — до рестарта записывает ID образа работающего контейнера в `$PREV_IMAGE_FILE` (best-effort, не падает если сервис не запущен).
|
||||
2. **git pull** — обновляет код репозитория.
|
||||
2b. **Build-once retag** (ORCH-036, BR-6) — если задан `$SOURCE_IMAGE`, хук ретегает его на `$TARGET_IMAGE` (`docker tag $SOURCE_IMAGE $TARGET_IMAGE`) и поднимает контейнер на этом образе через `up -d --no-build`. Это деплой РОВНО того образа, что прошёл staging, **без `docker build`**. Если `$SOURCE_IMAGE` не задан (дефолт) — шаг пропускается (обратная совместимость).
|
||||
- **Fail-closed провенанс-guard** (ORCH-058, Strategy B) — ПЕРЕД `docker tag`, если задан `$EXPECTED_REVISION`, хук сверяет OCI-лейбл `org.opencontainers.image.revision` у `$SOURCE_IMAGE` с `$EXPECTED_REVISION`. Несовпадение / пустой лейбл (`<no value>`) / ошибка inspect → лог + `exit 1` (FAILED → авто-rollback), **прод не трогается**. Не задан `$EXPECTED_REVISION` (дефолт) → проверка пропускается (обратная совместимость для не-self репозиториев).
|
||||
3. **Рестарт контейнера** — `docker compose --profile $COMPOSE_PROFILE up -d --no-build $TARGET_SERVICE`.
|
||||
4. **Health-цикл** — 10 попыток × 6с = до 60с. Критерий: HTTP 200 + тело содержит `"status":"ok"`.
|
||||
- **Успех** → `exit 0`, лог "Deploy SUCCESS".
|
||||
@@ -17,6 +18,17 @@
|
||||
- Если восстановился → `exit 1` (деплой провалился, откат успешен).
|
||||
- Если и откат не помог → `exit 2` (критично).
|
||||
|
||||
### Режим `--build-staging` (ORCH-058, Strategy A)
|
||||
|
||||
Пересобирает **staging-образ** из провалидированного коммита и пересоздаёт 8501, чтобы артефакт, который мы валидируем, был РОВНО тем, что позже build-once ретегается в прод (инвариант `INV-FRESH`). Собирает/пересоздаёт **только staging (8501)** — никогда прод (8500).
|
||||
|
||||
1. `docker build --build-arg GIT_SHA=$GIT_SHA -t $TARGET_IMAGE $BUILD_CONTEXT` — пересборка из host-worktree валидированного коммита; `GIT_SHA` штампуется в OCI-лейбл `org.opencontainers.image.revision`.
|
||||
2. `docker compose [--profile $COMPOSE_PROFILE] up -d --no-build $TARGET_SERVICE` — пересоздание staging на свежем образе.
|
||||
3. Health-цикл 10×6с. Провал сборки/health → `exit 1`.
|
||||
4. **`staging_check` против СВЕЖЕГО образа** (Strategy A, шаг 3 — ADR-001, AC-4) — после health хук запускает `docker exec $STAGING_CONTAINER python3 $STAGING_CHECK_PATH --base-url http://localhost:$TARGET_PORT --mode $STAGING_CHECK_MODE` (дефолт `--mode stub`, без LLM-трат). Запуск **внутри** staging-контейнера канонический (ORCH-048): suite читает реестр из собственного env контейнера, а `staging_check.py` берётся из bind-mount (`/repos/orchestrator/scripts/...`, не из образа). Это ровно тот артефакт, что позже build-once ретегается в прод → валидируем то, что промоутим (AC-4). PASS → `exit 0`; любой не-ноль (FAIL чека или safety-abort `ORCH_STAGING≠true`) → `exit 1`.
|
||||
|
||||
Запускается оркестратором на ребре `deploy-staging → deploy` (QG-под-чек `check_staging_image_fresh` → `rebuild_staging_image` пробрасывает явный staging-таргет, см. `INFRA.md`). Тот же контракт кодов выхода (0 = здоров **и** staging_check PASS).
|
||||
|
||||
### Режим `--rollback`
|
||||
|
||||
Вручную откатывает сервис на предыдущий образ из `$PREV_IMAGE_FILE`.
|
||||
@@ -31,6 +43,12 @@
|
||||
| `COMPOSE_PROFILE`| `staging` | Docker compose profile (пусто = без профиля) |
|
||||
| `PREV_IMAGE_FILE`| `$REPO/.deploy-prev-image-staging`| Файл для сохранения предыдущего образа |
|
||||
| `SOURCE_IMAGE` | _(unset)_ | Build-once (ORCH-036): провалидированный образ для retag на `$TARGET_IMAGE` перед рестартом (без rebuild). Не задан → шаг пропущен. |
|
||||
| `EXPECTED_REVISION` | _(unset)_ | Build-once (ORCH-058, Strategy B): ожидаемый git-SHA `$SOURCE_IMAGE` (лейбл `org.opencontainers.image.revision`). Задан → fail-closed guard перед `docker tag`. Не задан → проверка пропущена. |
|
||||
| `GIT_SHA` | _(unset)_ | `--build-staging` (ORCH-058, Strategy A): коммит, штампуемый в OCI-лейбл `revision` при пересборке staging-образа. |
|
||||
| `BUILD_CONTEXT` | `$REPO` | `--build-staging`: docker build context (host-worktree валидированного коммита). |
|
||||
| `STAGING_CONTAINER` | `$TARGET_SERVICE` (`orchestrator-staging`) | `--build-staging` (ORCH-058): контейнер, внутри которого `docker exec` запускает `staging_check`. |
|
||||
| `STAGING_CHECK_PATH` | `/repos/orchestrator/scripts/staging_check.py` | `--build-staging` (ORCH-058): путь к `staging_check.py` внутри контейнера (bind-mount, не образ). |
|
||||
| `STAGING_CHECK_MODE` | `stub` | `--build-staging` (ORCH-058): режим `staging_check` (`stub` — быстро, без LLM; `full-real` — дожидается аналитика). |
|
||||
| `LOG` | `/var/log/orchestrator/deploy-hook.log` | Лог-файл (fallback: `$REPO/deploy-hook.log`) |
|
||||
|
||||
> ⚠️ **Дефолт — всегда STAGING**. Прод активируется только явным переопределением env.
|
||||
|
||||
@@ -83,6 +83,8 @@ ADR `docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md` и гл
|
||||
| `ORCH_DEPLOY_HOOK_SCRIPT` / `_HOST_REPO_PATH` | путь к хук-скрипту (отн. репо) и чекаут orchestrator на хосте |
|
||||
| `ORCH_DEPLOY_PROD_SOURCE_IMAGE` | staging-образ для build-once retag на прод-тег (без rebuild) |
|
||||
| `ORCH_DEPLOY_PROD_TARGET_SERVICE` / `_TARGET_PORT` / `_TARGET_IMAGE` / `_COMPOSE_PROFILE` / `_PREV_IMAGE_FILE` | прод-цель хука + снапшот для авто-rollback |
|
||||
| `ORCH_IMAGE_FRESHNESS_ENABLED` | ORCH-058 единый kill-switch провенанса staging-образа (A+B как целое); дефолт `true`, false → legacy build-once без проверки свежести |
|
||||
| `ORCH_IMAGE_FRESHNESS_REPOS` | CSV репозиториев с реальным гейтом свежести; пусто → только self-hosting `orchestrator` |
|
||||
| `ORCH_RECONCILE_ENABLED` | kill-switch sweeper потерянных webhook (ORCH-053); дефолт `true`. **При инциденте/раскатке** — `false` глушит весь фоновый reconciler |
|
||||
| `ORCH_RECONCILE_PLANE_ENABLED` | отдельный флаг F-2 (опрос Plane API); `false` гасит только plane-ветку, F-1 продолжает работать; дефолт `true` |
|
||||
| `ORCH_RECONCILE_INTERVAL_S` | период фонового прохода reconciler, сек; дефолт `120` |
|
||||
@@ -131,6 +133,7 @@ ADR `docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md` и гл
|
||||
|
||||
**Страховки:**
|
||||
- Стадия `deploy-staging` (порт 8501) — обязательный гейт перед прод-деплоем орка. Прод-деплой недостижим, пока staging-гейт не зелёный (см. `STAGING.md`, ORCH-35). Гейт условный: реален только для self-hosting (repo=orchestrator), для остальных проектов — no-op.
|
||||
- **Свежесть staging-образа (ORCH-058):** на ребре `deploy-staging → deploy` (ПОСЛЕ merge-gate, ДО Phase A) QG-под-чек `check_staging_image_fresh` пересобирает staging-образ из валидированного коммита и пересоздаёт 8501 (Strategy A), а хук перед build-once retag fail-closed сверяет OCI-лейбл `revision` с `EXPECTED_REVISION` (Strategy B). Гарантирует: в прод промоутится РОВНО провалидированный артефакт (инцидент LESSONS_ORCH-036 п.4 — тихий промоут устаревшего образа). Сборки/recreate — ТОЛЬКО staging (8501); FAIL → откат на `development`. Условный: реален только для self-hosting.
|
||||
|
||||
**Правила для агентов при задачах ORCH:**
|
||||
1. НЕ перезапускать / не ронять прод-контейнер `orchestrator` в рамках задачи.
|
||||
|
||||
@@ -75,6 +75,27 @@ completely invisible to commands that do not pass `--profile staging`.
|
||||
docker logs -f orchestrator-staging
|
||||
```
|
||||
|
||||
## Staging-образ как источник прод-артефакта (ORCH-058)
|
||||
|
||||
Прод-деплой орка — **build-once**: хук ретегает провалидированный staging-образ
|
||||
(`orchestrator-orchestrator-staging`) на прод-тег **без rebuild** (ORCH-036). Чтобы
|
||||
в прод не попал устаревший образ (инцидент LESSONS_ORCH-036 п.4), ORCH-058 гарантирует
|
||||
свежесть staging-образа **двумя слоями** (только self-hosting):
|
||||
|
||||
- **A — пересборка staging (liveness):** на ребре `deploy-staging → deploy` (ПОСЛЕ
|
||||
merge-gate, ДО Phase A) QG-под-чек `check_staging_image_fresh` через хук
|
||||
`--build-staging` пересобирает staging-образ из worktree валидированного коммита
|
||||
(`--build-arg GIT_SHA=<sha>`, OCI-лейбл `org.opencontainers.image.revision`) и
|
||||
пересоздаёт 8501. Так валидируем РОВНО тот артефакт, что промоутится в прод.
|
||||
FAIL → откат на `development`. Сборки/recreate — **только staging (8501)**.
|
||||
- **B — fail-closed guard (safety):** прод-хук перед `docker tag` сверяет лейбл
|
||||
`revision` у `SOURCE_IMAGE` с `EXPECTED_REVISION` (пробрасывает оркестратор);
|
||||
несовпадение / пустой лейбл / ошибка inspect → `exit 1`, прод не трогается.
|
||||
|
||||
Kill-switch `ORCH_IMAGE_FRESHNESS_ENABLED` включает A+B **как целое**; область —
|
||||
`ORCH_IMAGE_FRESHNESS_REPOS` (пусто → только `orchestrator`). Детали — `DEPLOY_HOOK.md`,
|
||||
`docs/work-items/ORCH-058/06-adr/ADR-001-staging-image-provenance.md`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Task | Description |
|
||||
|
||||
7
docs/work-items/ORCH-058/00-business-request.md
Normal file
7
docs/work-items/ORCH-058/00-business-request.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Business Request: Self-deploy: retag берёт устаревший staging-образ (риск тихого регресса)
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
|
||||
## Description
|
||||
|
||||
TBD
|
||||
87
docs/work-items/ORCH-058/01-brd.md
Normal file
87
docs/work-items/ORCH-058/01-brd.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# BRD — ORCH-058: Self-deploy retag берёт устаревший staging-образ (риск тихого регресса)
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
Тип: bug / техдолг инфраструктуры self-deploy
|
||||
Источник: `docs/history/LESSONS_ORCH-036-selfdeploy.md` п.4 (самый опасный из 4 багов bootstrap ORCH-36)
|
||||
|
||||
## 1. Контекст
|
||||
|
||||
ORCH-36 сделал стадию `deploy` исполняемой для self-hosting репозитория `orchestrator`:
|
||||
- Phase B (`src/self_deploy.py::build_deploy_command`) запускает детачед host-хук
|
||||
`scripts/orchestrator-deploy-hook.sh` с параметром `SOURCE_IMAGE=orchestrator-orchestrator-staging`.
|
||||
- Хук (шаг **2b**, BUILD-ONCE, ORCH-36 BR-6) делает `docker tag $SOURCE_IMAGE → $TARGET_IMAGE`
|
||||
**без `docker build`** — «прод получает ровно тот артефакт, что прошёл staging».
|
||||
|
||||
Дизайн-предпосылка BUILD-ONCE: **staging-образ свеж и провалидирован**. На практике этой
|
||||
гарантии НЕТ.
|
||||
|
||||
## 2. Проблема (корень)
|
||||
|
||||
Конвейер **нигде не пересобирает** образ `orchestrator-orchestrator-staging` из текущего
|
||||
кода (HEAD `main` / провалидированной ветки):
|
||||
- Стадия `deploy-staging` запускает только `scripts/staging_check.py` (e2e-проверка)
|
||||
против **уже работающего** контейнера `orchestrator-staging` (8501) — что бы в нём ни
|
||||
крутилось. Сборка staging-образа — ручная операция (STAGING.md / ORCH-34), вне конвейера.
|
||||
- Между «образ собран» и «retag в прод» нет провенанс-связи с провалидированным коммитом.
|
||||
|
||||
Следствие (инцидент ORCH-36): staging-образ не пересобрали из нового `main` →
|
||||
`staging_check` прошёл против СТАРОГО кода → BUILD-ONCE retag промоутнул СТАРЫЙ образ в прод.
|
||||
Деплой «зелёный» (`result=0`, health ok), но прод молча откатился на код 2-дневной давности:
|
||||
пропал `deploy-finalizer` → задача не закрылась → бесконечная петля Phase B.
|
||||
|
||||
## 3. Почему это критично
|
||||
|
||||
> Это **самый опасный** из четырёх багов self-deploy: он **не падает**, а **тихо откатывает
|
||||
> прод**. Зелёный гейт = ложный позитив. Орк обслуживает все проекты (enduro-trails) из одного
|
||||
> прод-инстанса → тихий регресс инструмента = групповой инцидент для всех проектов.
|
||||
|
||||
Текущая защита (staging-гейт, merge-gate, health-check хука) НЕ ловит этот класс: все они
|
||||
зелёные, потому что проверяют не тот артефакт, что уезжает в прод.
|
||||
|
||||
## 4. Бизнес-цель
|
||||
|
||||
Гарантировать инвариант: **в прод никогда не промоутится образ, не собранный из
|
||||
провалидированного для данной задачи коммита; при невозможности это доказать — деплой
|
||||
fail-fast (вердикт FAILED → откат на development), а не «тихо зелёный»**.
|
||||
|
||||
## 5. Объём (scope)
|
||||
|
||||
В объёме:
|
||||
- Привязка артефакта (staging-образ → прод-retag) к провалидированному коммиту.
|
||||
- Fail-fast при рассинхроне образа и кода (никаких тихих промоутов устаревшего).
|
||||
- Условность как ORCH-35/36/43: реально только для `orchestrator`; прочие репо — no-op /
|
||||
прежнее поведение.
|
||||
- Контракт never-raise и fail-closed (на сомнении — не деплоить).
|
||||
|
||||
Вне объёма:
|
||||
- Полный авто-approve прод-деплоя (ORCH-54).
|
||||
- Изменение exit-code-контракта хука (0/1/2) и реестров `STAGE_TRANSITIONS` / `QG_CHECKS` как
|
||||
набора стадий.
|
||||
- Миграции схемы БД.
|
||||
- Деплой/рестарт **прод**-контейнера `orchestrator` (8500) в рамках задачи.
|
||||
|
||||
## 6. Бизнес-требования (BR)
|
||||
|
||||
- **BR-1.** Образ, который BUILD-ONCE retag промоутит в прод, ДОЛЖЕН соответствовать коду,
|
||||
провалидированному стадией `deploy-staging` для данной задачи (тот же git-коммит).
|
||||
- **BR-2.** Если соответствие НЕ доказуемо (staging-образ собран не из провалидированного
|
||||
коммита, либо провенанс невозможно прочесть) — деплой ОБЯЗАН fail-fast: вердикт `FAILED`,
|
||||
штатный откат на `development` (контракт БАГ-8), без рестарта прода.
|
||||
- **BR-3.** `staging_check.py` (e2e-валидация) ДОЛЖЕН прогоняться против артефакта,
|
||||
соответствующего тому же провалидированному коммиту, что уедет в прод (нельзя валидировать
|
||||
один образ, а катить другой).
|
||||
- **BR-4.** Поведение условно: реально для `orchestrator`; для прочих репозиториев — no-op /
|
||||
без регрессий прежнего синхронного деплоя.
|
||||
- **BR-5.** Выбранное решение НЕ должно приводить к вечной блокировке деплоя (если механизм
|
||||
свежести отсутствует — нужен путь, который доводит до зелёного, а не fail-fast'ит навсегда).
|
||||
- **BR-6.** Контракт never-raise: сбой проверки свежести/провенанса не должен валить
|
||||
stage_engine; на любом сомнении — fail-closed (трактуем как несоответствие).
|
||||
- **BR-7.** Документация-голден-сорс: INFRA / DEPLOY_HOOK / STAGING / architecture README +
|
||||
CHANGELOG обновляются в том же PR; решение оформляется ADR.
|
||||
|
||||
## 7. Связанные материалы
|
||||
|
||||
- `docs/history/LESSONS_ORCH-036-selfdeploy.md` (п.4 — корень)
|
||||
- `docs/architecture/adr/adr-0007-executable-self-deploy.md`, `docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md`
|
||||
- `src/self_deploy.py`, `scripts/orchestrator-deploy-hook.sh`, `src/config.py`
|
||||
- `docs/operations/STAGING.md`, `docs/operations/DEPLOY_HOOK.md`, `docs/operations/INFRA.md`
|
||||
126
docs/work-items/ORCH-058/02-trz.md
Normal file
126
docs/work-items/ORCH-058/02-trz.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# ТЗ — ORCH-058: провенанс staging-образа перед BUILD-ONCE retag в прод
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
|
||||
> Примечание: ТЗ фиксирует ТРЕБУЕМЫЕ изменения и точки в коде. **Выбор стратегии**
|
||||
> (пересборка из HEAD `main` ПЕРЕД валидацией vs. fail-fast по провенансу образа, либо их
|
||||
> комбинация) — решение **архитектора** (ADR в `06-adr/`). Ниже перечислены точки
|
||||
> касания для обеих стратегий; архитектор выбирает и при необходимости сужает.
|
||||
|
||||
## 1. Инвариант, который нужно обеспечить
|
||||
|
||||
`INV-FRESH`: образ, передаваемый хуку как `SOURCE_IMAGE` для BUILD-ONCE retag в прод,
|
||||
собран из ТОГО ЖЕ git-коммита, что прошёл `deploy-staging` для этой задачи. Если это
|
||||
недоказуемо — деплой fail-fast (`deploy_status: FAILED` → откат на `development`, БАГ-8),
|
||||
прод не трогается.
|
||||
|
||||
Якорь «провалидированного коммита» (architect фиксирует точно в ADR): SHA HEAD ветки задачи
|
||||
после merge-gate rebase на `origin/main` (то, что валидировал `deploy-staging` + merge-gate).
|
||||
|
||||
## 2. Текущее поведение (что чинить)
|
||||
|
||||
| Место | Сейчас | Проблема |
|
||||
|---|---|---|
|
||||
| `scripts/orchestrator-deploy-hook.sh` шаг 2b | `docker tag $SOURCE_IMAGE → $TARGET_IMAGE` без проверки происхождения образа | промоутит любой образ под именем `orchestrator-orchestrator-staging`, даже устаревший |
|
||||
| Стадия `deploy-staging` (`.openclaw/agents/deployer.md` + `staging_check.py`) | гоняет e2e против уже запущенного 8501, не пересобирая образ | валидирует не тот артефакт, что уедет в прод |
|
||||
| `src/self_deploy.py::build_deploy_command` | передаёт `SOURCE_IMAGE`, `TARGET_*`, `COMPOSE_PROFILE`, `PREV_IMAGE_FILE`; провенанс/SHA не передаёт | хук не знает, какой коммит ожидать |
|
||||
| `Dockerfile` | без OCI-лейбла `revision`/git-SHA | у образа нет машиночитаемого происхождения для проверки |
|
||||
|
||||
## 3. Задействованные модули `src/` и файлы
|
||||
|
||||
- `src/self_deploy.py` — основной (provenance-helpers + проброс ожидаемого SHA в команду хука).
|
||||
- `src/config.py` — новые настройки (`ORCH_`-префикс обязателен, урок ORCH-36 п.2).
|
||||
- `scripts/orchestrator-deploy-hook.sh` — fail-fast по провенансу и/или пересборка перед retag.
|
||||
- `Dockerfile` — лейбл происхождения образа (для стратегии «провенанс по labels/sha»).
|
||||
- `src/qg/checks.py` — опц. новый детерминированный под-чек свежести (если стратегия «гейт»).
|
||||
- `src/stage_engine.py` — опц. точка вызова под-чека на ребре `deploy-staging → deploy`
|
||||
(рядом с merge-gate, строки ~262–288). **Реестр `STAGE_TRANSITIONS` не меняется.**
|
||||
- `.openclaw/agents/deployer.md` — шаги стадии `deploy-staging` (если выбран rebuild-перед-валидацией).
|
||||
- `docker-compose.yml` — опц. build-args/labels для staging-сервиса (если стратегия rebuild).
|
||||
|
||||
## 4. Требуемые изменения — стратегия A (пересборка из HEAD main перед валидацией)
|
||||
|
||||
A1. Перед прогоном `staging_check.py` стадия `deploy-staging` для `orchestrator` пересобирает
|
||||
образ `orchestrator-orchestrator-staging` из провалидированного коммита (worktree ветки
|
||||
после merge-gate rebase) и пересоздаёт контейнер 8501 на свежем образе.
|
||||
A2. `staging_check.py` гоняется против свежего контейнера; на `SUCCESS` ровно ЭТОТ образ
|
||||
становится `SOURCE_IMAGE` для прод-retag (loop closed).
|
||||
A3. Детерминированно (без LLM в критическом пути): сборку/recreate выполняет код стадии или
|
||||
host-хук в staging-режиме, не агент-деплойер «руками».
|
||||
A4. Безопасность: операция трогает ТОЛЬКО staging (8501), НИКОГДА прод (8500).
|
||||
|
||||
## 5. Требуемые изменения — стратегия B (fail-fast по провенансу образа)
|
||||
|
||||
B1. `Dockerfile`: добавить лейбл происхождения, напр.
|
||||
`LABEL org.opencontainers.image.revision=$GIT_SHA` через `ARG GIT_SHA` (build-arg).
|
||||
B2. Сборка staging-образа (ручная или из стратегии A) проставляет `GIT_SHA` = коммит сборки.
|
||||
B3. `src/self_deploy.py::build_deploy_command`: вычислить ожидаемый SHA провалидированного
|
||||
коммита и пробросить в команду хука новым env (напр. `EXPECTED_REVISION=<sha>`).
|
||||
Новый pure-helper, напр. `expected_revision(repo, branch) -> str` (never-raise).
|
||||
B4. `scripts/orchestrator-deploy-hook.sh` шаг 2b: ПЕРЕД `docker tag` прочитать лейбл
|
||||
`$SOURCE_IMAGE` (`docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}'`)
|
||||
и сравнить с `$EXPECTED_REVISION`. Несовпадение / пустой лейбл / пустой ожидаемый SHA →
|
||||
`log` + `exit 1` (fail-fast). Поведение обратносовместимо: при незаданном
|
||||
`EXPECTED_REVISION` — текущее поведение (без проверки), чтобы не сломать не-self репо.
|
||||
B5. exit 1 хука уже маппится `map_exit_code_to_status → FAILED` (контракт не меняется),
|
||||
Phase C пишет `14-deploy-log.md` `deploy_status: FAILED` → откат на `development` (БАГ-8).
|
||||
|
||||
## 6. Требуемые изменения — опц. под-гейт (если архитектор выберет gate-side для B)
|
||||
|
||||
- Новый детерминированный (без LLM) под-чек, напр. `check_staging_image_fresh`, по образцу
|
||||
`check_branch_mergeable` (ORCH-043): pure verdict-logic + условность (`self_deploy_applies`
|
||||
/ `is_self_hosting_repo`), never-raise, для прочих репо → `(True, "N/A")`.
|
||||
- Вызов на ребре `deploy-staging → deploy` ПЕРЕД Phase A (рядом с merge-gate, `stage_engine`
|
||||
~268–288). FAIL → откат на `development` (как merge-gate). Реестр стадий неизменен —
|
||||
это под-гейт ребра, не новая стадия.
|
||||
- Если выбран чисто хуковый fail-fast (раздел 5) — под-гейт не нужен.
|
||||
|
||||
## 7. Изменения API
|
||||
|
||||
Нет. Эндпоинты (`/health`, `/status`, `/queue`, `/webhook/*`) не меняются. Опц.: в снимок
|
||||
`GET /queue` можно добавить диагностическое поле о свежести образа — НЕ обязательно.
|
||||
|
||||
## 8. Изменения схемы БД
|
||||
|
||||
Нет. Состояние deploy — sentinel-файлы (`.deploy-state-<repo>/<wi>/`, ORCH-36). Миграции
|
||||
запрещены (как ORCH-36/43/53).
|
||||
|
||||
## 9. Конфигурация (`src/config.py`, ВСЕ с префиксом `ORCH_`)
|
||||
|
||||
Кандидаты (architect финализирует имена и дефолты):
|
||||
- `image_freshness_enabled: bool = True` — kill-switch проверки (поэтапный раскат).
|
||||
- `image_freshness_repos: str = ""` — CSV; пусто → только self-hosting (как `self_deploy_repos`).
|
||||
- (для стратегии B) проброс `EXPECTED_REVISION` строится в `build_deploy_command`, отдельной
|
||||
настройки может не требоваться.
|
||||
- (для стратегии A) при необходимости — имя/тег staging-образа уже есть
|
||||
(`deploy_prod_source_image`).
|
||||
|
||||
Урок ORCH-36 п.2: любая настройка, читаемая pydantic Settings, ОБЯЗАНА иметь префикс `ORCH_`.
|
||||
|
||||
## 10. Новые QG checks (если применимо)
|
||||
|
||||
- Опц. `check_staging_image_fresh` (см. §6) — добавить в реестр `QG_CHECKS` и в
|
||||
snapshot-тест реестра (`tests/test_qg_registry_snapshot.py`). Только если выбран gate-side.
|
||||
|
||||
## 11. Артефакты pipeline (создать/обновить В ТОМ ЖЕ PR)
|
||||
|
||||
- `06-adr/ADR-001-<slug>.md` — выбор стратегии (A / B / A+B), якорь «провалидированного
|
||||
коммита», точки fail-fast, условность, never-raise, отсутствие deadlock (BR-5).
|
||||
- `docs/operations/DEPLOY_HOOK.md` — описание провенанс-проверки / пересборки и новых env.
|
||||
- `docs/operations/STAGING.md` — как и когда пересобирается staging-образ в конвейере.
|
||||
- `docs/operations/INFRA.md` — обновить топологию/риск self-deploy (закрыт п.4 каскада).
|
||||
- `docs/architecture/README.md` — секция ORCH-36/58 (свежесть артефакта в BUILD-ONCE).
|
||||
- `CHANGELOG.md` — запись ORCH-058.
|
||||
- При выборе стратегии A: bootstrap-чеклист (урок ORCH-36 «сквозной»: реальный staging-прогон
|
||||
до мержа).
|
||||
|
||||
## 12. Инварианты / ограничения (self-hosting safety)
|
||||
|
||||
- Никогда не рестартовать/ронять прод 8500 в рамках задачи (CLAUDE.md). Любая сборка/recreate —
|
||||
только staging 8501.
|
||||
- Никогда не пушить/форс-пушить `main` (как merge-gate).
|
||||
- Контракты НЕ меняются: exit-code хука (0/1/2), `map_exit_code_to_status`,
|
||||
`check_deploy_status`/`_parse_deploy_status`, БАГ-8 rollback, terminal-sync, merge-gate.
|
||||
- Fail-closed: на любом сомнении (нет лейбла, нет ожидаемого SHA, ошибка inspect) —
|
||||
трактовать как несоответствие → FAILED, никогда не промоутить «на авось».
|
||||
- never-raise: helpers и под-чек не должны пробрасывать исключение в stage_engine.
|
||||
71
docs/work-items/ORCH-058/03-acceptance-criteria.md
Normal file
71
docs/work-items/ORCH-058/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Критерии приёмки — ORCH-058
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
|
||||
Критерии сформулированы вокруг инварианта `INV-FRESH` и **не зависят** от выбранной
|
||||
архитектором стратегии (A — пересборка, B — fail-fast по провенансу, A+B). Каждый — с
|
||||
чётким условием PASS/FAIL.
|
||||
|
||||
## AC-1 — Соответствие артефакта коду (центральный инвариант)
|
||||
- PASS: образ, который BUILD-ONCE retag промоутит в прод (`SOURCE_IMAGE`), доказуемо собран
|
||||
из коммита, провалидированного стадией `deploy-staging` для этой задачи.
|
||||
- FAIL: в прод может уехать образ, собранный не из провалидированного коммита.
|
||||
|
||||
## AC-2 — Fail-fast при рассинхроне (никаких тихих зелёных)
|
||||
- PASS: если staging-образ собран НЕ из провалидированного коммита (или провенанс нечитаем),
|
||||
деплой завершается `deploy_status: FAILED` и откатом на `development` (БАГ-8); прод НЕ
|
||||
рестартуется на устаревший образ.
|
||||
- FAIL: при рассинхроне деплой завершается `SUCCESS` / «зелёным», прод тихо откатывается.
|
||||
|
||||
## AC-3 — Fail-closed на сомнении
|
||||
- PASS: при отсутствии лейбла происхождения, пустом ожидаемом SHA, ошибке `docker image
|
||||
inspect` или любой неоднозначности — трактуется как несоответствие → FAILED (никогда не
|
||||
промоутится «на авось»).
|
||||
- FAIL: сомнительный/непроверяемый случай трактуется как «свежий» и промоутится.
|
||||
|
||||
## AC-4 — Валидация и промоут — один и тот же артефакт
|
||||
- PASS: `staging_check.py` прогоняется против образа/контейнера, соответствующего тому же
|
||||
провалидированному коммиту, который затем уезжает в прод.
|
||||
- FAIL: валидируется один образ, а в прод retag'ается другой.
|
||||
|
||||
## AC-5 — Условность (self-hosting only)
|
||||
- PASS: проверка/пересборка реальна только для `orchestrator` (и репо из `image_freshness_repos`,
|
||||
если задан); для прочих репо — no-op, синхронный деплой не-self репо без регрессий.
|
||||
- FAIL: логика срабатывает для не-self репозиториев или ломает их деплой.
|
||||
|
||||
## AC-6 — Никакого deadlock деплоя (BR-5)
|
||||
- PASS: при штатном прогоне (staging-образ корректно отражает провалидированный коммит)
|
||||
деплой доходит до `SUCCESS` и `deploy → done`; механизм свежести не блокирует валидный
|
||||
деплой навсегда.
|
||||
- FAIL: валидный деплой вечно fail-fast'ится / задача зависает на `deploy`.
|
||||
|
||||
## AC-7 — Контракты не изменены
|
||||
- PASS: `STAGE_TRANSITIONS` (набор стадий), exit-code-контракт хука (0/1/2),
|
||||
`map_exit_code_to_status`, `check_deploy_status`/`_parse_deploy_status`, БАГ-8 rollback,
|
||||
terminal-sync, merge-gate — без изменений; схема БД без миграций.
|
||||
- FAIL: затронут любой из перечисленных контрактов или добавлена миграция БД.
|
||||
|
||||
## AC-8 — never-raise
|
||||
- PASS: сбой проверки свежести/провенанса (битый образ, ssh/docker error, отсутствующий
|
||||
worktree) не пробрасывает исключение в `stage_engine`; возвращается безопасный вердикт.
|
||||
- FAIL: исключение из новой логики всплывает и валит обработку стадии.
|
||||
|
||||
## AC-9 — Self-hosting safety
|
||||
- PASS: новая логика НЕ рестартует/не роняет прод-контейнер `orchestrator` (8500) и не
|
||||
пушит/форс-пушит `main`; любые сборки/recreate — только staging (8501).
|
||||
- FAIL: нарушено любое из ограничений выше.
|
||||
|
||||
## AC-10 — Конфигурация и kill-switch
|
||||
- PASS: новые настройки имеют префикс `ORCH_`; есть kill-switch (напр. `image_freshness_enabled`)
|
||||
для поэтапного раската; при выключенном флаге — прежнее поведение.
|
||||
- FAIL: настройка без `ORCH_`-префикса (не читается pydantic) или нет способа отключить.
|
||||
|
||||
## AC-11 — Документация (golden source)
|
||||
- PASS: в том же PR обновлены DEPLOY_HOOK.md, STAGING.md, INFRA.md, architecture/README.md,
|
||||
CHANGELOG.md и заведён ADR `06-adr/ADR-001-*`.
|
||||
- FAIL: функционал изменён, документация/ADR не обновлены (→ reviewer REQUEST_CHANGES).
|
||||
|
||||
## AC-12 — Тесты зелёные
|
||||
- PASS: `pytest tests/ -q` зелёный, включая новые тесты из `04-test-plan.yaml` и
|
||||
snapshot-тест реестра QG (если добавлен под-чек).
|
||||
- FAIL: любой тест из плана красный или регрессия существующих.
|
||||
124
docs/work-items/ORCH-058/04-test-plan.yaml
Normal file
124
docs/work-items/ORCH-058/04-test-plan.yaml
Normal file
@@ -0,0 +1,124 @@
|
||||
work_item: ORCH-058
|
||||
description: >
|
||||
Провенанс staging-образа перед BUILD-ONCE retag в прод. Тесты покрывают инвариант
|
||||
INV-FRESH: соответствие промоутируемого образа провалидированному коммиту, fail-fast
|
||||
и fail-closed при рассинхроне, условность self-hosting, never-raise, неизменность
|
||||
контрактов. Часть кейсов помечена strategy-зависимыми (A=пересборка, B=fail-fast по
|
||||
провенансу) — финальный набор подтверждает архитектор в ADR; пишутся тесты для
|
||||
выбранной стратегии.
|
||||
tests:
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: >
|
||||
Pure provenance-verdict: SHA образа == ожидаемый SHA -> свежий (PASS).
|
||||
Совпадающие revision дают вердикт "соответствует".
|
||||
module: tests/test_image_freshness.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: >
|
||||
Pure provenance-verdict: SHA образа != ожидаемый SHA -> НЕ свежий ->
|
||||
вердикт несоответствия (вход для fail-fast).
|
||||
module: tests/test_image_freshness.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: >
|
||||
Fail-closed: пустой/отсутствующий лейбл образа ИЛИ пустой ожидаемый SHA ->
|
||||
трактуется как несоответствие (никогда не "свежий по умолчанию").
|
||||
module: tests/test_image_freshness.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: >
|
||||
never-raise: provenance-helper при docker/ssh/inspect ошибке или отсутствующем
|
||||
worktree возвращает безопасный вердикт (несоответствие), не пробрасывает исключение.
|
||||
module: tests/test_image_freshness.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: >
|
||||
Условность: для не-self репозитория проверка свежести = no-op (True/"N/A");
|
||||
для orchestrator (или репо из image_freshness_repos) — реальна.
|
||||
module: tests/test_image_freshness.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: >
|
||||
[Стратегия B] build_deploy_command пробрасывает EXPECTED_REVISION=<sha>
|
||||
в remote-команду хука рядом с SOURCE_IMAGE; формат env корректен (shlex-quote).
|
||||
module: tests/test_deploy_build_once.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: >
|
||||
[Стратегия B] Хук содержит ветку fail-fast: при заданном EXPECTED_REVISION и
|
||||
несовпадении revision лейбла SOURCE_IMAGE -> exit 1 ПЕРЕД docker tag; при пустом
|
||||
EXPECTED_REVISION -> обратносовместимое поведение (без проверки). Статическая
|
||||
проверка текста scripts/orchestrator-deploy-hook.sh (паттерн test_deploy_build_once).
|
||||
module: tests/test_deploy_hook_provenance.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: >
|
||||
[Стратегия B] Dockerfile объявляет ARG GIT_SHA и LABEL
|
||||
org.opencontainers.image.revision=$GIT_SHA (статическая проверка текста Dockerfile).
|
||||
module: tests/test_deploy_hook_provenance.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-09
|
||||
type: unit
|
||||
description: >
|
||||
Маппинг контракта: exit 1 хука (fail-fast по провенансу) ->
|
||||
map_exit_code_to_status == "FAILED" (контракт ORCH-36 не изменён).
|
||||
module: tests/test_deploy_hook_mapping.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-10
|
||||
type: integration
|
||||
description: >
|
||||
Stale-образ -> fail-fast end-to-end: на ребре deploy-staging->deploy при
|
||||
несоответствии образа Phase B/хук дают FAILED -> advance_stage откатывает на
|
||||
development (БАГ-8), прод не "зелёный". Прод-рестарт замокан.
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-11
|
||||
type: integration
|
||||
description: >
|
||||
Свежий образ -> happy path: соответствие revision -> деплой доходит до SUCCESS и
|
||||
deploy->done; механизм свежести не блокирует валидный деплой (anti-deadlock, AC-6).
|
||||
Host-процесс/хук замокан.
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-12
|
||||
type: unit
|
||||
description: >
|
||||
[Если выбран gate-side] check_staging_image_fresh зарегистрирован в QG_CHECKS;
|
||||
snapshot-тест реестра обновлён и зелёный.
|
||||
module: tests/test_qg_registry_snapshot.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-13
|
||||
type: unit
|
||||
description: >
|
||||
Конфигурация: новые настройки (image_freshness_enabled / image_freshness_repos)
|
||||
читаются с префиксом ORCH_ и имеют дефолты; kill-switch off -> прежнее поведение.
|
||||
module: tests/test_config.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-14
|
||||
type: unit
|
||||
description: >
|
||||
Регрессия контрактов: STAGE_TRANSITIONS (набор стадий) и exit-code-контракт хука
|
||||
(0/1/2) не изменены существующими правками.
|
||||
module: tests/test_stages.py
|
||||
expected: PASS
|
||||
@@ -0,0 +1,209 @@
|
||||
# ADR-001 (ORCH-058): Провенанс staging-образа перед BUILD-ONCE retag в прод
|
||||
|
||||
## Статус
|
||||
Accepted (design) — реализация в ветке `feature/ORCH-058-self-deploy-retag-staging`.
|
||||
Метка: `arch:major-change` (новая deploy-safety модель + новый QG + новый режим хука).
|
||||
|
||||
## Контекст
|
||||
|
||||
ORCH-36 сделал стадию `deploy` исполняемой для self-hosting (`orchestrator`): Phase B
|
||||
(`self_deploy.build_deploy_command`) запускает детачед host-хук, который шагом **2b**
|
||||
(BUILD-ONCE) делает `docker tag $SOURCE_IMAGE → $TARGET_IMAGE` **без `docker build`** —
|
||||
«прод получает ровно тот артефакт, что прошёл staging».
|
||||
|
||||
Дизайн-предпосылка BUILD-ONCE: **staging-образ свеж и собран из провалидированного кода**.
|
||||
На практике этой гарантии НЕТ (BRD §2):
|
||||
|
||||
- Стадия `deploy-staging` запускает только `scripts/staging_check.py` против **уже
|
||||
работающего** контейнера 8501 — что бы в нём ни крутилось. Пересборка staging-образа —
|
||||
ручная операция (STAGING.md / ORCH-34), вне конвейера.
|
||||
- Между «образ собран» и «retag в прод» нет провенанс-связи с провалидированным коммитом.
|
||||
|
||||
Инцидент (LESSONS_ORCH-036 п.4 — **самый опасный** из 4 багов bootstrap): staging-образ
|
||||
не пересобрали из нового `main` → `staging_check` прошёл против СТАРОГО кода → BUILD-ONCE
|
||||
retag промоутнул СТАРЫЙ образ в прод. Деплой «зелёный» (`result=0`, health ok), но прод
|
||||
**молча откатился** на код 2-дневной давности. Орк обслуживает все проекты из одного
|
||||
прод-инстанса → тихий регресс инструмента = групповой инцидент.
|
||||
|
||||
Текущая защита (staging-гейт, merge-gate, health-check хука) этот класс НЕ ловит: все
|
||||
гейты зелёные, потому что проверяют **не тот артефакт**, что уезжает в прод.
|
||||
|
||||
## Инвариант, который нужно обеспечить
|
||||
|
||||
`INV-FRESH` (ТЗ §1): образ, передаваемый хуку как `SOURCE_IMAGE` для BUILD-ONCE retag в
|
||||
прод, собран из ТОГО ЖЕ git-коммита, что прошёл `deploy-staging` для этой задачи. Если
|
||||
это недоказуемо — деплой fail-fast (`deploy_status: FAILED` → откат на `development`,
|
||||
БАГ-8), прод не трогается.
|
||||
|
||||
### Якорь «провалидированного коммита»
|
||||
|
||||
**SHA = `git rev-parse HEAD` в worktree ветки задачи ПОСЛЕ merge-gate** (т.е. после
|
||||
возможного `auto_rebase_onto_main` + `push --force-with-lease`). Это ровно тот tree,
|
||||
который merge-gate ре-тестировал зелёным и который сольётся в `main`. Один helper
|
||||
`validated_revision(repo, branch)` (never-raise) вычисляет SHA и служит ЕДИНСТВЕННЫМ
|
||||
источником и для штампа сборки (Стратегия A), и для ожидаемого ревижна (Стратегия B) —
|
||||
два потребителя одного якоря не могут разойтись.
|
||||
|
||||
## Решение: A + B (defense in depth)
|
||||
|
||||
Ни одна стратегия по отдельности не закрывает задачу:
|
||||
|
||||
- **B в одиночку** (fail-fast по провенансу) делает тихий промоут структурно невозможным,
|
||||
НО если staging-образ устарел — fail-fast'ит **навсегда** (нет пути к зелёному без
|
||||
ручной пересборки) → нарушает BR-5 / AC-6 (deadlock), воспроизводит ровно тот
|
||||
bootstrap-разрыв, который мы устраняем.
|
||||
- **A в одиночку** (пересборка из провалидированного коммита) закрывает петлю «валидируем =
|
||||
промоутим», НО не имеет утверждения В МОМЕНТ retag: гонка/отключение/сбой пересборки
|
||||
снова даст тихий промоут.
|
||||
|
||||
Поэтому берём **обе**, как взаимодополняющие слои:
|
||||
|
||||
### Стратегия A — пересборка staging-образа из провалидированного коммита (liveness, AC-4/AC-6)
|
||||
|
||||
Для self-hosting на ребре `deploy-staging → deploy`, **после merge-gate** (когда
|
||||
валидированный HEAD финализирован) и **до Phase A**, детерминированный код:
|
||||
|
||||
1. Вычисляет `sha = validated_revision(repo, branch)`.
|
||||
2. Пересобирает `orchestrator-orchestrator-staging` из **worktree ветки** (build-context =
|
||||
валидированный tree) с `--build-arg GIT_SHA=<sha>` и пересоздаёт контейнер 8501 на
|
||||
свежем образе (`--no-build`).
|
||||
3. Прогоняет `staging_check.py --mode stub` против свежего 8501.
|
||||
|
||||
Результат: ровно ЭТОТ образ (с лейблом `revision=<sha>`) становится `SOURCE_IMAGE` для
|
||||
прод-retag → петля замкнута, валидируем и промоутим один артефакт (AC-4). Пересборка/
|
||||
recreate трогают **ТОЛЬКО staging (8501)**, НИКОГДА прод (8500) (AC-9).
|
||||
|
||||
Исполнение — через host (ssh, синхронно): docker CLI / compose доступны на ХОСТЕ, не в
|
||||
контейнере (Dockerfile ставит только `openssh-client git`; staging_check уже гоняется
|
||||
`docker exec`-ом на хосте). Новый режим хука `--build-staging` (см. ниже) выполняет сборку
|
||||
и recreate. Синхронный ssh достаточен — рестарт staging не убивает прод-worker (в отличие
|
||||
от Phase B, где нужен detached + finalizer).
|
||||
|
||||
Реализуется как **детерминированный QG-под-чек `check_staging_image_fresh`** (по образцу
|
||||
`check_branch_mergeable`, ORCH-043): pure-условность + never-raise; для прочих репо →
|
||||
`(True, "N/A")`. Регистрируется в `QG_CHECKS` и в `tests/test_qg_registry_snapshot.py`.
|
||||
Вызов — на ребре через `_handle_image_freshness(...)` в `stage_engine` (рядом с
|
||||
`_handle_merge_gate`, ПОСЛЕ него, ДО Phase A). FAIL → откат на `development` + release
|
||||
merge-lease (как merge-gate). **`STAGE_TRANSITIONS` (набор стадий) НЕ меняется** — это
|
||||
под-гейт ребра.
|
||||
|
||||
### Стратегия B — fail-closed провенанс-guard в хуке (safety, AC-1/AC-2/AC-3)
|
||||
|
||||
1. **`Dockerfile`**: `ARG GIT_SHA` + `LABEL org.opencontainers.image.revision=$GIT_SHA`.
|
||||
Без build-arg лейбл пустой → fail-closed на стороне B (см. ниже).
|
||||
2. **`build_deploy_command`**: вычисляет `EXPECTED_REVISION = validated_revision(repo,
|
||||
branch)` и пробрасывает в env команды хука.
|
||||
3. **`orchestrator-deploy-hook.sh` шаг 2b** — ПЕРЕД `docker tag`:
|
||||
- читает лейбл `SOURCE_IMAGE`:
|
||||
`docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$SOURCE_IMAGE"`;
|
||||
- сравнивает с `$EXPECTED_REVISION`;
|
||||
- несовпадение / пустой лейбл / пустой `EXPECTED_REVISION` / ошибка inspect →
|
||||
`log` + `exit 1` (**fail-closed**, никогда не промоутить «на авось»).
|
||||
- **Обратная совместимость:** при НЕзаданном `EXPECTED_REVISION` — текущее поведение
|
||||
(проверка пропускается), чтобы не сломать не-self репо и legacy-вызовы.
|
||||
4. `exit 1` уже маппится `map_exit_code_to_status → FAILED` (контракт не меняется), Phase C
|
||||
пишет `deploy_status: FAILED` → откат на `development` (БАГ-8). Прод не рестартуется на
|
||||
устаревший образ — guard срабатывает ДО `docker tag`/restart.
|
||||
|
||||
### Новый режим хука `--build-staging` (для Стратегии A)
|
||||
|
||||
`orchestrator-deploy-hook.sh --build-staging` (env: `GIT_SHA`, `BUILD_CONTEXT` = host-путь
|
||||
worktree, `TARGET_IMAGE=orchestrator-orchestrator-staging`, `TARGET_SERVICE`,
|
||||
`COMPOSE_PROFILE=staging`, `TARGET_PORT=8501`):
|
||||
`docker build --build-arg GIT_SHA=<sha> -t <TARGET_IMAGE> <BUILD_CONTEXT>` →
|
||||
`docker compose --profile staging up -d --no-build orchestrator-staging` → health 8501.
|
||||
Тот же exit-code-контракт (0=ok). Дефолты режима — STAGING-safe (как у `--deploy`).
|
||||
|
||||
Host-путь build-context выводится из container-пути worktree заменой
|
||||
`repos_dir → host_repos_dir` (как `host_state_dir` в `self_deploy.py`); требуется
|
||||
производный helper host-worktree-пути (или новая настройка `ORCH_HOST_WORKTREES_DIR`).
|
||||
|
||||
## Конфигурация (`src/config.py`, все с префиксом `ORCH_` — урок ORCH-36 п.2)
|
||||
|
||||
- `image_freshness_enabled: bool = True` — **единый** kill-switch ВСЕЙ фичи (A и B вместе).
|
||||
`False` → ни пересборки, ни проброса `EXPECTED_REVISION` → поведение ровно как ORCH-36
|
||||
(BUILD-ONCE без guard). A и B включаются/выключаются **как одно целое**, чтобы не было
|
||||
опасной полу-конфигурации «B без A» (вечный fail-fast).
|
||||
- `image_freshness_repos: str = ""` — CSV; пусто → только self-hosting (как
|
||||
`self_deploy_repos` / `merge_gate_repos`).
|
||||
|
||||
> **Инвариант конфигурации (AC-6):** B активен ТОЛЬКО когда активен A. По умолчанию
|
||||
> (`image_freshness_enabled=True`) валидный деплой всегда доходит до зелёного (A пересобирает
|
||||
> → лейбл == EXPECTED → B пропускает). Полное выключение → legacy ORCH-36 поведение.
|
||||
|
||||
## Порядок на ребре `deploy-staging → deploy` (self-hosting)
|
||||
|
||||
1. `check_staging_status` (существующий) — первичный staging-вердикт агента (smoke,
|
||||
что staging-инфра жива).
|
||||
2. merge-gate `check_branch_mergeable` (существующий) — финализирует валидированный HEAD
|
||||
(rebase если позади, ре-тест зелёный, lease HELD). DEFER на busy-lock → возврат без
|
||||
пересборки.
|
||||
3. **`check_staging_image_fresh` (НОВЫЙ, Стратегия A)** — пересборка из валидированного
|
||||
HEAD + recreate 8501 + `staging_check`. FAIL → откат на `development` + release lease.
|
||||
4. Phase A (существующий) → запрос approve.
|
||||
5. Phase B (human Approved) → `build_deploy_command` с `EXPECTED_REVISION` → хук-guard (B)
|
||||
→ BUILD-ONCE retag только при совпадении → restart прод → Phase C finalizer.
|
||||
|
||||
> Двойной прогон `staging_check` (агент на стадии + код на шаге 3) — **намеренный**: первый
|
||||
> валидирует УЖЕ работающий (потенциально устаревший) 8501 как soft pre-check; авторитетный
|
||||
> — шаг 3 против СВЕЖЕГО образа, который и уедет в прод. `--mode stub` быстр и без LLM-трат.
|
||||
|
||||
## Контракты, которые НЕ меняются (AC-7)
|
||||
|
||||
`STAGE_TRANSITIONS` (набор стадий), exit-code-контракт хука (0/1/2),
|
||||
`map_exit_code_to_status`, `check_deploy_status` / `_parse_deploy_status` (frontmatter-only),
|
||||
БАГ-8 rollback, terminal-sync `deploy → done`, merge-gate (ORCH-43), Phase A/B/C ORCH-36.
|
||||
**Схема БД — без миграций** (состояние свежести не персистится в БД; провенанс живёт в
|
||||
лейбле образа). Добавление `check_staging_image_fresh` в `QG_CHECKS` — ожидаемое расширение
|
||||
реестра (ТЗ §10), не входит в замороженный список AC-7.
|
||||
|
||||
## Last-line-of-defence / fail-closed (AC-2/AC-3)
|
||||
|
||||
Даже если A отключена/проиграла гонку/сбойнула — **B (хук-guard) делает тихий промоут
|
||||
устаревшего образа структурно невозможным**: рассинхрон лейбла и `EXPECTED_REVISION` →
|
||||
`exit 1` ДО retag → FAILED → откат. На любом сомнении (нет лейбла, пустой ожидаемый SHA,
|
||||
ошибка inspect) — трактуется как несоответствие. Прод никогда не трогается «на авось».
|
||||
|
||||
## never-raise (AC-8)
|
||||
|
||||
`validated_revision`, `rebuild_staging_image`, `check_staging_image_fresh`,
|
||||
`build_deploy_command` (проброс EXPECTED) — все защищены try/except, любая ошибка → безопасный
|
||||
вердикт (для A-под-чека: `(False, reason)` с release lease; пустой `EXPECTED_REVISION` на
|
||||
сомнении → B fail-closed). Исключение никогда не всплывает в `stage_engine`.
|
||||
|
||||
## Последствия
|
||||
|
||||
**Плюсы**
|
||||
- Класс «тихого регресса прод» закрыт структурно (B), а валидный деплой всегда доходит до
|
||||
зелёного (A) — bootstrap-разрыв «ручная пересборка staging» устранён.
|
||||
- Валидируем и промоутим один и тот же артефакт (AC-4); провенанс машиночитаем (лейбл).
|
||||
- Единый kill-switch, поэтапный раскат, условность только для self-hosting — без регрессий
|
||||
для не-self репо.
|
||||
|
||||
**Минусы / ограничения**
|
||||
- Латентность ребра растёт: +`docker build` staging + recreate 8501 + повторный
|
||||
`staging_check` перед Phase A. Приемлемо (выполняется в monitor-треде, как merge-gate
|
||||
re-test; bounded timeouts).
|
||||
- `staging_check` гоняется дважды (soft pre-check агента + авторитетный код) — осознанная
|
||||
плата за AC-4. Возможная будущая оптимизация: облегчить шаг 3 до health+revision-smoke,
|
||||
если merge-gate re-test признать достаточным для кода.
|
||||
- Требуется host-доступ к `docker build`/`compose` под slin (как для `--deploy`) и writable
|
||||
build-context (worktree) — заложено инфра-требованиями (07).
|
||||
- Новая под-компонента (QG `check_staging_image_fresh` + режим хука `--build-staging`) →
|
||||
`arch:major-change`.
|
||||
|
||||
## Альтернативы (отклонены)
|
||||
|
||||
- **Только B.** Deadlock без авто-пересборки (BR-5/AC-6). ❌
|
||||
- **Только A.** Нет утверждения в момент retag → гонка/отключение снова даёт тихий промоут
|
||||
(AC-2/AC-3). ❌
|
||||
- **Rebuild в хуке на Phase B (прод-сторона).** Уничтожает BUILD-ONCE (прод-rebuild) и
|
||||
промоутит образ, который staging-e2e никогда не валидировал. ❌
|
||||
- **Rebuild напрямую из контейнера через docker.sock.** В образе нет docker CLI/compose;
|
||||
staging-операции и так host-side (ssh). ❌
|
||||
|
||||
## Связанные ADR
|
||||
Глобальный: `docs/architecture/adr/adr-0008-staging-image-provenance.md`.
|
||||
`adr-0007-executable-self-deploy` (ORCH-36, BUILD-ONCE), `adr-0006-merge-gate` (ORCH-43,
|
||||
образец edge-под-гейта), `adr-0003-staging-gate` (ORCH-35, условность), `adr-0005`
|
||||
(run-as-host-uid).
|
||||
71
docs/work-items/ORCH-058/07-infra-requirements.md
Normal file
71
docs/work-items/ORCH-058/07-infra-requirements.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Инфра-требования — ORCH-058
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
|
||||
Топология не меняется (тот же сервер mva154, те же контейнеры 8500/8501, общая БД). Меняется
|
||||
**что делает self-deploy на ребре `deploy-staging → deploy`** для self-hosting. Полная
|
||||
топология/риски — `docs/operations/INFRA.md` (обновить в том же PR).
|
||||
|
||||
## IR-1. Host-сборка staging-образа (Стратегия A)
|
||||
|
||||
Шаг свежести пересобирает `orchestrator-orchestrator-staging` на ХОСТЕ (docker CLI/compose
|
||||
есть на хосте, НЕ в контейнере — образ ставит только `openssh-client git`). Требуется:
|
||||
|
||||
- Рабочий ssh `slin@127.0.0.1` (уже есть, ORCH-36 / LESSONS п.1–2: passwd-запись uid 1000,
|
||||
ключ смонтирован, `ORCH_DEPLOY_*` префиксы).
|
||||
- На хосте под `slin` доступны `docker build` и `docker compose --profile staging`
|
||||
(recreate 8501). Группа docker (`group_add: "999"` / host-доступ к `docker.sock`) — уже
|
||||
настроено.
|
||||
- **Build-context = host-путь worktree** валидированной ветки
|
||||
(`/home/slin/repos/_wt/<repo>/<branch-slug>`), читаемый под `slin`. Worktree уже
|
||||
создаётся launcher'ом/merge-gate под slin (ADR-0005 run-as-host-uid) — права ок.
|
||||
- Лог-директория хука writable под slin (`/var/log/orchestrator`, LESSONS п.3) — уже.
|
||||
|
||||
## IR-2. Вывод host-пути worktree
|
||||
|
||||
В контейнере worktree виден как `ORCH_WORKTREES_DIR=/repos/_wt/...`; на хосте — как
|
||||
`/home/slin/repos/_wt/...`. Маппинг = замена `repos_dir → host_repos_dir` (как
|
||||
`self_deploy.host_state_dir`). Реализация: производный helper host-worktree-пути, либо новая
|
||||
настройка `ORCH_HOST_WORKTREES_DIR` (дефолт `/home/slin/repos/_wt`). Без неё — деривация из
|
||||
`host_repos_dir`.
|
||||
|
||||
## IR-3. OCI-лейбл происхождения (Стратегия B)
|
||||
|
||||
`Dockerfile`: `ARG GIT_SHA` + `LABEL org.opencontainers.image.revision=$GIT_SHA`. Сборки БЕЗ
|
||||
build-arg (ручные/legacy) дают пустой лейбл → B fail-closed (это by design, не регрессия:
|
||||
прод-retag без доказуемого провенанса должен падать). Любой существующий способ сборки прод/
|
||||
staging-образа (CI, ручной) при включённой фиче ОБЯЗАН передавать `--build-arg GIT_SHA=<sha>`,
|
||||
иначе деплой задачи fail-fast'нется на guard. Шаг A это делает автоматически.
|
||||
|
||||
## IR-4. ssh-режим хука `--build-staging`
|
||||
|
||||
Новый режим `orchestrator-deploy-hook.sh --build-staging` запускается синхронно (рестарт
|
||||
staging безопасен, detached/finalizer не нужны — в отличие от Phase B прод). Дефолты режима —
|
||||
STAGING-safe (`TARGET_PORT=8501`, `--profile staging`). Прод (8500) этим режимом НЕ
|
||||
затрагивается.
|
||||
|
||||
## IR-5. Конфигурация (env, префикс `ORCH_`)
|
||||
|
||||
- `ORCH_IMAGE_FRESHNESS_ENABLED` (дефолт true) — единый kill-switch A+B.
|
||||
- `ORCH_IMAGE_FRESHNESS_REPOS` (дефолт пусто → self-hosting).
|
||||
- (опц.) `ORCH_HOST_WORKTREES_DIR` (дефолт `/home/slin/repos/_wt`).
|
||||
|
||||
`EXPECTED_REVISION` для хука строится в `build_deploy_command` — отдельной настройки не
|
||||
требует. `deploy_prod_source_image` (= `orchestrator-orchestrator-staging`) переиспользуется.
|
||||
|
||||
## IR-6. Безопасность self-hosting (инварианты)
|
||||
|
||||
- Любые `docker build` / `compose up` / recreate — ТОЛЬКО staging (8501); прод (8500) не
|
||||
рестартуется в рамках шага свежести.
|
||||
- `main` не пушится; force-only — `--force-with-lease` на ветку задачи (merge-gate, без
|
||||
изменений). Шаг A не пушит ничего (только локальный `docker build`).
|
||||
- B-guard срабатывает ДО `docker tag`/restart — прод не трогается на сомнении.
|
||||
|
||||
## IR-7. Bootstrap-чеклист (урок ORCH-36 «сквозной»)
|
||||
|
||||
Перед мержем ORCH-058 — **реальный** прогон в staging-петле (не только бумажные гейты):
|
||||
сборка staging из worktree с GIT_SHA → лейбл присутствует
|
||||
(`docker image inspect ... revision`) → recreate 8501 → `staging_check` зелёный →
|
||||
`build_deploy_command` отдаёт непустой `EXPECTED_REVISION` → хук-guard пропускает при
|
||||
совпадении и `exit 1` при подмене `SOURCE_IMAGE` на устаревший. Зафиксировать в bootstrap-
|
||||
заметке (как LESSONS_ORCH-036).
|
||||
16
docs/work-items/ORCH-058/10-tech-risks.md
Normal file
16
docs/work-items/ORCH-058/10-tech-risks.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Технические риски — ORCH-058
|
||||
|
||||
Work Item ID: ORCH-058
|
||||
|
||||
| ID | Риск | Вероятность / Влияние | Митигация |
|
||||
|----|------|----------------------|-----------|
|
||||
| R-1 | **Полу-конфигурация «B без A»** → вечный fail-fast деплоя (B падает, никто не пересобирает) | Низк. / Высок. (deadlock, BR-5) | Единый kill-switch `image_freshness_enabled` включает/выключает A и B **как целое**; раздельных флагов A/B нет. Дефолт — оба включены. AC-6. |
|
||||
| R-2 | **Рассинхрон якоря**: merge-gate делает rebase ПОСЛЕ того, как агент прогнал staging_check → HEAD изменился | Сред. / Сред. | Якорь берётся ПОСЛЕ merge-gate; шаг A пересобирает из post-rebase HEAD; авторитетный staging_check — против свежего образа. Pre-check агента — soft. |
|
||||
| R-3 | **Гонка**: между пересборкой A и Phase B human-approve worktree HEAD сместился | Низк. / Высок. | B сверяет лейбл образа с `EXPECTED_REVISION`=validated_revision на момент Phase B; рассинхрон → fail-closed `exit 1`, прод не трогается. AC-2/AC-3. |
|
||||
| R-4 | **Пустой лейбл** (ручная/legacy/CI-сборка без `--build-arg GIT_SHA`) | Сред. / Высок. | Fail-closed: пустой лейбл → несоответствие → `exit 1`. By design. Шаг A всегда передаёт GIT_SHA. IR-3 фиксирует требование к любым сборкам. |
|
||||
| R-5 | **Латентность ребра**: +docker build staging +recreate +повторный staging_check перед approve | Высок. / Низк. | Bounded timeouts; выполняется в monitor-треде (как merge-gate re-test). `staging_check --mode stub` без LLM-трат. Приемлемо. |
|
||||
| R-6 | **Сборка/recreate случайно затронет прод (8500)** | Низк. / Критич. | Режим хука `--build-staging` со STAGING-safe дефолтами (8501, `--profile staging`); код шага A никогда не передаёт прод-параметры. AC-9. Тест-инвариант: цель != прод. |
|
||||
| R-7 | **docker build на хосте падает** (нет места, недоступен daemon, битый worktree) | Низк. / Сред. | never-raise: `check_staging_image_fresh` → `(False, reason)` + release lease → откат на `development` (не зависание, не тихий промоут). AC-8. |
|
||||
| R-8 | **Двойной staging_check** воспринят как баг/лишняя трата | Сред. / Низк. | Документировано как намеренное (soft pre-check агента vs авторитетный код против промоутимого образа). Будущая оптимизация — облегчить шаг A. |
|
||||
| R-9 | **Самохостинг-bootstrap**: фича не действует, пока сама не в проде (старый прод-образ без лейбла) | Высок. (однократно) / Сред. | Bootstrap-чеклист (IR-7): первый реальный staging-прогон + ручной разрыв; B обратносовместим (без `EXPECTED_REVISION` — старое поведение), раскат поэтапный через флаг. |
|
||||
| R-10 | **Деградация не-self репо** | Низк. / Высок. | Условность (`image_freshness_repos` пусто → только orchestrator); для прочих — `(True, "N/A")` + хук без `EXPECTED_REVISION` = прежний путь. AC-5. |
|
||||
Reference in New Issue
Block a user