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
