From 8e91c8c23c04f379a6b413d9796606c8d53bbd11 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Sat, 6 Jun 2026 14:49:21 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=174 --- docs/work-items/ORCH-040/01-brd.md | 93 +++++++++++++++++++ docs/work-items/ORCH-040/02-trz.md | 86 +++++++++++++++++ .../ORCH-040/03-acceptance-criteria.md | 55 +++++++++++ docs/work-items/ORCH-040/04-test-plan.yaml | 81 ++++++++++++++++ 4 files changed, 315 insertions(+) create mode 100644 docs/work-items/ORCH-040/01-brd.md create mode 100644 docs/work-items/ORCH-040/02-trz.md create mode 100644 docs/work-items/ORCH-040/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-040/04-test-plan.yaml diff --git a/docs/work-items/ORCH-040/01-brd.md b/docs/work-items/ORCH-040/01-brd.md new file mode 100644 index 0000000..22aeafd --- /dev/null +++ b/docs/work-items/ORCH-040/01-brd.md @@ -0,0 +1,93 @@ +# 01 — BRD: Агенты пишут файлы под root в смонтированный хост-репо + +Work Item: **ORCH-040** +Тип: инфра-фикс (runtime / docker-compose) +Исполнение: через Dev напрямую (по решению Owner) + +## 1. Бизнес-контекст и проблема + +Контейнер `orchestrator` (prod, 8500) работает под `uid=0 (root)`. Он монтирует +хостовый каталог `/home/slin/repos` → `/repos` (rw). Claude-CLI агенты запускаются +через `subprocess.Popen` **внутри контейнера**, то есть тоже под root. Они пишут: + +- в git worktree задач — `/repos/_wt///...`; +- в прод-клон — `/repos//docs/work-items/...` (через коммит/пуш из worktree). + +В результате на **хосте** файлы создаются с владельцем `root:root`. + +### Симптом +При ребилде/деплое прода `git pull` / `git reset` под пользователем `slin` падает: + +``` +error: insufficient permission for adding an object to repository database .git/objects +Permission denied (на docs/work-items/ORCH-016, владелец root:root) +``` + +Каждый будущий деплой будет ломаться, пока вручную не выполнить `chown`. + +### Диагноз (живая разведка 05–06.06) +- `docker exec orchestrator id` → `uid=0(root) gid=0(root) groups=0,999`. +- Хост `slin` = `uid=1000 gid=1000`, группы: `sudo`, `docker(999)`. +- `/home/slin/repos` → `/repos` (rw); на хосте `/repos` уже `1000:1000 rwxrwxr-x`. +- `docs/work-items/*` на хосте — `root:root` (наследие прошлых прогонов). + +## 2. Цель + +Агенты конвейера **не должны** создавать `root`-файлы в хостовом репозитории. +После любого прогона конвейера `git pull/status/reset` под `slin` на хосте +работает **без ручного chown**. + +## 3. Объём (scope) + +В объёме: +- Изменение runtime-режима контейнера так, чтобы артефакты создавались под + `uid:gid` хоста (`1000:1000`). +- Сохранение работоспособности: claude-auth (preflight), git/ssh, docker.sock + (деплой), запуск конвейера. +- Обновление документации (INFRA.md, CHANGELOG, ADR с обоснованием варианта). +- Проверка на staging (8501) ДО прода. + +Вне объёма: +- Массовое исправление прав уже существующих `root:root` файлов в истории + (разовый `chown` на хосте делает Owner; в задаче — только описать команду). +- Изменение логики конвейера, QG, схемы БД. +- Смена модели/effort агентов, прочие фичи. + +## 4. Заинтересованные стороны +- Owner (Слава) — заказчик, владелец хоста mva154. +- Стрим — разведка/контекст. +- Проект enduro-trails — co-tenant того же прод-инстанса (групповой риск). + +## 5. Ограничения и риски (off-limits) + +Self-hosting: прод-инстанс `orchestrator` ОДИН на все прод-проекты, общая БД и +очередь. **Нельзя ломать**: запуск конвейера, доступ к Plane/Gitea/SSH из агентов, +docker.sock. Любой рестарт контейнера под новым uid — **только в окно тишины** +(нет активных задач). Тестировать на staging ПЕРЕД продом. + +### Известные мины (подтверждены разведкой) +- **МИНА 1 — docker.sock**: `/var/run/docker.sock` = `srw-rw---- root:999`. + Доступ идёт через gid 999, не через root. При переходе на непривилегированный + uid обязателен supplementary group `999`. *В текущем `docker-compose.yml` уже + есть `group_add: ["999"]` для обоих сервисов — учесть, не сломать.* +- **МИНА 2 — claude creds (БЛОКЕР)**: `/home/slin/.claude/.credentials.json` = + `root:root 0600`. Сейчас читает контейнер-root. Под `uid=1000` без доступа → + `claude-auth` ломается → весь конвейер умирает (preflight ORCH-044 заворачивает). + Проверить ПЕРВЫМ. +- **МИНА 3 — claude бинарь**: реальный бинарь `/opt/claude-code/bin/claude.exe` + (root:root, `+x` для всех — ok). `ORCH_CLAUDE_BIN=/usr/bin/claude` в env не + существует; launcher использует hardcode `CLAUDE_BIN=/opt/claude-code/bin/claude.exe`. + Под uid 1000 исполним, но проверить запуск. +- **SSH-маунт**: `/home/slin/.orchestrator-ssh` → `/root/.ssh:ro`. При смене uid + HOME/домашний каталог меняется — путь к ключам нужно поправить (деплой по ssh). +- **HOME**: launcher форсит `HOME=/home/slin` (две точки: env Popen и git_env). + Креды читаются из `/home/slin/.claude`. Учесть при смене uid. + +## 6. Бизнес-ценность +Устранение постоянного ручного `chown` после каждого деплоя; деплой прода +перестаёт ломаться на правах; снимается источник простоя конвейера всех проектов. + +## 7. Допущения +- Хост-каталоги `/app/data` и `/repos` уже `1000:1000` (запись под uid 1000 пройдёт). +- Dockerfile уже содержит `git config --system --add safe.directory '*'`. +- Окно тишины для рестарта контейнера согласуется с Owner. diff --git a/docs/work-items/ORCH-040/02-trz.md b/docs/work-items/ORCH-040/02-trz.md new file mode 100644 index 0000000..8e1082a --- /dev/null +++ b/docs/work-items/ORCH-040/02-trz.md @@ -0,0 +1,86 @@ +# 02 — ТЗ: agent-файлы под uid хоста (не root) + +Work Item: **ORCH-040** + +## 1. Суть требования +Артефакты конвейера (worktree + docs) должны создаваться на хосте под +`uid:gid = 1000:1000` (slin), а не `root:root`. При этом сохраняется работа +claude-auth, git, ssh-деплоя и docker.sock. + +## 2. Задействованные модули и файлы + +| Файл | Роль в задаче | +|------|----------------| +| `docker-compose.yml` | runtime-режим контейнера (prod `orchestrator` + `orchestrator-staging`). Основная точка изменения. | +| `Dockerfile` | возможные правки под непривилегированный запуск (safe.directory уже есть; при необходимости — создание пользователя/прав). | +| `src/agents/launcher.py` | `HOME=/home/slin` хардкод (env Popen ~стр.326 и git_env ~стр.513); путь `CLAUDE_BIN` (стр.187). Проверить совместимость при смене uid; править ТОЛЬКО при необходимости. | +| `docs/operations/INFRA.md` | блок «Тома (volumes)» (SSH-маунт `/root/.ssh`), карта рантайма — обновить. | +| `CHANGELOG.md` | запись об изменении. | +| `docs/work-items/ORCH-040/06-adr/` | ADR с выбором варианта + обоснованием (создаёт архитектор). | + +## 3. Варианты решения (вход для ADR — выбор и обоснование за архитектором) + +> Анализ фиксирует варианты как требование «выбрать и обосновать в ADR». +> Рекомендация разведки — Вариант 1. + +1. **Вариант 1 (рекомендован): `user: "1000:1000"` в docker-compose.** + Все файлы сразу `slin:slin`, git на хосте без chown. Обязательные довески: + - сохранить/проверить `group_add: ["999"]` (docker.sock) — **уже присутствует**; + - обеспечить доступ uid 1000 к claude creds (`/home/slin/.claude/.credentials.json`): + `chown 1000:1000` на хосте ИЛИ права на чтение для 1000 (задокументировать); + - поправить SSH-маунт: `/home/slin/.orchestrator-ssh` → домашний каталог uid 1000 + (`/home/slin/.ssh`), а не `/root/.ssh`; согласовать с `HOME` в launcher; + - проверить запуск `claude.exe` + `git` + `ssh` под uid 1000. + +2. **Вариант 2: subprocess агента под непривилегированным uid внутри контейнера** + (`Popen preexec_fn setuid` / `gosu`). Точечно, но сложнее; контейнер остаётся root. + +3. **Вариант 3 (fallback, костыль): chown-хук нормализации прав после стадии** + (`chown -R 1000:1000` worktree/доки). Лечит симптом, не корень. Применять, только + если В1 неустранимо рвёт creds/sock. + +## 4. Требуемые изменения (при выбранном Варианте 1) + +### 4.1 docker-compose.yml (оба сервиса: `orchestrator`, `orchestrator-staging`) +- Добавить `user: "1000:1000"`. +- Сохранить `group_add: ["999"]` (НЕ удалять). +- Изменить SSH-маунт: target `/root/.ssh` → каталог `.ssh` пользователя 1000, + синхронно с `HOME`, который форсит launcher (`/home/slin`). То есть привести к + единому HOME: маунт `/home/slin/.orchestrator-ssh` → `/home/slin/.ssh:ro`. +- Маунт `/home/slin/.claude` и `.claude.json` — оставить; проверить доступ uid 1000. + +### 4.2 Доступ к claude creds +- Обеспечить, что `/home/slin/.claude/.credentials.json` читается uid 1000 + (на хосте — операция Owner; в ТЗ зафиксировать команду и проверку). + +### 4.3 src/agents/launcher.py +- Проверить, что `HOME=/home/slin` остаётся валиден под uid 1000 (домашний каталог + существует и доступен). Менять ТОЛЬКО при доказанной необходимости. +- Не менять CLAUDE_BIN, если запуск под 1000 подтверждён. + +### 4.4 Dockerfile +- Менять при необходимости (например, гарантировать существование `/home/slin` и + права). `git config --system --add safe.directory '*'` уже есть — оставить. + +## 5. Изменения API +Нет. + +## 6. Изменения схемы БД +Нет. + +## 7. Новые QG checks +Нет. Существующий staging-гейт (`check_staging_status`, ORCH-35) — обязательная +страховка перед прод-деплоем self (без изменений). + +## 8. Артефакты pipeline, которые должны быть созданы/обновлены +- `06-adr/ADR-NNN-.md` — выбор варианта + обоснование (мины 1–3, SSH, HOME). +- `docs/operations/INFRA.md` — обновить блок volumes (SSH target) и, при изменении + режима, упоминание uid рантайма. +- `CHANGELOG.md` — запись `fix:`/`refactor:` по Conventional Commits. +- `12-review.md`, `13-test-report.md`, `15-staging-log.md` — по ходу конвейера. + +## 9. Порядок безопасного внедрения (требование) +1. Живая разведка прав creds/sock/ssh ДО кода. +2. Применить и проверить на **staging (8501)** end-to-end. +3. Прод-рестарт контейнера под новым uid — только в окно тишины (нет активных задач). +4. Регресс на хосте: новые tracked-артефакты `1000:1000`, `git pull` под slin без ошибок. diff --git a/docs/work-items/ORCH-040/03-acceptance-criteria.md b/docs/work-items/ORCH-040/03-acceptance-criteria.md new file mode 100644 index 0000000..ffa72dc --- /dev/null +++ b/docs/work-items/ORCH-040/03-acceptance-criteria.md @@ -0,0 +1,55 @@ +# 03 — Критерии приёмки: ORCH-040 + +Work Item: **ORCH-040** + +Каждый критерий имеет чёткое условие PASS/FAIL. Задача считается принятой, когда +**все** критерии = PASS. + +## AC-1 — Артефакты создаются под uid хоста (корневой критерий) +- **PASS**: после прогона тестовой задачи конвейером end-to-end новые tracked-файлы + в `/home/slin/repos/orchestrator/docs/work-items/*` и в worktree + (`/repos/_wt/...`) имеют владельца `slin:slin` (1000:1000). + `ls -ld /home/slin/repos/orchestrator/docs/work-items/*` → НЕ `root:root`. +- **FAIL**: появляются новые `root:root` tracked-файлы. + +## AC-2 — git под slin работает без ручного chown +- **PASS**: на хосте под `slin` `git -C /home/slin/repos/orchestrator pull`, + `git status`, `git reset` выполняются без `Permission denied` / + `insufficient permission for adding an object`. +- **FAIL**: любая из команд падает на правах. + +## AC-3 — claude-агенты стартуют (preflight ok) +- **PASS**: `claude-auth`/preflight проходит; агент конвейера запускается и + завершается `exit_code=0` (не `Not logged in`, не отказ доступа к creds). +- **FAIL**: агент падает на авторизации/чтении `/home/slin/.claude`. + +## AC-4 — docker.sock доступен (деплой не сломан) +- **PASS**: из контейнера под новым uid `docker ps` / docker-операции деплоя + (ORCH-36 путь) работают — доступ через gid 999 сохранён (`group_add: ["999"]`). +- **FAIL**: docker-операции отваливаются (`permission denied` на сокете). + +## AC-5 — SSH-деплой работает +- **PASS**: ssh-ключи читаются из домашнего каталога нового uid; деплой-хук по ssh + (`DEPLOY_SSH_*`) выполняется. +- **FAIL**: ssh не находит/не читает ключи (маунт указывает на чужой HOME). + +## AC-6 — Конвейер не сломан (без регресса) +- **PASS**: тестовая задача проходит стадии без падения запуска конвейера; доступ к + Plane/Gitea из агентов сохранён; `pytest tests/ -q` зелёный. +- **FAIL**: конвейер встаёт / тесты падают. + +## AC-7 — Проверено на staging ДО прода +- **PASS**: изменение прогнано на staging (8501), `15-staging-log.md` → + `staging_status:` положительный; прод-рестарт выполнен в окно тишины. +- **FAIL**: изменение применено сразу на прод без staging-прогона. + +## AC-8 — Документация обновлена (golden source) +- **PASS**: `docs/operations/INFRA.md` (блок volumes / SSH target / uid рантайма) + и `CHANGELOG.md` обновлены; ADR с выбором варианта и обоснованием создан в + `06-adr/`. Reviewer подтверждает. +- **FAIL**: код изменён, документация/ADR не обновлены. + +## AC-9 — Прод-контейнер не уронен вне окна тишины +- **PASS**: рестарт self выполнен без активных задач; конвейер enduro-trails не + пострадал. +- **FAIL**: рестарт во время активных задач / падение прод-инстанса. diff --git a/docs/work-items/ORCH-040/04-test-plan.yaml b/docs/work-items/ORCH-040/04-test-plan.yaml new file mode 100644 index 0000000..253a86b --- /dev/null +++ b/docs/work-items/ORCH-040/04-test-plan.yaml @@ -0,0 +1,81 @@ +work_item: ORCH-040 +description: > + Инфра-фикс: контейнер/агенты не плодят root-файлы в хостовом репо. + Часть проверок автоматизируема через pytest (валидация compose-конфига), + часть — обязательные ops/integration проверки на staging и хосте (manual), + т.к. касаются прав файловой системы хоста и рантайма docker. + +tests: + # --- Автоматизируемые (pytest, парсинг docker-compose.yml) --- + - id: TC-01 + type: unit + description: > + docker-compose.yml: оба сервиса (orchestrator, orchestrator-staging) + имеют user: "1000:1000" (при выборе Варианта 1). + module: tests/test_orch040_compose.py + expected: PASS + + - id: TC-02 + type: unit + description: > + docker-compose.yml: оба сервиса сохраняют group_add со значением "999" + (доступ к docker.sock не потерян — МИНА 1). + module: tests/test_orch040_compose.py + expected: PASS + + - id: TC-03 + type: unit + description: > + docker-compose.yml: SSH-маунт согласован с HOME агента — target каталога + .ssh лежит под /home/slin (а не /root/.ssh), для обоих сервисов. + module: tests/test_orch040_compose.py + expected: PASS + + - id: TC-04 + type: unit + description: > + launcher: HOME, форсимый в окружении агента и git_env, указывает на каталог, + совместимый с SSH/claude-маунтами (/home/slin) — нет рассинхрона HOME vs uid. + module: tests/test_orch040_compose.py + expected: PASS + + # --- Регресс существующего поведения --- + - id: TC-05 + type: unit + description: > + Весь существующий набор тестов зелёный (нет регресса логики конвейера/launcher). + module: tests/ # pytest tests/ -q + expected: PASS + + # --- Integration / ops (staging 8501, затем хост) --- + - id: TC-06 + type: integration + description: > + На staging (8501) прогнать тестовую задачу конвейером end-to-end; артефакты + worktree и docs создаются под 1000:1000 (НЕ root:root). Проверка AC-1. + module: scripts/staging_check.py # + ls -ld на хосте + expected: PASS + + - id: TC-07 + type: integration + description: > + После staging-прогона на хосте под slin: git -C /home/slin/repos/orchestrator + pull/status/reset без Permission denied. Проверка AC-2. + module: manual/host-check + expected: PASS + + - id: TC-08 + type: integration + description: > + claude preflight/auth проходит под новым uid: агент стартует и завершается + exit_code=0 (creds /home/slin/.claude читаются). Проверка AC-3 (МИНА 2). + module: manual/staging-agent-run + expected: PASS + + - id: TC-09 + type: integration + description: > + docker.sock доступен из контейнера под uid 1000 (docker ps работает) и + ssh-деплой-хук выполняется. Проверка AC-4, AC-5 (МИНА 1 + SSH). + module: manual/staging-deploy-path + expected: PASS