Files
orchestrator/docs/history/BUGFIXES_2026-06-02_ORCH2.md
Dev Agent 7c68d1d812
All checks were successful
CI / test (pull_request) Successful in 9s
docs(orchestrator): adopt enduro doc canon + CLAUDE.md + ADR (ORCH-9)
2026-06-05 12:33:55 +03:00

5.2 KiB
Raw Blame History

ORCH-2 / S-4 — git worktree per task (изоляция shared /repos)

Дата: 2026-06-02 Ветка: feature/ORCH-2-worktree Источник: AUDIT_2026-06-02.md (SERIOUS S-4), DEV_TASK_ORCH2_WORKTREE.md Исполнитель: Dev (Opus 4.8 Tokenator)

Проблема (S-4)

Все git-операции (launcher.launch cmd, _monitor_agent commit/push, check_tests_local) делали git checkout <branch> в одном общем /repos/<repo>. При двух активных задачах checkout одной перетирал рабочую копию другой → гонки (на ET-009 это дало «два коллектора» и путаницу веток).

Решение

git worktree per branch. Каждая задача (ветка) работает в изолированной рабочей копии:

/repos/<repo>                      ← основной clone (fetch / worktree mgmt / read-only)
/repos/_wt/<repo>/<safe-branch>    ← worktree задачи (рабочая копия агента)

Изменения

Файл Что
src/config.py + worktrees_dir: str = "/repos/_wt"
src/git_worktree.py (новый) _safe, get_worktree_path, ensure_worktree, remove_worktree
src/agents/launcher.py launch(): ветка резолвится заранее → ensure_worktree; cmd = cd <worktree> без git checkout; _write_task_file(repo, branch, ...) пишет в worktree; _monitor_agent commit/push в worktree (checkout убран); чтение 01-questions.md/10-conflict.md из worktree; QG-диспетчер прокидывает branch
src/qg/checks.py _repo_path(repo, branch) helper (worktree если есть, иначе shared); артефакт-чеки получили опциональный branch; check_tests_localensure_worktree + make test в worktree (TODO про S-4 удалён)
src/webhooks/plane.py QG-диспетчер прокидывает branch; review-файл fallback читается из worktree
src/webhooks/gitea.py git branch -r --contains <sha> — подтверждено read-only, оставлено в main clone (+ комментарий)
tests/test_git_worktree.py (новый) покрытие _safe/get_worktree_path/ensure_worktree/remove_worktree + изоляция двух веток (реальные локальные git-репо в tmp, без сети)
tests/test_launcher.py TestWriteTaskFile обновлён под новую сигнатуру (запись в worktree)
docs/ARCHITECTURE.md раздел «Изоляция через git worktree»; убран пункт про shared-checkout гонки

Совместимость с прежними фиксами

  • B-1 (запись task-файла без docker, прямой open()): сохранена — теперь путь = worktree.
  • B-2 (Popen stdout → файл, monitor proc.wait() без зомби): не тронут.
  • S-5 (check_reviewer_verdict — только YAML-frontmatter): не тронут, добавлен лишь worktree-путь.
  • S-1 (check_tests_local — свой make test вместо Gitea CI): сохранён, тесты теперь в worktree.

Обратная совместимость QG-диспетчеризации: артефакт-чеки принимают branch опционально (default None → shared /repos/<repo>), поэтому существующие 2-арг вызовы/тесты не сломаны.

Проверка

# Тесты (в контейнере через образ — хостовый .venv сломан):
IMG=$(docker inspect orchestrator --format '{{.Config.Image}}')
docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q
# → 37 passed, 9 failed (pre-existing test_webhooks 401/signature — НЕ относятся к ORCH-2,
#   идентичны baseline на main).

# test_git_worktree.py изолированно → 9 passed.

Тест изоляции (в работающем контейнере)

docker exec orchestrator python3 -c "
import sys; sys.path.insert(0,'/app')
from src.git_worktree import ensure_worktree
import subprocess
p1 = ensure_worktree('enduro-trails','feature/wt-test-A')
p2 = ensure_worktree('enduro-trails','feature/wt-test-B')
b1 = subprocess.run(['git','-C',p1,'branch','--show-current'],capture_output=True,text=True).stdout.strip()
b2 = subprocess.run(['git','-C',p2,'branch','--show-current'],capture_output=True,text=True).stdout.strip()
assert p1!=p2 and b1!=b2, 'NOT ISOLATED'
print('ISOLATION OK', p1, p2, b1, b2)
"

(Результат прогона на сервере — см. ниже / в отчёте Стрим.)

Ограничения / заметки

  • Очередь задач (ORCH-1 / F-2b) не входит в эту задачу.
  • remove_worktree существует, но автоматический вызов при done не подключён (опционально, отдельным шагом).