architect(ET): auto-commit from architect run_id=190
All checks were successful
CI / test (push) Successful in 18s
All checks were successful
CI / test (push) Successful in 18s
This commit is contained in:
@@ -52,6 +52,34 @@ created → analysis → architecture → development → review → testing →
|
|||||||
|
|
||||||
Подробнее: [adr-0006](adr/adr-0006-merge-gate.md), детально — `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md`.
|
Подробнее: [adr-0006](adr/adr-0006-merge-gate.md), детально — `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md`.
|
||||||
|
|
||||||
|
### Исполняемый самодеплой стадии `deploy` (ORCH-36)
|
||||||
|
`deploy` перестаёт быть «бумажной»: для self-hosting (`is_self_hosting_repo`) стадия
|
||||||
|
РЕАЛЬНО деплоит прод (8500) через хост-хук `scripts/orchestrator-deploy-hook.sh`,
|
||||||
|
а `deploy_status: SUCCESS` означает доказанный health-ok, не декларацию LLM. Три фазы
|
||||||
|
(детерминированно, без LLM в критическом пути self-restart):
|
||||||
|
- **Фаза A (вход в `deploy`)** — при `deploy_require_manual_approve=true` вместо запуска
|
||||||
|
прод-deployer выставляется approval-pending статус Plane + запрос approve
|
||||||
|
(Plane-коммент + Telegram). Перехват в `advance_stage` ПОСЛЕ `check_staging_status`
|
||||||
|
и merge-gate.
|
||||||
|
- **Фаза B (Plane → `Approved`)** — `advance_stage(deploy, finished_agent=None)`
|
||||||
|
запускает **detached host-процесс** (ssh + setsid → хук с прод-параметрами +
|
||||||
|
build-once retag `SOURCE_IMAGE`) и ставит детерминированный **finalizer-job**;
|
||||||
|
маркер `initiated` — идемпотентность. Возврат БЕЗ advance (вердикта ещё нет).
|
||||||
|
- **Фаза C (finalizer)** — новый контейнер после рестарта читает sentinel `result`
|
||||||
|
(exit-code хука), маппит `0→SUCCESS / иначе→FAILED`, пишет `14-deploy-log.md`,
|
||||||
|
вызывает `advance_stage(deploy, finished_agent="deployer")` → существующие контракты:
|
||||||
|
`SUCCESS → done`, `FAILED → откат БАГ-8 на development`.
|
||||||
|
|
||||||
|
Approve = смена статуса Plane на `Approved` (status-only verdict model; комментарии
|
||||||
|
не управляют конвейером). На старте — обязательный ручной approve (флаг `true`); полный
|
||||||
|
авто — отдельная задача (ORCH-54). Условность как ORCH-35: реально для `orchestrator`,
|
||||||
|
прочие репо — прежний синхронный ssh-деплой агентом. Контракты не меняются:
|
||||||
|
`STAGE_TRANSITIONS`, реестр QG, `check_deploy_status`/`_parse_deploy_status`, БАГ-8,
|
||||||
|
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`.
|
||||||
|
|
||||||
## Откаты
|
## Откаты
|
||||||
- Reviewer REQUEST_CHANGES → откат на `development` + retry (`MAX_DEVELOPER_RETRIES = 3`).
|
- Reviewer REQUEST_CHANGES → откат на `development` + retry (`MAX_DEVELOPER_RETRIES = 3`).
|
||||||
- Tester `check_tests_passed` FAIL → откат на `development` + retry.
|
- Tester `check_tests_passed` FAIL → откат на `development` + retry.
|
||||||
@@ -109,4 +137,4 @@ created → analysis → architecture → development → review → testing →
|
|||||||
Схема БД, потоки данных, resilience-слой, детали Dockerfile — [internals.md](internals.md).
|
Схема БД, потоки данных, 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.*
|
*Актуально на 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.*
|
||||||
|
|||||||
64
docs/architecture/adr/adr-0007-executable-self-deploy.md
Normal file
64
docs/architecture/adr/adr-0007-executable-self-deploy.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# ADR-0007: Исполняемый самодеплой стадии `deploy` (Вариант B, ORCH-36)
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
Accepted (design) — реализация в ветке `feature/ORCH-036`.
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
Стадия `deploy` была «бумажной»: deployer-агент писал `deploy_status:` в
|
||||||
|
`14-deploy-log.md`, гейт `check_deploy_status` парсил вердикт и двигал
|
||||||
|
`deploy → done`. Реального деплоя не было. ORCH-36 делает стадию исполняемой для
|
||||||
|
self-hosting (`orchestrator`), сохраняя прежний ssh-путь для остальных репо.
|
||||||
|
|
||||||
|
Три ограничения формируют дизайн (детально — `docs/work-items/ORCH-036/06-adr/ADR-001`):
|
||||||
|
1. **Self-restart**: рестарт прод-контейнера 8500 убивает in-container процесс →
|
||||||
|
рестарт делает ВНЕШНИЙ host-процесс.
|
||||||
|
2. **Status-only verdict model**: approve = смена статуса Plane на `Approved`
|
||||||
|
(комментарии не управляют конвейером).
|
||||||
|
3. **Гонка гейта**: вердикт нельзя читать до завершения асинхронного хука.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
Для self-hosting стадия `deploy` исполняется в три фазы детерминированным кодом
|
||||||
|
(без LLM в критическом пути self-restart):
|
||||||
|
|
||||||
|
- **Фаза A (вход в `deploy`)** — для self + `deploy_require_manual_approve=true`
|
||||||
|
вместо запуска прод-deployer выставляется approval-pending статус Plane + запрос
|
||||||
|
approve (Plane-коммент + Telegram). Перехват в `advance_stage` на ребре
|
||||||
|
`deploy-staging → deploy` (после `check_staging_status` и merge-gate).
|
||||||
|
- **Фаза B (Plane → Approved)** — `advance_stage(deploy, finished_agent=None)`
|
||||||
|
запускает **detached host-процесс** (ssh + setsid → `orchestrator-deploy-hook.sh`
|
||||||
|
с прод-параметрами и build-once retag) и ставит **детерминированный finalizer-job**
|
||||||
|
с задержкой; маркер `initiated` — идемпотентность. Возврат БЕЗ advance.
|
||||||
|
- **Фаза C (finalizer)** — после рестарта новый контейнер дочитывает sentinel
|
||||||
|
`result` (exit-code хука), маппит `0→SUCCESS / иначе→FAILED`, пишет
|
||||||
|
`14-deploy-log.md`, вызывает `advance_stage(deploy, finished_agent="deployer")`
|
||||||
|
→ существующие контракты: `SUCCESS → done`, `FAILED → откат БАГ-8 на development`.
|
||||||
|
|
||||||
|
### Ключевые инварианты (НЕ меняются)
|
||||||
|
`STAGE_TRANSITIONS`, реестр QG, `check_deploy_status` / `_parse_deploy_status`
|
||||||
|
(frontmatter only), откат БАГ-8, terminal-sync `deploy → done`, merge-gate (ORCH-43),
|
||||||
|
exit-code-контракт хука (0/1/2).
|
||||||
|
|
||||||
|
### Новое (сквозное)
|
||||||
|
- **Детерминированный job-kind** `deploy-finalizer` в очереди (reserved-agent, не
|
||||||
|
LLM): read-result | defer | map+write+advance. Зеркалит детерминизм merge-gate.
|
||||||
|
- **Approve-флаг** `deploy_require_manual_approve` (дефолт `true`; полный авто —
|
||||||
|
отдельная задача после набора метрик доверия, ORCH-54).
|
||||||
|
- **Build-once**: опциональный `SOURCE_IMAGE` retag в хуке (обратно совместимо).
|
||||||
|
- **Restart-safe состояние** деплоя — sentinel-файлы под
|
||||||
|
`<repos_dir>/.deploy-state-<repo>/<wi>/` (как merge-lease), БЕЗ миграции БД.
|
||||||
|
|
||||||
|
### Условность
|
||||||
|
Вся логика — только для `is_self_hosting_repo(repo)` (как ORCH-35). Прочие репо
|
||||||
|
деплоятся прежним синхронным ssh-путём агентом.
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
- `deploy_status: SUCCESS` доказан реальным health-ok; критический путь self-restart
|
||||||
|
детерминирован.
|
||||||
|
- Вводится новая под-компонента (finalizer job-handler) → изменение помечено
|
||||||
|
`arch:major-change`.
|
||||||
|
- Approve вписан в status-only модель: restart-safe, аудируемо, идемпотентно.
|
||||||
|
- На старте — обязательный ручной approve; молчаливых деплоев нет (Plane+Telegram).
|
||||||
|
|
||||||
|
## Связанные ADR
|
||||||
|
`adr-0003` (staging-gate), `adr-0006` (merge-gate), `adr-0005` (run-as-host-uid).
|
||||||
|
Детальный per-work-item: `docs/work-items/ORCH-036/06-adr/ADR-001-executable-self-deploy.md`.
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
# ADR-001: Исполняемый самодеплой — стадия `deploy` дёргает хост-хук (Вариант B)
|
||||||
|
|
||||||
|
Work Item: ORCH-036
|
||||||
|
Stage: architecture
|
||||||
|
Автор: architect
|
||||||
|
Дата: 2026-06-06
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Стадия `deploy` сейчас «бумажная»: deployer-агент (LLM) пишет в `14-deploy-log.md`
|
||||||
|
`deploy_status: SUCCESS|FAILED`, а гейт `check_deploy_status` (`src/qg/checks.py:464`)
|
||||||
|
парсит этот вердикт и двигает `deploy → done`. Реального docker-деплоя нет (прод
|
||||||
|
катается руками). BRD ORCH-36 требует превратить стадию в РЕАЛЬНЫЙ самодеплой с
|
||||||
|
обязательным ручным approve, build-once и авто-rollback (BR-1…BR-10).
|
||||||
|
|
||||||
|
Три твёрдых ограничения, разведанных в коде, определяют дизайн:
|
||||||
|
|
||||||
|
1. **Self-restart (BR-2).** Прод-контейнер `orchestrator` (8500) — ОДИН на все
|
||||||
|
проекты, и в нём же исполняется deployer. `docker compose up -d orchestrator`
|
||||||
|
из контейнера убьёт процесс агента/воркера на середине. Реальный рестарт обязан
|
||||||
|
делать ВНЕШНИЙ процесс на хосте, переживающий гибель контейнера.
|
||||||
|
2. **Status-only verdict model.** Комментарии Plane НЕ управляют конвейером —
|
||||||
|
механизм `:approved:`/`:rejected:` был удалён (`src/webhooks/plane.py:544`,
|
||||||
|
bug-3 «echo self-hit»). Единственный человеческий гейт — **смена статуса Plane
|
||||||
|
на `Approved`** (`handle_verdict` → `_try_advance_stage` → `advance_stage`).
|
||||||
|
3. **Гонка чтения гейта.** Так как реальный рестарт асинхронный и убивает контейнер,
|
||||||
|
`check_deploy_status` нельзя выполнять на выходе агента — вердикта ещё нет; его
|
||||||
|
преждевременное чтение → ложный FAILED → ложный откат.
|
||||||
|
|
||||||
|
Контракты, которые НЕ меняются (BR-9, AC-10): `STAGE_TRANSITIONS`,
|
||||||
|
`check_deploy_status` / `_parse_deploy_status` (frontmatter only), откат БАГ-8
|
||||||
|
(`deploy → development`), terminal-sync `deploy → done`, merge-gate (ORCH-43),
|
||||||
|
exit-code-контракт хука (0/1/2).
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Деплой стадии `deploy` для self-hosting (`orchestrator`) разбивается на **три фазы**,
|
||||||
|
оркеструемые детерминированным кодом (без LLM в критическом пути self-restart). Для
|
||||||
|
НЕ-self репо (enduro-trails и пр.) поведение НЕ меняется — прежний синхронный
|
||||||
|
ssh-деплой агентом.
|
||||||
|
|
||||||
|
### Условность по репо
|
||||||
|
Вся новая логика гейтится `is_self_hosting_repo(repo)` (как ORCH-35). Не-self репо
|
||||||
|
идут существующим путём: deployer-агент на стадии `deploy` делает ssh-деплой
|
||||||
|
синхронно, пишет `14-deploy-log.md`, гейт срабатывает на выходе агента.
|
||||||
|
|
||||||
|
### Фаза A — запрос approve (вход в `deploy`)
|
||||||
|
В `advance_stage` на ребре `deploy-staging → deploy` (ПОСЛЕ зелёного
|
||||||
|
`check_staging_status` и merge-gate ORCH-43), для self-hosting + `deploy_require_
|
||||||
|
manual_approve=true`:
|
||||||
|
- **НЕ** ставить в очередь прод-deployer (перехватить штатный
|
||||||
|
`enqueue_job(get_agent_for_stage("deploy-staging"))`);
|
||||||
|
- выставить issue в approval-pending статус (паттерн `set_issue_in_review`),
|
||||||
|
написать Plane-коммент «approve для прод-деплоя» + Telegram (BR-5);
|
||||||
|
- записать restart-safe маркер `approve-requested` (sentinel-файл, см. ниже).
|
||||||
|
|
||||||
|
Задача остаётся на стадии `deploy` и ждёт человека. `STAGE_TRANSITIONS` не меняется.
|
||||||
|
|
||||||
|
При `deploy_require_manual_approve=false` (вне объёма, флаг НЕ выключается в ORCH-36 —
|
||||||
|
AC-12) Фаза A сразу переходит к Фазе B без человеческого гейта. Структурная ветка
|
||||||
|
закладывается, но дефолт `true`.
|
||||||
|
|
||||||
|
### Фаза B — инициация деплоя (смена статуса Plane → Approved)
|
||||||
|
Человек ставит issue в `Approved`. `handle_verdict(approved=True)` →
|
||||||
|
`_try_advance_stage` → `advance_stage(current_stage="deploy", finished_agent=None)`.
|
||||||
|
Новая ветка-перехват в `advance_stage`:
|
||||||
|
- условие: `current_stage=="deploy"` И `finished_agent is None` (человеческий путь)
|
||||||
|
И self-hosting И approve-флаг И маркер `initiated` ОТСУТСТВУЕТ;
|
||||||
|
- действие: запустить **внешний detached host-процесс** (см. ниже) и поставить в
|
||||||
|
очередь детерминированный **finalizer-job** с задержкой; записать маркер
|
||||||
|
`initiated` (идемпотентность: повторный Approved не запускает деплой дважды);
|
||||||
|
Plane-коммент «прод-деплой стартовал» + Telegram (BR-5);
|
||||||
|
- **вернуться БЕЗ advance** (НЕ запускать `check_deploy_status` — вердикта ещё нет).
|
||||||
|
|
||||||
|
Дискриминатор `finished_agent` разводит Фазу B (человек, `None`) и Фазу C
|
||||||
|
(finalizer, `"deployer"`), поэтому повторное использование `advance_stage` безопасно.
|
||||||
|
|
||||||
|
### Фаза C — фиксация вердикта (детерминированный finalizer)
|
||||||
|
Finalizer-job (claim'ится воркером уже в НОВОМ контейнере после рестарта):
|
||||||
|
- читает sentinel `result` (exit-code хука, записан host-процессом);
|
||||||
|
- если `result` ещё нет и бюджет попыток не исчерпан → **defer** (повторный
|
||||||
|
finalizer-job с `available_at_delay_s`, как merge-gate defer); бюджет считается
|
||||||
|
из `jobs` (`LIKE '%deploy-finalize%'`, restart-safe);
|
||||||
|
- если `result` есть → **маппинг exit-code → deploy_status** (детерминированный,
|
||||||
|
unit-тестируемый): `0 → SUCCESS`, `1|2|иное → FAILED`; записать
|
||||||
|
`14-deploy-log.md` (frontmatter `deploy_status:`), смержить в `main` (паттерн
|
||||||
|
лога), затем вызвать `advance_stage(current_stage="deploy", finished_agent="deployer")`;
|
||||||
|
- далее срабатывают СУЩЕСТВУЮЩИЕ контракты: `SUCCESS` → terminal-sync `deploy → done`
|
||||||
|
+ release merge-lease; `FAILED` → откат БАГ-8 `deploy → development` +
|
||||||
|
`set_issue_blocked` + Plane/Telegram (BR-3, AC-4). `_parse_deploy_status` НЕ меняется.
|
||||||
|
|
||||||
|
### Механизм detached-запуска: ssh + setsid
|
||||||
|
Выбор: **ssh на хост (`slin@DEPLOY_SSH_HOST`) с setsid-detached исполнением** хука.
|
||||||
|
Обоснование: ssh-ключи уже смонтированы (INFRA P-2), не-self репо уже деплоятся по
|
||||||
|
ssh (единый путь), хук живёт на хосте и под `slin` имеет полный доступ к docker вне
|
||||||
|
контейнера → переживает рестарт 8500 (BR-2). `setsid`/`nohup` + redirect отвязывает
|
||||||
|
удалённый процесс от ssh-канала, чтобы он пережил гибель ssh-клиента при рестарте
|
||||||
|
контейнера. Отвергнуто: вызов через docker.sock изнутри контейнера = ровно мина
|
||||||
|
«убей себя на середине вызова».
|
||||||
|
|
||||||
|
Эскиз (точная сборка — за разработчиком):
|
||||||
|
```
|
||||||
|
ssh -o StrictHostKeyChecking=no slin@$DEPLOY_SSH_HOST \
|
||||||
|
"setsid bash -c 'cd /home/slin/repos/orchestrator && \
|
||||||
|
SOURCE_IMAGE=orchestrator-orchestrator-staging \
|
||||||
|
TARGET_SERVICE=orchestrator TARGET_PORT=8500 \
|
||||||
|
TARGET_IMAGE=orchestrator-orchestrator COMPOSE_PROFILE= \
|
||||||
|
PREV_IMAGE_FILE=.deploy-prev-image-prod \
|
||||||
|
bash scripts/orchestrator-deploy-hook.sh --deploy; \
|
||||||
|
echo \$? > <result-sentinel>' >> <hook.log> 2>&1 </dev/null &"
|
||||||
|
```
|
||||||
|
ssh-команда возвращается сразу; remote-процесс detached. Запись sentinel `result`
|
||||||
|
делает **обёртка** (`echo $? > result`), а НЕ хук — контракт хука нетронут.
|
||||||
|
|
||||||
|
### Build-once (BR-6, AC-7)
|
||||||
|
Прод обязан подняться на ОБРАЗЕ, прошедшем staging (а не на пересборке). Решение:
|
||||||
|
расширить хук **опциональным** `SOURCE_IMAGE` (обратно совместимо: не задан →
|
||||||
|
текущее поведение). При заданном `SOURCE_IMAGE` хук ПЕРЕД `up -d --no-build`
|
||||||
|
делает `docker tag $SOURCE_IMAGE $TARGET_IMAGE`. Для прод-self:
|
||||||
|
`SOURCE_IMAGE=orchestrator-orchestrator-staging` → `TARGET_IMAGE=orchestrator-orchestrator`.
|
||||||
|
Это единственное допустимое изменение хука; exit-code-контракт и дефолтное
|
||||||
|
staging-поведение не меняются. `git pull` хука обновляет рабочее дерево хоста для
|
||||||
|
будущих сборок, но РАЗВЁРНУТЫЙ артефакт = перетегированный staging-образ.
|
||||||
|
|
||||||
|
### Restart-safe состояние: sentinel-файлы (без миграции БД)
|
||||||
|
По образцу merge-lease (`<repos_dir>/.merge-lease-<repo>.json`) состояние деплоя
|
||||||
|
хранится в файлах под `<repos_dir>/.deploy-state-<repo>/<work_item_id>/` (вне git,
|
||||||
|
видны и хосту, и контейнеру через mount `/home/slin/repos ↔ /repos`):
|
||||||
|
- `approve-requested` — Фаза A выполнена;
|
||||||
|
- `initiated` — Фаза B запущена (idempotency-guard);
|
||||||
|
- `result` — exit-code хука (пишет host-обёртка).
|
||||||
|
Бюджет finalize-defer считается из `jobs` (restart-safe), новых таблиц/колонок НЕТ
|
||||||
|
(TRZ §4).
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
### Плюсы
|
||||||
|
- `deploy_status: SUCCESS` становится ДОКАЗАННЫМ (реальный health-ok хука), не
|
||||||
|
декларацией LLM (BR-1).
|
||||||
|
- Self-restart безопасен: рестарт 8500 делает внешний host-процесс; орк себя не
|
||||||
|
убивает (BR-2). Вердикт фиксирует НОВЫЙ контейнер после рестарта.
|
||||||
|
- Критический путь self-restart **детерминирован** (без LLM) — главный выигрыш по
|
||||||
|
безопасности self-hosting; зеркалит детерминизм merge-gate ORCH-43.
|
||||||
|
- Approve вписан в существующую status-only модель — restart-safe, аудируемо в Plane,
|
||||||
|
идемпотентно (маркер `initiated`).
|
||||||
|
- Гонка чтения гейта закрыта: гейт читает РЕАЛЬНЫЙ итог через finalizer-defer.
|
||||||
|
- Build-once гарантирует «что тестировали — то в проде».
|
||||||
|
- Нетронуты: `STAGE_TRANSITIONS`, реестр QG, `_parse_deploy_status`, БАГ-8,
|
||||||
|
terminal-sync, merge-gate, контракт хука (exit-code).
|
||||||
|
|
||||||
|
### Минусы / ограничения
|
||||||
|
- Вводится **новый детерминированный job-handler** в очереди (reserved-agent
|
||||||
|
`deploy-finalizer`, не-LLM) — расширение dispatch воркера/лаунчера. Контейнированное,
|
||||||
|
но это новая под-компонента → задача помечается `arch:major-change`.
|
||||||
|
- Перехваты в `advance_stage` усложняют стадию `deploy` (три ветки по
|
||||||
|
`finished_agent`/маркерам). Требуется аккуратное покрытие тестами (TC-04…TC-09).
|
||||||
|
- Build-once зависит от того, что deploy-staging оставил валидный образ
|
||||||
|
`orchestrator-orchestrator-staging`; при rebase merge-gate возможен дрейф
|
||||||
|
образ↔main (см. 10-tech-risks R-3).
|
||||||
|
- Approve = смена статуса Plane на `Approved`; человек должен понимать, что на
|
||||||
|
стадии `deploy` `Approved` означает «деплой в прод» (документируется в deployer.md
|
||||||
|
и INFRA.md).
|
||||||
|
|
||||||
|
### Что обязан сделать developer
|
||||||
|
1. `src/config.py`: `deploy_require_manual_approve: bool = True` + прод-параметры
|
||||||
|
хука/ssh + `deploy_finalize_delay_s` / `deploy_finalize_max_attempts`.
|
||||||
|
2. `src/stage_engine.py`: перехваты Фазы A/B + ветка finalizer (Фаза C через
|
||||||
|
`advance_stage(..., finished_agent="deployer")`).
|
||||||
|
3. Очередь: reserved-agent `deploy-finalizer` (детерминированный handler:
|
||||||
|
read-result | defer | map+write+advance). Маппинг exit→status — отдельная
|
||||||
|
чистая функция (unit TC-01/02/03).
|
||||||
|
4. `scripts/orchestrator-deploy-hook.sh`: опциональный `SOURCE_IMAGE` retag
|
||||||
|
(обратно совместимо) + прод `PREV_IMAGE_FILE`.
|
||||||
|
5. Уведомления (Plane+Telegram) на initiate/success/rollback (BR-5).
|
||||||
|
6. Документация: `deployer.md`, `INFRA.md`, `DEPLOY_HOOK.md`, `CHANGELOG.md`.
|
||||||
|
7. Отладка — только на staging-цели хука; прод 8500 в разработке не трогать.
|
||||||
|
|
||||||
|
## Связанные решения
|
||||||
|
- Глобальный ADR: `docs/architecture/adr/adr-0007-executable-self-deploy.md`.
|
||||||
|
- ORCH-35 staging-gate (`adr-0003`), ORCH-43 merge-gate (`adr-0006`),
|
||||||
|
ORCH-21 auto-rollback, ORCH-34 хук, ORCH-40 run-as-host-uid (`adr-0005`).
|
||||||
48
docs/work-items/ORCH-036/07-infra-requirements.md
Normal file
48
docs/work-items/ORCH-036/07-infra-requirements.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Инфраструктурные требования — ORCH-036
|
||||||
|
|
||||||
|
Work Item: ORCH-036
|
||||||
|
Stage: architecture
|
||||||
|
Автор: architect
|
||||||
|
|
||||||
|
> Топология не меняется (та же mva154, те же два контейнера). Меняется ПРОЦЕДУРА
|
||||||
|
> прод-деплоя орка: из ручной → исполняемая через хост-хук с ручным approve.
|
||||||
|
|
||||||
|
## 1. Контейнеры / порты — без изменений
|
||||||
|
- prod `orchestrator` (8500), staging `orchestrator-staging` (8501) — как в INFRA.md.
|
||||||
|
- Образы (имена для build-once): prod `orchestrator-orchestrator`,
|
||||||
|
staging `orchestrator-orchestrator-staging`.
|
||||||
|
|
||||||
|
## 2. Хост-предусловия (Owner, в git не коммитятся)
|
||||||
|
- **HP-1.** ssh-доступ из контейнера на хост: `ssh slin@$DEPLOY_SSH_HOST` работает
|
||||||
|
под uid 1000 ключом из `~/.orchestrator-ssh` (INFRA P-2). Без него detached-запуск
|
||||||
|
Фазы B невозможен.
|
||||||
|
- **HP-2.** `<repos_dir>/.deploy-state-<repo>/` доступен на запись и хосту (host-обёртка
|
||||||
|
пишет `result`), и контейнеру (finalizer читает) — обеспечивается mount
|
||||||
|
`/home/slin/repos ↔ /repos` (как merge-lease).
|
||||||
|
- **HP-3.** `PREV_IMAGE_FILE` для прод — отдельный путь
|
||||||
|
(`.deploy-prev-image-prod`), чтобы не путать снапшоты prod/staging.
|
||||||
|
- **HP-4 (P-4 из INFRA).** Прод-рестарт self — только в окно тишины; общий инстанс
|
||||||
|
с enduro-trails. На старте — под ручным approve (флаг `true`).
|
||||||
|
|
||||||
|
## 3. Переменные окружения (карта; значения — на хосте, в git только дескрипторы)
|
||||||
|
| Переменная | Назначение | Дефолт |
|
||||||
|
|-----------|-----------|--------|
|
||||||
|
| `ORCH_DEPLOY_REQUIRE_MANUAL_APPROVE` | ручной approve перед прод-деплоем | `true` |
|
||||||
|
| `DEPLOY_SSH_USER` / `DEPLOY_SSH_HOST` | ssh-цель хост-хука | — (INFRA-карта) |
|
||||||
|
| `DEPLOY_HOOK_SCRIPT` | путь к хуку на хосте | `scripts/orchestrator-deploy-hook.sh` |
|
||||||
|
| прод `TARGET_SERVICE/PORT/IMAGE`, `COMPOSE_PROFILE` | override прод-профиля хука | `orchestrator`/`8500`/`orchestrator-orchestrator`/пусто |
|
||||||
|
| `SOURCE_IMAGE` (новый параметр хука) | образ для build-once retag | пусто → текущее поведение |
|
||||||
|
| `ORCH_DEPLOY_FINALIZE_DELAY_S` | задержка перед первым finalize-поллом | > 60с (health-loop хука) |
|
||||||
|
| `ORCH_DEPLOY_FINALIZE_MAX_ATTEMPTS` | бюджет finalize-defer | bounded (anti-livelock) |
|
||||||
|
|
||||||
|
Прописать дескрипторы в `.env.example` / INFRA.md. Реальные значения не коммитить.
|
||||||
|
|
||||||
|
## 4. Сетевые / процессные требования
|
||||||
|
- Detached host-процесс (ssh + setsid) обязан пережить рестарт прод-контейнера 8500.
|
||||||
|
- Finalizer-job исполняется в НОВОМ контейнере после рестарта (очередь restart-safe).
|
||||||
|
- MTTR авто-rollback < 60с (health-loop хука 10×6с уже укладывается, BR-8/AC-9).
|
||||||
|
|
||||||
|
## 5. Что НЕ требуется
|
||||||
|
- Новых контейнеров/портов/сервисов — нет.
|
||||||
|
- Изменений `docker-compose.yml` — не требуется (build-once через retag, не профиль).
|
||||||
|
- Multi-node / облако / message-queue — нет (принципы проекта).
|
||||||
34
docs/work-items/ORCH-036/08-data-requirements.md
Normal file
34
docs/work-items/ORCH-036/08-data-requirements.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Требования к данным / схеме БД — ORCH-036
|
||||||
|
|
||||||
|
Work Item: ORCH-036
|
||||||
|
Stage: architecture
|
||||||
|
Автор: architect
|
||||||
|
|
||||||
|
## Решение: миграция БД НЕ требуется
|
||||||
|
|
||||||
|
Схема SQLite (`events`, `tasks`, `agent_runs`, `jobs`) не меняется. Обоснование:
|
||||||
|
|
||||||
|
1. **Вердикт деплоя** — в `14-deploy-log.md` (frontmatter `deploy_status:`), как
|
||||||
|
сейчас. `_parse_deploy_status` не трогаем (AC-10).
|
||||||
|
2. **Approve / initiated / result-состояние** — restart-safe через **sentinel-файлы**
|
||||||
|
под `<repos_dir>/.deploy-state-<repo>/<work_item_id>/` (паттерн merge-lease
|
||||||
|
`<repos_dir>/.merge-lease-<repo>.json`), а не через новую таблицу/колонку:
|
||||||
|
- `approve-requested` — Фаза A;
|
||||||
|
- `initiated` — Фаза B (idempotency-guard);
|
||||||
|
- `result` — exit-code хука (пишет host-обёртка).
|
||||||
|
3. **Бюджет finalize-defer** считается из существующей таблицы `jobs`
|
||||||
|
(`task_content LIKE '%deploy-finalize%'`), как `_merge_defer_count` для merge-gate
|
||||||
|
— restart-safe, без новых полей.
|
||||||
|
4. **Finalizer-job** использует существующую структуру `jobs` (agent, repo,
|
||||||
|
task_content, task_id, available_at). Reserved-agent `deploy-finalizer` — это
|
||||||
|
значение в колонке `agent`, схема не меняется.
|
||||||
|
|
||||||
|
## Почему файлы, а не БД
|
||||||
|
- Sentinel должен быть виден И хосту (пишет `result`), И контейнеру (читает finalizer);
|
||||||
|
файл на общем mount это обеспечивает, SQLite-запись из host-обёртки — нет.
|
||||||
|
- Зеркалит уже принятый паттерн merge-lease (ORCH-43) — единообразие, restart-safe,
|
||||||
|
crash-реклейм по возрасту файла.
|
||||||
|
|
||||||
|
Если разработчик при реализации сочтёт необходимым поле статуса approve в БД —
|
||||||
|
это требует обновления данного ADR с обоснованием; по умолчанию — без миграции
|
||||||
|
(согласовано с TRZ §4).
|
||||||
23
docs/work-items/ORCH-036/10-tech-risks.md
Normal file
23
docs/work-items/ORCH-036/10-tech-risks.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Технические риски — ORCH-036
|
||||||
|
|
||||||
|
Work Item: ORCH-036
|
||||||
|
Stage: architecture
|
||||||
|
Автор: architect
|
||||||
|
|
||||||
|
| ID | Риск | Влияние | Вероятность | Митигация |
|
||||||
|
|----|------|---------|-------------|-----------|
|
||||||
|
| R-1 | Detached host-процесс не пережил рестарт 8500 (ssh-канал убит вместе с контейнером) | Деплой не завершён, `result` не записан, finalizer вечно defer'ит | Средняя | `setsid`/`nohup` + redirect отвязывает remote-процесс от ssh; интеграционная проверка на staging-цели (TC-08); finalize-defer bounded → по исчерпании `set_issue_blocked` + Telegram |
|
||||||
|
| R-2 | Преждевременное чтение `check_deploy_status` (вердикта ещё нет) | Ложный FAILED → ложный откат на development | Средняя | Фаза B возвращается БЕЗ advance; гейт запускает только finalizer (Фаза C) после появления `result`; defer пока `result` отсутствует |
|
||||||
|
| R-3 | Дрейф образ↔main: merge-gate сделал rebase, но staging-образ собран до rebase → build-once тегирует «не тот» код | В прод уезжает не точно то, что в `main` | Низкая | merge-gate (ORCH-43) делает re-test после rebase; build-once = «что валидировано на staging», что и есть контракт; задокументировано как осознанное ограничение; усиление (rebuild+revalidate staging после rebase) — отдельная задача |
|
||||||
|
| R-4 | Двойной Approved (человек кликнул дважды / дубль webhook) запускает деплой дважды | Двойной рестарт прода, гонка | Средняя | Маркер `initiated` (idempotency-guard); event-dedup webhook'ов Plane уже есть |
|
||||||
|
| R-5 | exit 2 хука (rollback тоже упал) → 8500 лежит → finalizer/новый контейнер не поднялся | Конвейер всех проектов встал | Низкая | health-loop + авто-rollback хука минимизируют; `restart: unless-stopped` поднимет контейнер на ПРЕДЫДУЩЕМ образе если retag не случился; exit 2 → `deploy_status: FAILED` + откат + Telegram-алерт; ручной `--rollback` хука как backstop |
|
||||||
|
| R-6 | Reserved-agent `deploy-finalizer` ошибочно уйдёт в LLM-путь лаунчера (`_spawn` → ValueError) | Finalizer не отработает | Низкая | Перехват ДО `_spawn` в `launch_job`; unit-тест маршрутизации |
|
||||||
|
| R-7 | sentinel-файлы не видны контейнеру/хосту (mount/uid) | Фазы B/C не синхронизируются | Низкая | Тот же mount и uid-модель, что у merge-lease (ORCH-40/43); HP-2 в 07-infra |
|
||||||
|
| R-8 | Approve через смену статуса Plane конфликтует с auto-advance других стадий | Случайный `Approved` на `deploy` ничего не ломает, но семантика неочевидна | Низкая | Перехват по `current_stage=="deploy"` + `finished_agent is None` + маркеры; задокументировать в deployer.md/INFRA, что `Approved` на `deploy` = «деплой в прод» |
|
||||||
|
| R-9 | Самодеплой ORCH ломает прод во время разработки самой ORCH-36 | Групповой простой (enduro-trails) | Низкая | Вся отладка — на staging-цели хука (8501); прод 8500 не трогать (AC: DoD); флаг approve=true |
|
||||||
|
|
||||||
|
## Сводный приоритет
|
||||||
|
- **Блокеры дизайна:** R-1, R-2 — закрыты архитектурой (setsid-detached + finalizer-defer).
|
||||||
|
- **Безопасность self-hosting:** R-5, R-9 — закрыты обязательным approve + staging-отладкой
|
||||||
|
+ авто-rollback + `restart: unless-stopped`.
|
||||||
|
- **Корректность:** R-3, R-4 — осознанные ограничения / idempotency-guard.
|
||||||
Reference in New Issue
Block a user