analyst(ET): auto-commit from analyst run_id=548

This commit is contained in:
2026-06-10 02:19:16 +03:00
committed by orchestrator-deployer
parent 3364436a2e
commit a0659de4d2
4 changed files with 448 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
---
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.

View File

@@ -0,0 +1,117 @@
---
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`.

View File

@@ -0,0 +1,99 @@
---
work_item: ORCH-057
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-057 — нормализация legacy root-owned файлов
Work Item: **ORCH-057** · Repo: **orchestrator** · Стадия: analysis
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL**
(что считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам репозитория.
---
## AC-1 — Конвейер стартует без ручного chown (или внятная блокирующая ошибка)
**Условие:** после миграции контейнера на новый uid первый job не падает сырым git-фаталом на launch.
- **PASS:** при нормализованных правах worktree создаётся и агент стартует; при НЕнормализованных
правах конвейер выдаёт понятную блокирующую ошибку с диагнозом и лечащей командой (НЕ сырой
`fatal: could not create leading directories … Permission denied`).
- **FAIL:** на launch всплывает сырой git-fatal/Permission denied без диагноза причины и инструкции.
---
## AC-2 — `ensure_worktree` даёт actionable-ошибку при отказе доступа
**Условие:** `src/git_worktree.py::ensure_worktree` классифицирует ошибки прав.
- **PASS:** при `Permission denied`/`could not create leading directories`/`insufficient permission`
поднимается `RuntimeError`, текст которого называет причину (legacy root-файлы в `/repos/_wt`/`.git`
после миграции uid) и указывает команду/ссылку на процедуру; ошибки, не связанные с правами,
сохраняют прежний контракт.
- **FAIL:** сырой git stderr пробрасывается без диагноза; либо подменяется смысл не-прав-ошибок;
либо `ensure_worktree` падает необработанно.
---
## AC-3 — Детект несоответствия владельца
**Условие:** новый леаф `src/fs_normalize.py` обнаруживает файлы с `uid != target_uid` в корнях
(`/repos/_wt`, `<repo>/.git/objects`, `<repo>/.git/worktrees`, `data/runs`).
- **PASS:** на среде с root-файлами `scan_ownership` возвращает mismatch=True + затронутые корни;
на чистой (`1000:1000`) среде — mismatch=False (no-op, идемпотентно); леаф never-raise.
- **FAIL:** mismatch не обнаружен на грязной среде / ложный mismatch на чистой / леаф бросает наружу.
---
## AC-4 — Наблюдаемость детекта
**Условие:** результат детекта виден оператору без падения задачи.
- **PASS:** при mismatch — структурный лог-WARNING (число/корни/лечащая команда) и Telegram (если
включён); опц. read-only отражение в `GET /queue`.
- **FAIL:** mismatch обнаружен, но никак не сообщён; оператор узнаёт о проблеме только по упавшей задаче.
---
## AC-5 — Self-hosting безопасность и нулевая регрессия enduro-trails
**Условие:** изменение безопасно для общего инстанса.
- **PASS:** код не рестартит/не роняет прод, не трогает `main`/force-push/прод-образ; chown — только
при наличии прав; при выключенном kill-switch поведение 1:1 как до ORCH-057; при пустом scope-CSV
feature активен только для self-hosting (enduro-trails не затронут); регресс `pytest tests/ -q` зелёный.
- **FAIL:** любой рестарт/деградация прода из кода задачи; ненулевая регрессия enduro-trails;
поведение меняется при выключенном флаге; падение всего регресса.
---
## AC-6 — Инварианты конвейера сохранены
**Условие:** изменение аддитивно.
- **PASS:** `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, machine-verdict-ключи и схема БД —
байт-в-байт прежние; новые флаги аддитивны и обратимы.
- **FAIL:** затронут любой exit/под-гейт, изменён machine-key, добавлена миграция схемы.
---
## AC-7 — Документированная процедура нормализации
**Условие:** процедура воспроизводима.
- **PASS:** `INFRA.md` содержит раздел «Миграция uid: обязательная нормализация legacy root-файлов»
с точными командами (`_wt`, оба `.git`, `data/runs`) как обязательный шаг миграции; ADR ORCH-057
фиксирует решение и ссылается на процедуру.
- **FAIL:** процедура отсутствует/абстрактна (как было в ORCH-040) либо не покрывает все корни.
---
## Сводная матрица AC ↔ FR/BR
| AC | Покрывает |
|----|-----------|
| AC-1 | BR-1 / FR-1, FR-3 |
| AC-2 | BR-1, BR-5 / FR-1 |
| AC-3 | BR-2 / FR-2 |
| AC-4 | BR-4 / FR-3 |
| AC-5 | NFR-1, NFR-2, NFR-5 / FR-4 |
| AC-6 | NFR-5 (инварианты) |
| AC-7 | BR-3 / FR-5 |

View File

@@ -0,0 +1,92 @@
work_item: ORCH-057
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
title: "Нормализация legacy root-owned файлов при миграции на uid 1000 (детект + защита worktree)"
framework: pytest
scope: >
Покрывается: классификация ошибки прав в ensure_worktree (внятная actionable-ошибка),
детект несоответствия владельца (fs_normalize.scan_ownership), идемпотентность на чистой среде,
fail-safe/never-raise, scope/kill-switch (self-hosting only при пустом CSV), опц. self-heal-noop
без прав. ВНЕ покрытия: реальный chown под root (требует привилегий — проверяется на staging
вручную), правка docker-compose/entrypoint (инфра, ручная проверка на 8501).
notes: >
Все FS-зависимые тесты используют tmp_path и monkeypatch os.getuid/os.stat — без реального chown
и без записи в /repos. Telegram/Plane мокаются. Полный регресс tests/ должен оставаться зелёным;
STAGE_TRANSITIONS/QG_CHECKS/схема БД не затрагиваются — отдельные guard-тесты не требуются, но
существующие тесты на инварианты должны пройти без изменений.
tests:
- id: TC-01
type: unit
description: "ensure_worktree при git-fatal 'could not create leading directories / Permission denied' поднимает RuntimeError с диагнозом legacy-root + лечащей командой, а не сырой git stderr"
module: tests/test_git_worktree_perm.py
expected: PASS
- id: TC-02
type: unit
description: "ensure_worktree при ошибке, НЕ связанной с правами (например branch conflict), сохраняет прежний контракт сообщения (не подменяет смысл)"
module: tests/test_git_worktree_perm.py
expected: PASS
- id: TC-03
type: unit
description: "scan_ownership на дереве с файлом uid != target_uid возвращает mismatch=True и список затронутых корней"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-04
type: unit
description: "scan_ownership на чистом дереве (все файлы target_uid) возвращает mismatch=False (идемпотентный no-op)"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-05
type: unit
description: "scan_ownership never-raise: при недоступном/несуществующем корне деградирует в WARNING и не бросает наружу"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-06
type: unit
description: "applies(repo): пустой ORCH_FS_NORMALIZE_REPOS → True только для self-hosting репо (orchestrator), False для enduro-trails; непустой CSV — по списку"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-07
type: unit
description: "kill-switch ORCH_FS_NORMALIZE_ENABLED=False → scan/normalize инертны (no-op), поведение 1:1 как до ORCH-057"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-08
type: unit
description: "normalize без прав (uid 1000, чужие root-файлы, ORCH_FS_NORMALIZE_AUTO=True) → no-op + честный лог 'нужна операторская процедура', НЕ исключение"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-09
type: unit
description: "TTL-кэш детекта: повторный вызов в окне TTL не пере-сканирует дерево (по образцу preflight._cache); force/reset инвалидирует"
module: tests/test_fs_normalize.py
expected: PASS
- id: TC-10
type: integration
description: "startup-хук lifespan при mismatch вызывает send_telegram (мок) и логирует WARNING; при ошибке детекта старт сервиса не падает (never-fatal)"
module: tests/test_fs_normalize_startup.py
expected: PASS
- id: TC-11
type: integration
description: "опц. гейт claim'а: при обнаруженном mismatch без прав исход job внятный (FR-1-сообщение / не-claim) ДО launch, а не сырой git-fatal"
module: tests/test_fs_normalize_startup.py
expected: PASS
- id: TC-12
type: integration
description: "GET /queue (если реализован read-only блок fs_ownership) отдаёт {enabled,target_uid,mismatch,roots,checked_at} и не 5xx-ит при выключенном флаге"
module: tests/test_api_queue.py
expected: PASS