8.3 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-057 | architecture | architect | proposed | 2026-06-10 | claude-opus-4-8 |
adr-0031: Нормализация legacy root-owned файлов при миграции uid — детект-leaf + actionable worktree-ошибка
- Статус: proposed
- Дата: 2026-06-10
- Задача: ORCH-057 (follow-up ORCH-040)
- Детальный ADR:
docs/work-items/ORCH-057/06-adr/ADR-001-legacy-ownership-normalization.md
Контекст
ORCH-040 перевёл контейнеры на user: "1000:1000", изменив только docker-compose.yml. Владельца
уже существующих root:root файлов в bind-mount /repos это не меняет. Под uid 1000
src/git_worktree.py::ensure_worktree (os.makedirs стр. 78 / git worktree add стр. 81/85) не может
создать worktree рядом с root-owned /repos/_wt/ → fatal: could not create leading directories … Permission denied, который сейчас пробрасывается сырым. Конвейер приходит сюда из
launcher._spawn/_materialize_deferred_branch (ORCH-088) — агент не стартует (launch-time
инфра-сбой, не код задачи). Инцидент 06.06 на проде (первый запуск ORCH-043); workaround Стрима
(chown -R 1000:1000) наложен вручную. ADR-040 описал нормализацию абстрактно («вне объёма кода») и
не дал процедуры → баг воспроизводим на чистой среде / новом репо / после исторического запуска под
root. Контейнер бежит без root → код физически не может chown чужие файлы; ему доступны лишь
детект + диагностика.
Решение
Три аддитивных, обратимых kill-switch'ем слоя — паттерн условного leaf-гейта (coverage_gate/
serial_gate) + best-effort startup-хук (main.lifespan, как lease-reclaim). STAGE_TRANSITIONS /
QG_CHECKS / check_* / machine-verdict-ключи (verdict:/result:/deploy_status:/
staging_status:/security_status:/coverage_status:) / схема БД — байт-в-байт прежние.
- Actionable worktree-ошибка (D1):
ensure_worktreeклассифицирует класс «нет прав» (маркерыPermission denied/could not create leading directories/insufficient permission/EACCES/EPERM) и поднимаетRuntimeErrorс причиной (legacy root-файлы после миграции uid) + лечащей командой + ссылкой на INFRA.md. Не-прав-ошибки сохраняют прежний текст/смысл (никакой подмены). Меняется лишь формулировка, не факт сбоя. - Детект-leaf
src/fs_normalize.py(D2): чистый, never-raise, TTL-кэш (паттернpreflight).scan_ownership(roots, target_uid)обходит/repos/_wt,<repo>/.git/objects,<repo>/.git/worktrees,data/runs; ранний выход при первомst_uid != target_uid(target_uid=os.getuid()по умолчанию).applies(repo)(kill-switch + scope; пусто →is_self_hosting_repo) проверяется ПЕРВЫМ → дорогой обход только при applies. Идемпотентно; ошибка обхода → WARNING + консервативныйmismatch=False. - Интеграция = наблюдаемость, без блокировки claim (D3): best-effort
scan_ownership()на стартеmain.lifespan→ WARNING + Telegram при mismatch. Claim НЕ гейтится: внятный ранний отказ даёт D1 в точке launch (знает repo, агент ещё не тратил токены). Блокирующий preflight-гейт отвергнут — preflight не знает repo, заблокировал бы и enduro-trails на общем/repos. - Опц.
normalize()(D4): chown только приCAP_CHOWN/root (под uid 1000 — no-op + лог), флагfs_normalize_auto(дефолтFalse). Init-контейнер/root-entrypoint отвергнут: реинтродукция root-контекста (анти-цель ORCH-040) + правка compose = self-deploy/групповой риск. Реальную нормализацию несёт операторская процедура. - Процедура (D5):
INFRA.mdполучает раздел «Миграция uid: обязательная нормализация legacy root-файлов» (точные команды по всем корням) как обязательный шаг миграции; forward-breadcrumb из ADR-040. - Флаги:
fs_normalize_enabled(kill-switch, дефолтTrue),fs_normalize_repos(CSV, пусто → self-hosting only),fs_target_uid(1000),fs_normalize_auto(False),fs_scan_roots,fs_scan_cache_ttl_s(300). Наблюдаемость — блокfs_ownershipвGET /queue; опц.POST /fs-normalize/check.
Альтернативы
- Init-контейнер/root-entrypoint — реинтродукция root (анти-цель ORCH-040), self-deploy compose, групповой риск ради разовой операции. Отвергнуто; носитель нормализации — операторская процедура.
- Блокирующий claim-гейт (preflight) — preflight не знает repo → регресс enduro на общем
/repos. Отвергнуто. - Блокирующий claim-гейт (queue_worker/claim) — дорогой FS-обход в hot-path + «молчаливое зависание» вместо диагноза D1. Отвергнуто.
- Авто-chown из app по умолчанию — под uid 1000 невозможен; ложное ожидание самолечения.
Отвергнуто (оставлен opt-in
fs_normalize_auto). - Hard-fail старта при mismatch — нарушает never-raise, стопорит сервис всех проектов. Отвергнуто.
Последствия
- Класс «сырой git-fatal на launch после миграции uid» закрыт внятным диагнозом (D1) + проактивным startup-сигналом (D3); пробел процедуры ADR-040 закрыт (INFRA.md).
- Нулевая регрессия enduro-trails (scope first); инварианты конвейера/схема БД — байт-в-байт.
- Никакого root-контекста/рестарта прода/касания
main/force-push/прод-образа (NFR-1). - Плата: фактический
chownостаётся ручным операторским шагом (но теперь внятным, с инструкцией); +1 best-effort startup-хук и leaf-модуль;fs_normalize_auto=Trueпод root реинтродуцирует chown-контекст (дефолтFalse, не для прод-self). - Аддитивно/обратимо: не
arch:major-change(нет новой стадии/QG/таблицы/смены топологии) — leaf- startup-хук + docs.
- Откат:
fs_normalize_enabled=False→ полный no-op (мгновенный обратимый kill-switch).
Связи
adr-0005 (контейнер под host-uid — порождающее решение ORCH-040, чей пробел закрываем),
adr-0029/adr-0012 (coverage/security-гейт — паттерн условного leaf applies/scope/never-raise/
fail-open), adr-0017 (serial-gate — leaf never-raise + отложенный срез ветки _materialize_deferred_ branch, чья точка падает в ensure_worktree), adr-0011 (job-reaper — образец best-effort
startup-хука в lifespan), adr-0024 (disk-watchdog — образец «только читать/уведомлять, не трогать
хост/прод»).