---
work_item: ORCH-057
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
---

# 01 — BRD (бизнес-требования): ORCH-057 — нормализация legacy root-owned файлов при миграции на uid 1000 (one-time + защита)

Work Item: **ORCH-057** · Repo: **orchestrator** · Стадия: analysis

## 1. Бизнес-контекст и проблема

ORCH-040 перевёл оба контейнера (`orchestrator` 8500, `orchestrator-staging` 8501) с root
на `user: "1000:1000"` (slin). Изменён был **только** `docker-compose.yml`. Однако bind-mount
`/home/slin/repos → /repos` уже содержал файлы и каталоги, созданные **прежним root-контейнером**
(`root:root`). Смена `user:` владельца существующих файлов НЕ меняет.

**Реальный инцидент (прод, 06.06, поймали на первом запуске ORCH-043).** Первый job под uid 1000
упал на стадии **launch** (НЕ на коде задачи):

```
fatal: could not create leading directories of
'/repos/_wt/orchestrator/feature_ORCH-043-.../.git': Permission denied
```

Причина: `/repos/_wt/` и старые worktree-папки = `root:root` → uid 1000 не может создать рядом
новый каталог worktree. Установлено фактически: ошибка возникает в `src/git_worktree.py::ensure_worktree`
(вызов `git worktree add`), куда конвейер приходит из `src/agents/launcher.py::_spawn` (стр. 500)
и `_materialize_deferred_branch` (ORCH-088). Агент даже не стартует — падает создание worktree.

**Ручной workaround (применён Стрим, прод снова рабочий, ОДНОРАЗОВО):**
```
sudo chown -R 1000:1000 /home/slin/repos/_wt
sudo chown -R 1000:1000 /home/slin/repos/orchestrator/.git /home/slin/repos/enduro-trails/.git
sudo chown -R 1000:1000 /home/slin/repos/orchestrator   # +data/runs/*.log (37 root-логов)
```

ADR-001 ORCH-040 упоминал «массовый chown старых root-файлов» лишь абстрактно («вне объёма кода»,
«разовая операция Owner») и НЕ дал конкретной процедуры чистки legacy worktree — поэтому deployer
её не выполнил, и баг проявился в проде. Прод сейчас рабочий (ручной фикс наложен), но проблема
**воспроизведётся** на чистой среде, новом репо или после любого исторического запуска под root,
если её не закрыть кодом + процедурой.

**Это follow-up / закрытие недоделанного AC ORCH-040** (legacy-файлы), а не новая фича.

## 2. Объём (scope)

### В объёме
- **Защита launcher (код):** при `Permission denied` на создании worktree выдавать **внятную,
  диагностируемую** ошибку «legacy root-файлы в `/repos/_wt` — требуется нормализация прав»
  с указанием команды, а НЕ сырой `git fatal`.
- **Раннее обнаружение (код):** детектирование наличия файлов с `uid != <target_uid>` в
  `ORCH_REPOS_DIR` (включая `_wt`, `.git/objects`, `.git/worktrees`, `data/runs`) при старте
  контейнера / перед претензией на job — чтобы конвейер падал **внятно и заранее**, а не сырым
  git-фаталом на launch.
- **Процедура нормализации (документация):** в `docs/operations/INFRA.md` (и собственный ADR
  ORCH-057) — обязательная одноразовая процедура нормализации legacy root-файлов при миграции uid,
  с точными командами и областью охвата (`_wt`, `.git`, `data/runs`).
- **Опционально (по решению архитектора):** механизм one-time нормализации при буте/деплое —
  init-контейнер/хук под root, либо blocking-entrypoint-проверка.

### Вне объёма
- Изменение логики конвейера, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, схемы БД.
- Пересмотр самого решения ORCH-040 (uid 1000) — оно принято и остаётся.
- Перенос инстанса на другой хост / другой uid (отдельная задача при миграции хоста).
- Массовая ретроактивная переработка ADR-001 ORCH-040 (его история не переписывается;
  допускается forward-breadcrumb-ссылка на ORCH-057 — решает архитектор).
- Выбор конкретного варианта реализации one-time нормализации (a/b/в) — зона архитектора (06-adr).

## 3. Заинтересованные стороны

- **Заказчик / Owner** — Слава (homenet542), инициатор; принимает результат.
- **Эксплуатация** — Стрим (применял ручной workaround); потребитель процедуры в INFRA.md.
- **Затронутые проекты** — `orchestrator` (self-hosting) и `enduro-trails` (общий инстанс, общая
  очередь, общий bind-mount `/repos`): нормализация прав `/repos` касается обоих репо.

## 4. Бизнес-требования (BR)

- **BR-1** — После миграции контейнера на новый uid конвейер запускается **без ручного `chown`**:
  либо авто-нормализация прав, либо **явная блокирующая ошибка с инструкцией** (никогда не сырой
  `git fatal` на launch).
- **BR-2** — На свежей среде / новом репо / после исторического запуска под root проблема
  **не воспроизводится** (детект + понятная диагностика срабатывают до падения агента).
- **BR-3** — `INFRA.md` и ADR содержат **конкретную процедуру** нормализации legacy root-файлов
  (точные команды, область: `_wt`, `.git/objects`, `.git/worktrees`, `data/runs`), помеченную как
  обязательный шаг миграции uid.
- **BR-4** — Несоответствие владельца наблюдаемо: оператор узнаёт о проблеме из лога/уведомления/
  read-only статуса, а не по падению задачи на launch.
- **BR-5** — Защита `ensure_worktree` распознаёт класс ошибки «нет прав на создание worktree» и
  сообщает причину + лечащую команду (опц. — авто-самолечение, если процесс имеет права).

## 5. Нефункциональные требования (NFR)

- **NFR-1 (self-hosting безопасность)** — Решение **никогда** не перезапускает/не роняет
  прод-контейнер `orchestrator`, не трогает `main`/force-push/прод-образ. Контейнер бежит под
  uid 1000 (без root) → код **не может** делать `chown` без root; код ограничивается
  детектом + внятной диагностикой/блокировкой, а фактический `chown` — операторская/init-процедура.
- **NFR-2 (общий инстанс)** — Нулевая регрессия для `enduro-trails`: feature под kill-switch и
  scope-флагом (по образцу `serial_gate`/`coverage_gate`); выключено → поведение 1:1 как до ORCH-057.
- **NFR-3 (never-raise / fail-safe)** — Детект-леаф никогда не бросает наружу неожиданное исключение
  и не блокирует старт сервиса по своей ошибке; деградирует в WARNING.
- **NFR-4 (идемпотентность)** — Повторный запуск детекта/нормализации на уже корректной среде —
  no-op без побочных эффектов.
- **NFR-5 (обратимость)** — Поведение откатывается выключением kill-switch без миграций/правки схемы.
- **NFR-6 (наблюдаемость)** — Вердикт (есть/нет mismatch, сколько файлов, какие корни) логируется
  структурно; при проблеме — Telegram с кликабельным номером задачи (если применимо) + read-only
  отражение в `GET /queue`.

## 6. Допущения и ограничения

- Целевой uid:gid рантайма = `1000:1000` (slin), подтверждён ORCH-040 (P-3); на хосте `/repos`,
  `/app/data` штатно `1000:1000`.
- Контейнер бежит под numeric uid 1000 без записи в `/etc/passwd` базового образа; в образе создан
  реальный user `slin` (uid 1000) для `getpwuid()` (ORCH-058, Dockerfile). Под uid 1000 `chown`
  чужих (root) файлов **невозможен** без CAP_CHOWN/root.
- `git config --system --add safe.directory '*'` уже в образе — git доверяет bind-mount.
- Корни проверки: `ORCH_REPOS_DIR` (`/repos`), включая `_wt`, `<repo>/.git/objects`,
  `<repo>/.git/worktrees`, и `data/runs` (37 root-логов в инциденте).
- `start_pipeline` (ORCH-088) отложил срез ветки на момент claim analyst-job → детект уместен
  и на старте сервиса, и перед claim'ом (точку выбирает архитектор).

## 7. Критерии успеха

После миграции uid (или на чистой среде) первый же job проходит launch без ручного `chown`, либо —
если права не нормализованы — конвейер выдаёт **понятную блокирующую диагностику** с командой
исправления вместо сырого `git fatal`. INFRA.md/ADR содержат воспроизводимую процедуру.
Для `enduro-trails` — нулевая регрессия. Детальные PASS/FAIL — в `03-acceptance-criteria.md`.

## 8. Риски

- Контейнер без root не может `chown` → авто-самолечение возможно только частично/при наличии прав;
  основной гарант — детект+диагностика+процедура (детали — `10-tech-risks.md`, архитектор).
- Рекурсивный обход больших `.git/objects` / `_wt` может быть дорог → нужен дешёвый/семплированный
  детект и кэш (как preflight TTL).
- Ложно-блокирующая ошибка может застопорить и enduro-trails (общий `/repos`) → строгий scope/fail-safe.
- Правка `docker-compose.yml`/entrypoint (init-контейнер) = деплой self → групповой риск (NFR-1),
  обязательная страховка staging.
