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