"""ORCH-094 — sync convergence for a done task stuck on a deploy status (TC-10). Integration-level: ANY sync source (reconciler tick / monitor tick / a direct deploy-status setter call) that touches a DB-done task converges Plane to Done idempotently instead of an intermediate deploy status, and a repeated tick does NOT swing the Done<->deploy-status pendulum. The guard lives on the setter (ADR-001 D1/D7), so the reconciler code itself is unchanged — driving the setter the way a stale actor would is the faithful reproduction of the 061 flapp. """ import os import tempfile import pytest _test_db = os.path.join(tempfile.gettempdir(), "test_reconciler_done_converge.db") os.environ["ORCH_DB_PATH"] = _test_db os.environ["ORCH_REPOS_DIR"] = tempfile.gettempdir() os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") from unittest.mock import MagicMock # noqa: E402 import src.db as _db # noqa: E402 from src.db import init_db, get_db # noqa: E402 from src import plane_sync # noqa: E402 from src import post_deploy # noqa: E402 from src import config as cfg # noqa: E402 @pytest.fixture(autouse=True) def fresh_db(monkeypatch, tmp_path): monkeypatch.setattr(_db.settings, "db_path", _test_db) if os.path.exists(_test_db): os.unlink(_test_db) init_db() monkeypatch.setattr(cfg.settings, "deploy_status_guard_enabled", True, raising=False) monkeypatch.setattr(cfg.settings, "deploy_status_guard_repos", "", raising=False) monkeypatch.setattr(post_deploy.settings, "repos_dir", str(tmp_path)) monkeypatch.setattr(post_deploy.settings, "host_repos_dir", str(tmp_path)) yield @pytest.fixture def spy(monkeypatch): direct = MagicMock() done = MagicMock() monkeypatch.setattr(plane_sync, "_set_issue_state_direct", direct) monkeypatch.setattr(plane_sync, "set_issue_done", done) monkeypatch.setattr(plane_sync, "_resolve_project_id", lambda w=None, p=None: "proj-1") monkeypatch.setattr( plane_sync, "get_project_states", lambda pid: {"awaiting_deploy": "S-aw", "deploying": "S-dep", "monitoring": "S-mon"}, ) return direct, done def _make_task(stage="done", repo="orchestrator", wi="ORCH-061"): conn = get_db() conn.execute( "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage) " "VALUES (?, ?, ?, ?, ?)", (f"plane-{wi}", wi, repo, "feature/ORCH-061-x", stage), ) conn.commit() conn.close() def test_tc10_repeated_sync_converges_no_pendulum(spy): direct, done = spy _make_task("done") # done, window closed (no ARMED sentinel) # Simulate many sync ticks alternately trying to set Monitoring / Awaiting, # exactly like the observed 061 pendulum (Awaiting <-> Monitoring forever). for i in range(10): if i % 2 == 0: plane_sync.set_issue_monitoring("ORCH-061", reason="reconciler-tick") else: plane_sync.set_issue_awaiting_deploy("ORCH-061", reason="reconciler-tick") # Every tick converged to Done; not a single intermediate deploy-status PATCH. assert direct.call_count == 0 assert done.call_count == 10 # All convergence calls target the same terminal Done (no swing). assert all(c.args == ("ORCH-061",) for c in done.call_args_list)