170 lines
15 KiB
Markdown
170 lines
15 KiB
Markdown
---
|
||
work_item: ORCH-112
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-15
|
||
model_used: claude-opus-4-8
|
||
escalate: full-cycle
|
||
---
|
||
|
||
# 01 — BRD / Bug-report: ORCH-112 — failed/cancelled task artifacts must be cleaned from shared checkout
|
||
|
||
Work Item: **ORCH-112** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug → эскалация в full-cycle**
|
||
|
||
> ⚠️ **`escalate: full-cycle` (ADR-001 D5 ORCH-019).** Баг помечен `Bug`, но по сути это
|
||
> **архитектурный + safety-critical (self-hosting)** дефект: правка лежит в самом опасном пути
|
||
> прод-деплоя (хост-хук, прямо перед рестартом прод-контейнера) и требует **решения о политике
|
||
> жизненного цикла** shared checkout (ADR). Поэтому выпускается **полный** analysis-пакет, а не
|
||
> облегчённый bug-пакет. Оператор снимает багфикс-трек: `POST /bug-fast-track/escalate?work_item=ORCH-112`
|
||
> → задача пойдёт через стадию `architecture` (architect выпустит ADR для политики cleanup/изоляции).
|
||
|
||
---
|
||
|
||
## 1. Бизнес-контекст и проблема
|
||
|
||
### Симптом (наблюдаемое)
|
||
Self-deploy задачи **ORCH-111** упал на шаге `git pull origin main` хост-хука деплоя с ошибкой:
|
||
```
|
||
error: Your local changes to the following files would be overwritten by merge:
|
||
src/config.py
|
||
Please commit your changes or stash them before you merge.
|
||
```
|
||
Деплой прерван, конвейер потребовал **ручного вмешательства оператора** (на self-hosting это
|
||
групповой риск — встаёт деплой и всех других проектов).
|
||
|
||
### Причина симптома (установленный факт)
|
||
В **общем (shared) checkout** `/home/slin/repos/orchestrator` оставались грязные файлы от
|
||
ранее **неуспешной/отменённой/перезапущенной задачи ORCH-104** (тема Lite installer):
|
||
- модифицированный tracked-файл: `src/config.py`;
|
||
- модифицированный/untracked: `docs/deployment/LITE_SETUP.md`;
|
||
- untracked: `scripts/install_lite.py`, `tests/test_install_lite.py`,
|
||
`docs/deployment/lite-install.example.yaml`.
|
||
|
||
Через несколько дней эти остатки **заблокировали** `git pull` другой задачи (ORCH-111).
|
||
|
||
### Локализация (анализ — куда смотреть архитектору/разработчику)
|
||
|
||
**Установленный факт о топологии (CLAUDE.md / `docs/architecture/README.md`):**
|
||
`/home/slin/repos/orchestrator` (хост) == `/repos/orchestrator` (контейнер, bind-mount) ==
|
||
**main clone** (`settings.repos_dir/<repo>` = `settings.deploy_host_repo_path`). Это **deploy-база
|
||
и база управления worktree'ами**, а НЕ рабочая копия агента.
|
||
|
||
1. **Первичный дефект — нерезистентный `git pull`.**
|
||
`scripts/orchestrator-deploy-hook.sh:224-226` делает `cd "$REPO"` (= deploy-база) и
|
||
**голый** `git pull origin main` **без гигиены рабочего дерева**. Любая локальная правка
|
||
tracked-файла блокирует merge → деплой падает. Проверено: во всём `src/`+`scripts/` **нет ни
|
||
одного** `git reset --hard` / `git clean` / `git stash` для приведения базы к чистому состоянию.
|
||
Shared checkout трактуется как «всегда чистый», что не гарантировано.
|
||
|
||
2. **Невыполненный/неэнфорснутый инвариант + отсутствие «дворника».**
|
||
Нормальный конвейер **не пишет** в рабочее дерево main clone: агенты работают в изолированных
|
||
worktree'ах `/repos/_wt/<repo>/<branch>` (`git_worktree.ensure_worktree`); `docker build`
|
||
использует контекст **worktree** (`image_freshness._host_worktree_path`), не main clone;
|
||
fallback'и гейтов на main clone — **только чтение** (`git show origin/main:...`,
|
||
`qg/checks.py:451`, `coverage_gate.py:297`, `stage_engine.py:145`). Поэтому грязь ORCH-104
|
||
почти наверняка — **ручной/брошенный WIP** в shared checkout во время инцидента ORCH-104
|
||
(косвенное подтверждение: файлы `install_lite.py`/`test_install_lite.py`/`lite-install.example.yaml`
|
||
**никогда не существовали в git-истории** — закоммиченный артефакт ORCH-104 это
|
||
`scripts/setup_lite.py`, commit `e2cf883`). Вне зависимости от источника: **нет механизма**,
|
||
который детектирует/чистит грязную базу и **нет задокументированного/энфорснутого инварианта**
|
||
«main checkout — неизменяемая deploy-база, не workspace».
|
||
|
||
3. **`cancel_task` чистит worktree + remote-ветку, но НЕ shared checkout.**
|
||
`stage_engine.cancel_task` (шаг 3d, строки ~2330-2343): `remove_worktree(repo, branch)` +
|
||
`gitea.delete_remote_branch(repo, branch)`. Это корректно (конвейер в main clone не пишет), но
|
||
означает **нулевое покрытие** случая «грязная deploy-база» в каскадах failed/cancelled.
|
||
|
||
**Вывод:** даже если первопричина грязи — ручное действие, устойчивость должна быть на стороне
|
||
системы: deploy-база обязана **самовосстанавливаться** в чистый `origin/main` перед pull, а
|
||
политика жизненного цикла — гарантировать, что остатки failed/cancelled задач не клинят будущие
|
||
операции.
|
||
|
||
## 2. Объём (scope)
|
||
|
||
### В объёме
|
||
- Сделать self-deploy `git pull origin main` (shared deploy-база) **устойчивым к грязному рабочему
|
||
дереву** — приведение базы к чистому `origin/main` **автономно**, без ручного вмешательства.
|
||
- Гарантировать, что после **failed / cancelled / брошенной** задачи в shared checkout не остаётся
|
||
рабочих остатков, способных заблокировать будущий деплой/операцию (сходимость базы к чистому
|
||
`origin/main`).
|
||
- Задокументировать (и где осуществимо — мягко энфорснуть/гардить) инвариант
|
||
«shared main checkout — deploy/worktree-management база, НЕ редактируемый workspace».
|
||
- Наблюдаемость: лог + Telegram-алерт, когда deploy-база найдена грязной и автоочищена (или отказ).
|
||
|
||
### Вне объёма
|
||
- ❌ Запрет/контроль ручных операций оператора в shared checkout (вне технической власти системы;
|
||
закрываем устойчивостью, а не запретом).
|
||
- ❌ Изменение модели worktree per-task (`git_worktree`, ORCH-2) — она корректна и не трогается.
|
||
- ❌ Любое изменение `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключей / схемы БД.
|
||
- ❌ Изменение поведения деплоя на чистой базе (happy-path должен остаться байт-в-байт).
|
||
- ❌ Выбор конкретного механизма (reset --hard vs janitor vs guard) — это **зона архитектора** (ADR).
|
||
|
||
## 3. Заинтересованные стороны
|
||
- **Заказчик/оператор (Слава)** — страдает от ручного разруливания залипших деплоев; принимает результат.
|
||
- **Self-hosting конвейер orchestrator** — прямой потребитель (надёжность прод-деплоя).
|
||
- **Все проекты на общем инстансе (enduro-trails)** — косвенно: залипший self-deploy орка
|
||
останавливает обслуживание их задач.
|
||
|
||
## 4. Бизнес-требования (BR)
|
||
- **BR-1** — Грязное рабочее дерево shared deploy-базы (модифицированные tracked-файлы и/или
|
||
untracked-файлы) **НЕ должно блокировать** self-deploy `git pull origin main`: деплой обязан
|
||
привести базу к чистому, актуальному `origin/main` **без ручного вмешательства**.
|
||
- **BR-2** — После failed / cancelled / брошенной задачи в shared checkout **не должно оставаться**
|
||
рабочих остатков этой задачи, способных заблокировать будущий деплой/git-операцию; база
|
||
**сходится** к чистому `origin/main`.
|
||
- **BR-3** — Инвариант «shared main checkout (`<host_repos_dir>/<repo>`) — deploy/worktree-management
|
||
база, НЕ workspace» должен быть **задокументирован** (`docs/operations/INFRA.md` +
|
||
`docs/architecture/README.md`) и, где осуществимо, **энфорснут/гардирован**; конвейер/агенты
|
||
**никогда** не пишут рабочие изменения в main clone (верифицировать, что это так).
|
||
- **BR-4** — **Наблюдаемость:** обнаружение грязной базы и факт автоочистки (или отказ) должны
|
||
логироваться и алертиться (Telegram, кликабельный номер) — оператор видит, что гигиена сработала.
|
||
- **BR-5** — На **чистой** базе поведение деплоя — **байт-в-байт прежнее** (обычный fast-forward
|
||
`git pull`); никакого регресса happy-path.
|
||
|
||
## 5. Нефункциональные требования (NFR)
|
||
- **NFR-1 (self-hosting safety)** — гигиена **никогда** не трогает ветку `main` на remote, не делает
|
||
force-push, не рестартит прод-контейнер вне штатного гейта, не удаляет worktree/ветки **других
|
||
активных** задач. Оперирует **только** настроенным путём deploy-базы.
|
||
- **NFR-2 (сохранность deploy-состояния)** — автоочистка **не должна** удалять артефакты, легитимно
|
||
живущие под `$REPO`/рядом: rollback-снимки `$REPO/.deploy-prev-image-*`
|
||
(`deploy_prod_prev_image_file`), `deploy-hook.log`, sibling-состояния
|
||
`<repos_dir>/.deploy-state-*` / `.merge-lease-*.json`, и админ-записи worktree в `.git/worktrees`.
|
||
(Наивный `git clean -xfd` в `$REPO` уничтожил бы `.deploy-prev-image-*` и сломал rollback — это
|
||
**жёсткое ограничение** для архитектора/разработчика.)
|
||
- **NFR-3 (обратимость / kill-switch)** — новое поведение под флагом; выключенный флаг → деплой
|
||
байт-в-байт как до ORCH-112 (голый `git pull origin main`).
|
||
- **NFR-4 (надёжность)** — never-raise / fail-safe (по образцу leaf'ов `serial_gate`/`cancel`);
|
||
идемпотентность; restart-safe; сбой гигиены не должен маскировать или ухудшать исход деплоя сверх
|
||
текущего.
|
||
- **NFR-5 (нулевая регрессия конвейера)** — `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` /
|
||
machine-verdict ключи / схема БД / exit-code-контракт хука (0/1/2, ORCH-036) — **байт-в-байт**.
|
||
- **NFR-6 (область)** — изменение скоупится на self-hosting (`orchestrator`); поведение для прочих
|
||
репо/синхронного деплоя агентом — не ухудшается.
|
||
|
||
## 6. Допущения и ограничения
|
||
- Shared checkout и хост-хук физически разделяют один путь с контейнером через bind-mount
|
||
(`repos_dir`↔`host_repos_dir`); хук исполняется на **хосте** по ssh (ORCH-036, detached).
|
||
- Build-once путь (`SOURCE_IMAGE` retag) **не** зависит от содержимого рабочего дерева main clone —
|
||
прод получает ровно staging-валидированный образ; значит дискард рабочего дерева base перед pull
|
||
**безопасен для деплоимого артефакта**. (`--build-staging` собирается из **worktree**, не из main —
|
||
отдельный контур.)
|
||
- Источник истины кода — `origin/main`; локальные правки в deploy-базе **по определению** не должны
|
||
существовать (это deploy-база, а не место работы).
|
||
- Конкретный механизм (resilient pull через reset+clean со скоуп-исключениями / активный janitor /
|
||
guard инварианта / комбинация) — **открытый вопрос для архитектуры**, решается в `06-adr/`.
|
||
|
||
## 7. Критерии успеха
|
||
Self-deploy успешно выполняет `git pull` на ранее грязной shared-базе **без ручного вмешательства**;
|
||
deploy-база сходится к чистому `origin/main`; rollback-состояние и sibling-артефакты сохранены;
|
||
happy-path и весь конвейер — без регресса; обязательный регресс-тест **красный до фикса, зелёный
|
||
после**. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||
|
||
## 8. Риски
|
||
- Деструктивная гигиена (`reset --hard`/`clean`) в **прод-deploy-базе** рядом с рестартом прода —
|
||
ошибка скоупа может удалить rollback-state/логи (см. NFR-2) → ADR обязателен.
|
||
- Маскировка реальной первопричины: если в будущем какой-то код **начнёт** писать в main clone,
|
||
«тихая автоочистка» это скроет → нужна наблюдаемость (BR-4).
|
||
- Кросс-каттинг с ORCH-036 (self-deploy), ORCH-058 (image-freshness/provenance), ORCH-090 (cancel),
|
||
ORCH-2 (worktree-модель). Детали/митигации — `10-tech-risks.md` (заполняет архитектор).
|