--- work_item: ORCH-057 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-10 model_used: claude-opus-4-8 --- # 02 — ТЗ (TRZ): ORCH-057 — нормализация legacy root-owned файлов при миграции на uid 1000 Work Item: **ORCH-057** · Repo: **orchestrator** · Стадия: analysis > ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD и фактического кода. > Архитектурное обоснование/выбор варианта one-time нормализации (init-контейнер vs blocking-entrypoint > vs ансибл) — задача архитектора (`06-adr/`). Здесь — требования, контракты и ограничения. ## 1. Сводка изменения Закрыть недоделанный AC ORCH-040 по legacy-файлам. Три слоя: 1. **Защита launcher** — `ensure_worktree` распознаёт `Permission denied`/git-fatal на создании worktree и поднимает **внятную** ошибку с диагнозом «legacy root-файлы в `/repos/_wt` — нужна нормализация прав» + лечащая команда (опц. авто-самолечение при наличии прав). 2. **Ранний детект** — новый чистый леаф находит файлы с `uid != target_uid` в `ORCH_REPOS_DIR` (`_wt`, `.git/objects`, `.git/worktrees`, `data/runs`); вызывается на старте сервиса и/или перед claim'ом job; never-raise, config-gated, с наблюдаемостью. 3. **Процедура** — `INFRA.md` + ADR ORCH-057: точные команды разовой нормализации как обязательный шаг миграции uid. Опционально — one-time нормализация под root через init-механизм (решает архитектор). Инвариант: `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключи / схема БД — **байт-в-байт прежние**. Изменение аддитивно и обратимо kill-switch'ем. ## 2. Задействованные модули / пути | Путь | Действие | |------|----------| | `src/git_worktree.py` (`ensure_worktree`, `remove_worktree`) | изменить — классификация `Permission denied`/git-fatal на `git worktree add` / `os.makedirs` → внятный actionable `RuntimeError` (опц. self-heal при правах) | | `src/fs_normalize.py` | **создать** — чистый леаф (never-raise): `scan_ownership(roots, target_uid) -> результат`; опц. `normalize(...)` (chown только при наличии прав); хелпер `applies(repo)` + кэш (TTL, как preflight) | | `src/config.py` | изменить — добавить флаги (см. §7); без правки существующих значений | | `src/main.py` (`lifespan`) | изменить — добавить startup-вызов детекта (best-effort, never-fatal по образцу L-2/lease-reclaim), лог + Telegram при mismatch; read-only блок в `GET /queue` | | `src/preflight.py` **или** `src/queue_worker.py` | изменить (на выбор архитектора) — опц. гейт claim'а job при обнаруженном mismatch, чтобы падать внятно ДО launch (по образцу preflight-гейта) | | `docker-compose.yml` / `Dockerfile` / `scripts/*entrypoint*` | **кандидат** (решает архитектор) — one-time root-нормализация (init-контейнер/хук) ПЕРЕД стартом app; если выбрано — деплой self, обязательная staging-страховка | | `docs/operations/INFRA.md` | изменить — раздел «Миграция uid: обязательная нормализация legacy root-файлов» (команды + область) | | `docs/work-items/ORCH-057/06-adr/ADR-001-*.md` | создать (architect) — решение + процедура; опц. forward-breadcrumb из ADR-001 ORCH-040 (без переписывания истории) | | `CHANGELOG.md` | изменить — запись о ORCH-057 | | `tests/test_*` | создать — см. `04-test-plan.yaml` | ## 3. Функциональные требования ### FR-1 — Внятная ошибка `ensure_worktree` (BR-1, BR-5) При неуспехе `git worktree add` / `os.makedirs(os.path.dirname(wt))` по причине отказа доступа (`Permission denied`, `could not create leading directories`, `insufficient permission for adding an object`) `ensure_worktree` поднимает `RuntimeError` с сообщением, которое: (а) называет корневую причину (legacy root-owned файлы в `/repos/_wt` или `.git` после миграции uid ORCH-040); (б) указывает лечащую команду (`chown -R : …`) или ссылку на процедуру INFRA.md; (в) НЕ является сырым git stderr. Прочие (нет-прав-несвязанные) ошибки сохраняют текущий контракт (никакой подмены смысла). ### FR-2 — Детект несоответствия владельца (BR-2, BR-4) Леаф `fs_normalize.scan_ownership` обходит корни (`/repos/_wt`, `/.git/objects`, `/.git/worktrees`, `data/runs`) и возвращает: есть ли файлы с `uid != target_uid`, их число (или флаг «≥1»), список затронутых корней. Обход дешёвый/ограниченный (ранний выход при первом mismatch для быстрого вердикта; полный подсчёт — опционально/семплировано). Результат кэшируется по TTL (по образцу `preflight._cache`). `target_uid` = `os.getuid()` или конфиг (дефолт 1000). ### FR-3 — Реакция на детект (BR-1, BR-4) - **Startup (main.lifespan):** вызвать детект best-effort; при mismatch — структурный WARNING + Telegram (если включён) с числом/корнями и лечащей командой. Никогда не падать на старте по ошибке детекта (NFR-3). - **Опц. гейт claim'а:** при обнаруженном mismatch и `target_uid` без прав на chown — не претендовать на job (или претендовать и сразу честно фейлить с FR-1-сообщением), чтобы исход был внятным до launch. Конкретную точку (preflight vs queue_worker) выбирает архитектор; требование — «внятно и заранее». ### FR-4 — Опциональная авто-нормализация (BR-1) `fs_normalize.normalize` выполняет `chown -R target_uid:target_gid` по корням **только если процесс имеет на это право** (CAP_CHOWN/root). Под uid 1000 без прав — no-op + честный лог «нужна операторская процедура» (НЕ ошибка). Включается отдельным флагом (`*_AUTO`), по умолчанию — выкл (детект-only). Если архитектор выбирает init-контейнер под root — это и есть носитель FR-4 на буте. ### FR-5 — Документированная процедура (BR-3) `INFRA.md` получает раздел с точными командами разовой нормализации (`_wt`, оба `.git`, `data/runs`), помеченный как **обязательный** шаг миграции uid и часть чеклиста деплоя self. ADR ORCH-057 фиксирует решение и ссылается на процедуру; ADR-001 ORCH-040 опц. получает forward-ссылку. ## 4. Изменения API Нет новых обязательных эндпоинтов. **Опционально** (наблюдаемость, решает архитектор): - расширить `GET /queue` read-only блоком `fs_ownership` (`{enabled, target_uid, mismatch, roots, checked_at}`); - ручной триггер `POST /fs-normalize/check` (форс-пересчёт детекта) — по образцу `POST /serial-gate/unfreeze`. ## 5. Изменения схемы БД Нет. Состояние детекта — в памяти (TTL-кэш), как `preflight`. Таблицы/миграции/индексы не вводятся. ## 6. Требования к новым/изменённым QG checks Нет. Это **не** stage-гейт и **не** под-гейт ребра. `QG_CHECKS` / `check_*` / `STAGE_TRANSITIONS` / machine-verdict-ключи (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`/ `coverage_status:`) — не трогаются. (В описании баг-репорта «deploy-гейт ORCH-040» — это деплой-хук/ процедура, а не зарегистрированный QG.) ## 7. Совместимость / регресс - **Kill-switch** `ORCH_FS_NORMALIZE_ENABLED` (дефолт по решению архитектора; `False` → весь код инертен, поведение 1:1 как до ORCH-057). - **Scope** `ORCH_FS_NORMALIZE_REPOS` (CSV; пусто → **self-hosting only**, как `coverage_gate_repos` → enduro-trails не затронут). Локальный `applies(repo)` проверяется ПЕРВЫМ (дешёвый обход только при applies). - **Флаги** (рабочие имена, финал — за архитектором): `ORCH_FS_TARGET_UID` (дефолт 1000), `ORCH_FS_NORMALIZE_AUTO` (дефолт `False` — детект-only; `True` → попытка chown при наличии прав), `ORCH_FS_SCAN_ROOTS` (CSV переопределения корней), `ORCH_FS_SCAN_CACHE_TTL_S`. - **Never-raise / fail-safe** — ошибка детекта/нормализации деградирует в WARNING, не блокирует старт сервиса по своей вине; FR-1 меняет лишь **формулировку** ошибки worktree, не её факт. - **Self-hosting** (NFR-1) — код только читает/детектит/диагностирует (и chown ТОЛЬКО при наличии прав); не деплоит/не рестартит прод/не трогает `main`. Любое касание `docker-compose.yml`/entrypoint требует staging-прогона (8501) перед прод-рестартом в окно тишины. - **Обратимость** — выкл kill-switch → прежнее поведение; миграций/правки схемы нет. - **Пайплайн-артефакты:** обновляются `01..04` (analysis), `06-adr/`+`07-infra-requirements.md`+`10-tech-risks.md` (architecture), `12/13/15/14` (review/testing/staging/deploy), `INFRA.md`, `CHANGELOG.md`.