---
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` (заполняет архитектор).
