architect(ET): auto-commit from architect run_id=263
All checks were successful
CI / test (push) Successful in 16s
All checks were successful
CI / test (push) Successful in 16s
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 — design)
|
||||
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`) → задача застревает молча
|
||||
@@ -168,3 +196,4 @@ never-raise на единицу работы; тишина при синхрон
|
||||
---
|
||||
*Актуально на 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).*
|
||||
*ORCH-058: провенанс staging-образа перед BUILD-ONCE retag (check_staging_image_fresh + хук-guard) — design (см. adr-0008), реализация в ветке feature/ORCH-058. Обновлять также при изменении src/self_deploy.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`.
|
||||
@@ -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