"""ORCH-019 — composition with ORCH-088 serial-gate / ORCH-089 auto-label (AC-9). Covers (04-test-plan.yaml): TC-14 A bug-fast-track task is an ORDINARY repo task for the serial gate (ORCH-088): it counts as an active task and is gated like any other — it does NOT bypass serialisation. autoApprove/autoDeploy (ORCH-089) apply on the bug track (scope is repo-based, track-agnostic). """ import os import tempfile import pytest os.environ["ORCH_DB_PATH"] = os.path.join(tempfile.gettempdir(), "test_bft_composition.db") os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") import src.db as db # noqa: E402 from src.db import init_db, get_db, enqueue_job, claim_next_job # noqa: E402 from src import serial_gate, labels, config as cfg # noqa: E402 @pytest.fixture(autouse=True) def fresh_db(tmp_path, monkeypatch): dbfile = tmp_path / "comp.db" monkeypatch.setattr(db.settings, "db_path", str(dbfile)) monkeypatch.setattr(cfg.settings, "serial_gate_enabled", True, raising=False) monkeypatch.setattr(cfg.settings, "serial_gate_repos", "", raising=False) monkeypatch.setattr(cfg.settings, "serial_gate_freeze_enabled", False, raising=False) monkeypatch.setattr(cfg.settings, "task_deps_enabled", False, raising=False) monkeypatch.setattr(cfg.settings, "bug_fast_track_enabled", True, raising=False) monkeypatch.setattr(cfg.settings, "auto_label_enabled", True, raising=False) init_db() yield def _make_task(work_item_id, stage="analysis", repo="orchestrator", track="full"): conn = get_db() cur = conn.execute( "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage, title, track) " "VALUES (?, ?, ?, ?, ?, ?, ?)", (work_item_id, work_item_id, repo, f"feature/{work_item_id}", stage, work_item_id, track), ) tid = cur.lastrowid conn.commit() conn.close() return tid def test_tc14_bug_task_counts_as_active_in_serial_gate(): # An EARLIER bug task A (unfinished) must gate a later task B's analyst-job — # a bug task does NOT bypass the serial gate. _make_task("ORCH-301", stage="development", track="bug") # active bug predecessor b = _make_task("ORCH-302", stage="analysis", track="full") # new task enqueue_job("analyst", "orchestrator", "B", task_id=b) assert claim_next_job() is None, "a bug task must gate a later analyst-job (no bypass)" # The bug task is the active task in the snapshot. per = serial_gate.snapshot()["per_repo"]["orchestrator"] assert per["active_task"]["work_item_id"] == "ORCH-301" def test_tc14_bug_task_itself_gated_behind_predecessor(): # The bug task is also HELD behind an earlier non-bug task (symmetry). _make_task("ORCH-310", stage="development", track="full") # active predecessor b = _make_task("ORCH-311", stage="analysis", track="bug") # new BUG task enqueue_job("analyst", "orchestrator", "bug-B", task_id=b) assert claim_next_job() is None, "a bug task is itself serialised behind the predecessor" def test_tc14_bug_task_claimable_once_predecessor_done(): a = _make_task("ORCH-320", stage="development", track="full") b = _make_task("ORCH-321", stage="analysis", track="bug") jid = enqueue_job("analyst", "orchestrator", "bug-B", task_id=b) assert claim_next_job() is None # Finish A -> the bug task's analyst-job is now claimable. conn = get_db() conn.execute("UPDATE tasks SET stage='done' WHERE id=?", (a,)) conn.commit() conn.close() claimed = claim_next_job() assert claimed is not None and claimed["id"] == jid def test_tc14_auto_label_applies_track_agnostic(monkeypatch): # autoApprove/autoDeploy scope is repo-based, independent of the bug track. assert labels.auto_approve_applies("orchestrator") is True assert labels.auto_deploy_applies("orchestrator") is True