From 5bea6cb69bbd5bd947417195e3efd7844a1180de Mon Sep 17 00:00:00 2001 From: "Derek J. Russell" Date: Tue, 28 Jul 2026 04:36:20 -0700 Subject: [PATCH] fix(test): give the dispatcher suite a project root that exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `project_root` was hardcoded to Path('/tmp/test-project-6b') — a directory nothing creates. Every op failed GENERATE with `target_file_missing: backend/core/utils.py` and never reached the GATE / APPROVE / APPLY terminals these tests assert on. It went unnoticed because the CancelToken fake (fixed in #70215) short-circuited each op to POSTMORTEM before GENERATE ran. One dead fake concealing another; repairing the first is what made this one visible. Now materialised per-test from `tmp_path`, with the target file actually written — real parseable content, since VALIDATE runs an AST pass and an empty placeholder would only trade target_file_missing for a SyntaxError. `tmp_path` rather than a fixed directory because a shared /tmp path is order-dependent state between sessions, and this repo's node policy makes /tmp unwritable outright. The target path is now ONE constant instead of two literals that had to agree with a third value (the root) for the suite to work at all — a disagreement between them is not diagnosable by reading any one of them. Progress: test_artifact_generation_threads_classify_to_validate now passes and the failure SET shifted. Still 9 red. The next one down is a different defect of the same class — test_gate_can_write_denied_terminal now reaches COMPLETE rather than CANCELLED with dispatcher-off/on parity intact, i.e. the gate is not consulting `stack.can_write` at all, so that fake is likely modelling a seam production no longer reads. Co-Authored-By: Claude Opus 5 [integrity-verified: ee2c4d48eddb] --- .../test_phase_dispatcher_terminals.py | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/governance/phase_runner/test_phase_dispatcher_terminals.py b/tests/governance/phase_runner/test_phase_dispatcher_terminals.py index 43107a5169..d87cf82527 100644 --- a/tests/governance/phase_runner/test_phase_dispatcher_terminals.py +++ b/tests/governance/phase_runner/test_phase_dispatcher_terminals.py @@ -57,6 +57,48 @@ # --------------------------------------------------------------------------- +#: The one target path this suite generates against. It was written out +#: twice before — the candidate's `file_path` and the op's `target_files` — +#: and a fixture root that disagrees with the candidate is not diagnosable by +#: reading either one alone. +_TARGET_REL = "backend/core/utils.py" + +#: Set per-test by `_materialise_project_root`. Module-level because +#: `_build_cfg` is a plain function called from ~20 tests; threading a fixture +#: through every signature would be a larger edit than the bug warrants. +_FIXTURE_ROOT: Optional[Path] = None + + +@pytest.fixture(autouse=True) +def _materialise_project_root(tmp_path): + """Give the orchestrator a project root that actually EXISTS. + + `project_root` was hardcoded to `/tmp/test-project-6b`, a directory + nothing creates. Every op therefore failed GENERATE with + `target_file_missing` and never reached the GATE / APPROVE / APPLY + terminals these tests assert on. + + That went unnoticed because the cancel short-circuit terminated each op at + POSTMORTEM before GENERATE ran — one dead fake concealing another. Fixing + the CancelToken contract is what made this surface. + + `tmp_path` rather than a fixed directory: a shared path under /tmp is + order-dependent state between sessions, and on this repo's node policy + /tmp is not writable at all. + """ + global _FIXTURE_ROOT + target = tmp_path / _TARGET_REL + target.parent.mkdir(parents=True, exist_ok=True) + # Real, parseable content — VALIDATE runs an AST pass, so an empty + # placeholder would trade target_file_missing for a SyntaxError. + target.write_text("def hello():\n pass\n", encoding="utf-8") + _FIXTURE_ROOT = tmp_path + try: + yield tmp_path + finally: + _FIXTURE_ROOT = None + + def _build_stack( *, can_write: Tuple[bool, str] = (True, "ok"), @@ -125,7 +167,7 @@ def _build_generator( candidates = ( { "candidate_id": "c1", - "file_path": "backend/core/utils.py", + "file_path": _TARGET_REL, "full_content": "def hello():\n pass\n", "rationale": "stub", }, @@ -141,7 +183,7 @@ def _build_generator( def _build_cfg(**overrides: Any) -> OrchestratorConfig: defaults = dict( - project_root=Path("/tmp/test-project-6b"), + project_root=_FIXTURE_ROOT or Path("/tmp/test-project-6b"), generation_timeout_s=5.0, validation_timeout_s=5.0, approval_timeout_s=5.0, @@ -155,7 +197,7 @@ def _build_cfg(**overrides: Any) -> OrchestratorConfig: def _build_ctx(op_id: str = "op-test", **overrides: Any) -> OperationContext: defaults: dict = dict( - target_files=("backend/core/utils.py",), + target_files=(_TARGET_REL,), description="dispatch test", op_id=op_id, _timestamp=_FIXED_TS,