118 lines
11 KiB
Markdown
118 lines
11 KiB
Markdown
---
|
||
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 <uid>:<gid> …`) или ссылку на процедуру INFRA.md; (в) НЕ является сырым
|
||
git stderr. Прочие (нет-прав-несвязанные) ошибки сохраняют текущий контракт (никакой подмены смысла).
|
||
|
||
### FR-2 — Детект несоответствия владельца (BR-2, BR-4)
|
||
Леаф `fs_normalize.scan_ownership` обходит корни (`/repos/_wt`, `<repo>/.git/objects`,
|
||
`<repo>/.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`.
|