From 3a1972875f529284684a7a1b680f80c9daab4db1 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 16 Jun 2026 20:12:28 +0300 Subject: [PATCH] fix(tests): isolate repos_dir in ORCH-123 staging-runner test fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deterministic test-runner gate (full `pytest tests/`) failed on test_orch123_staging_runner_exec.py::test_r2_held_deploy_staging_not_rolled_back once ORCH-124 reached the testing stage. Root cause (pre-existing latent regress, surfaced — not introduced — by ORCH-124): the fixture isolated `worktrees_dir` but not `repos_dir`. `check_staging_status` falls back to `/` (and its origin/main) when the feature worktree is absent. After ORCH-123 merged, the real `/repos/orchestrator/docs/work-items/ORCH-123/15-staging-log.md` (verdict SUCCESS) exists on disk, so the intended-RED staging gate read it and went green -> advance_stage was called -> the R-2 assertion failed. Order-dependent: the test passed alone, failed in the full suite. Fix: isolate `settings.repos_dir` to an empty tmp subdir in the fixture (mirroring the existing worktrees_dir isolation) so the staging gate is deterministically "not found" -> red, regardless of suite ordering. The ORCH-123 R-2 invariant (a held deploy-staging task is never rolled back to development, adr-0049/ADR-001 D4) is preserved and strengthened — the fix only restores the test's stated premise. src/** / STAGE_TRANSITIONS / QG_CHECKS / check_* untouched (test-only change). Refs: ORCH-124 Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + tests/test_orch123_staging_runner_exec.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792d499..82ea966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - **Условность/откат (D6):** независимый под-флаг `serial_gate_pause_enabled` (env `ORCH_SERIAL_GATE_PAUSE_ENABLED`, дефолт `True`; зеркало `serial_gate_freeze_enabled`; область переиспользует `serial_gate_repos`, новый `*_repos` не вводится). Дефолт `True` — **истинный no-op** до явной операторской паузы (`paused_at` всюду NULL). `False` ⇒ pause-терм опущен из SQL, эндпоинты no-op, serial-gate **байт-в-байт ORCH-088/090** (осознанный rollback-режим). Глубже — `serial_gate_enabled=false`. - **Покрытие:** `tests/test_orch124_serial_gate_pause.py` (TC-01 обязательный регресс инцидента ORCH-116/ORCH-123 — красный до фикса, зелёный после; TC-02…TC-15: анти-регресс ORCH-088, durable/restart, resume, сохранность freeze/dependency, снапшот-reason, анти-дрейф 3 точек, offline hot-path, never-raise/fail-OPEN, kill-switch-нейтральность, структурный анти-регресс реестров/схем). - **Доки:** обновлены `docs/architecture/README.md` (раздел serial-gate + ось «пауза без блокировки») и `docs/architecture/internals.md` (ось «пауза» ⊥ оси «терминальность»); сквозной ADR `adr-0051`. + - **Тест-гигиена (development-стадия, латентный регресс ORCH-123):** изолирован `settings.repos_dir` в фикстуре `tests/test_orch123_staging_runner_exec.py` (зеркально уже имевшейся изоляции `worktrees_dir`). `check_staging_status` при отсутствии фиче-worktree фолбэчит на `/` (и его `origin/main`); после мержа ORCH-123 реальный `/repos/orchestrator/docs/work-items/ORCH-123/15-staging-log.md` (вердикт SUCCESS) появился на диске и делал предполагавшийся-КРАСНЫМ staging-гейт в `test_r2_held_deploy_staging_not_rolled_back` зелёным при полном прогоне `pytest tests/` (order-dependent: тест проходил в одиночку, падал в сьюте). Инвариант ORCH-123 R-2 («held `deploy-staging` не откатывается на `development`», adr-0049/ADR-001 D4) **сохранён и усилен** — изоляция лишь восстанавливает заявленную предпосылку теста «15-staging-log.md отсутствует ⇒ гейт красный». `src/**`/`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*` не тронуты (правка только теста). - **Детерминированный test-раннер вместо LLM-тестера на `testing`** (ORCH-116, `feat`): второй реализованный срез determinization-roadmap (ORCH-118 A5, `needs-hybrid-fallback`) — на стадии `testing` для self-hosting `orchestrator` **LLM-агент `tester` заменён детерминированным кодом** (`src/test_runner.py`). PASS/FAIL-ядро агента было деривируемым (регресс `pytest` + read-only smoke → `result:`); каждый прогон жёг токены/время opus-агента (~60–150k / 5–20 мин) и встраивал недетерминизм LLM в точку ветвления `testing → deploy-staging` / `testing → development`. **Инвариант (NFR-1):** это замена *продюсера* артефакта, **не** гейта — контракт `13-test-report.md`, гейт `check_tests_passed`/`_parse_tests_verdict`, `STAGE_TRANSITIONS`, machine-verdict `result:` (+ legacy `verdict:`/`status:`), схема БД — **байт-в-байт не тронуты**. Аддитивно, под kill-switch, never-raise, fail-closed, скоуп self-hosting, гибрид (LLM строго off-control-path). Эталон — `src/staging_runner.py` (ORCH-115). ADR: `docs/work-items/ORCH-116/06-adr/ADR-001-deterministic-test-runner.md`, сквозной `docs/architecture/adr/adr-0050-deterministic-test-runner.md`. - **Перехват в `launch_job` до `_spawn` (D1):** `if job.agent=="tester" and test_runner.should_intercept(job)` → `_run_test_runner_job` (зеркало `_run_staging_runner_job`, прецедент `deploy-finalizer`/`post-deploy-monitor`/`staging-runner` `launcher.py:397/402/405`): синхронно ведёт `jobs`-строку через `mark_job`, возвращает `None` (нет `agent_runs`, нет токенов). Дискриминатор — роль `tester` **И** стадия задачи `testing` (defense-in-depth: `tester` — единственный агент входа в `testing`, коллизии стадий нет, в отличие от общей роли `deployer`) **И** `applies(repo)`; `should_intercept` never-raise → `False` → штатный `_spawn` (fail-safe к LLM-пути). - **Leaf `src/test_runner.py` (новый, чистый never-raise):** по образцу `staging_runner`/`self_deploy`/`proc_group` (на импорте только `config`/`proc_group`; `db`/`git_worktree`/`self_deploy`/`qg.checks`/`stage_engine`/`notifications` — лениво). `applies(repo)` = kill-switch `test_runner_enabled` + скоуп `test_runner_repos` (пусто → self-hosting only) **И** резолв тест-контракта `_has_test_contract` (BR-9: репо без контракта → `False` → LLM-tester — enduro-trails 1:1 как до ORCH-116, даже если руками добавлен в CSV). Исполняет регресс `python -m pytest ` **в worktree ветки** (`git_worktree.get_worktree_path`, анти checkout-гонка ORCH-112) через `proc_group.run_in_process_group` (tree-kill, таймаут `test_runner_timeout_s=900`, малформ/непозитив → дефолт + WARNING) + опц. **read-only smoke** (`/health`/`/status`/`/queue` + блок `serial_gate`, stdlib `urllib`; транзиентная недостижимость — ограниченный ретрай, не-200/нет блока — немедленный FAIL; `test_runner_smoke_enabled`). Маппит exit-код **единым** контрактом `self_deploy.map_exit_code_to_status` в токенах `result:` (`0→PASS`/иначе/None→`FAIL`, fail-closed; smoke-провал AND-ится в `FAIL`); пишет `13-test-report.md` (тот же machine-key `result:` UPPERCASE + 52c-схема, `author_agent: test-runner`/`model_used: n/a`) + best-effort push в **фичеветку**; вызывает **существующий** `advance_stage(current_stage="testing", finished_agent="tester")` — без новых рёбер/исходов (transition-lease ORCH-114 берётся внутри `advance_stage` — граница O1). diff --git a/tests/test_orch123_staging_runner_exec.py b/tests/test_orch123_staging_runner_exec.py index 9950184..f803f07 100644 --- a/tests/test_orch123_staging_runner_exec.py +++ b/tests/test_orch123_staging_runner_exec.py @@ -50,6 +50,13 @@ def fresh_db(monkeypatch, tmp_path): os.unlink(_test_db) init_db() monkeypatch.setattr("src.git_worktree.settings.worktrees_dir", str(tmp_path), raising=False) + # Isolate repos_dir too: check_staging_status falls back to / + # (and origin/main on it) when the feature worktree is absent. Without this the + # gate would read REAL on-disk artifacts from the shared clone (e.g. a merged + # ORCH-123/15-staging-log.md), turning the intended-RED gate in test_r2 green and + # making the suite order-dependent. Point it at an empty tmp subdir (no .git, no + # work-items) so the staging gate is deterministically "not found" -> red. + monkeypatch.setattr(cfg.settings, "repos_dir", str(tmp_path / "repos"), raising=False) # Runner ON, self-hosting scope, host-side strategy ON (defaults). monkeypatch.setattr(cfg.settings, "staging_runner_enabled", True, raising=False) monkeypatch.setattr(cfg.settings, "staging_runner_repos", "", raising=False)