Level A — merge/deploy serialization within one repo: reuse the existing ORCH-043/065 merge-lease (no new mechanism); the only new logic is an unconditional pre-merge rebase in check_branch_mergeable — under the held lease, auto_rebase_onto_main is ALWAYS called when premerge_rebase_always (default True), not just when the branch is behind. No-op on an up-to-date branch (rebase keeps HEAD, force-with-lease -> "Everything up-to-date", CI not triggered). Kill-switch off -> ORCH-043 behaviour 1:1. Level B — declarative task dependencies: additive job_deps table (CREATE ... IF NOT EXISTS, no live-DB migration); claim_next_job gate (NOT EXISTS) defers a job whose depends-on tasks are not yet 'done' without occupying a max_concurrency slot; inert on empty job_deps -> zero regression. New leaf src/task_deps.py (never-raise): is_task_ready (fail-open), DFS cycle detection + Blocked/alert, declare/ingest_plane_relations (db source never hits the network on the hot path), snapshot. Telegram waiting-line, /queue observability, reconciler skip + cycle backstop, reaper untouched. Invariants unchanged: STAGE_TRANSITIONS, QG_CHECKS registry (dep gate is a claim_next_job врезка, not a registered QG), DB schema of existing tables, HTTP endpoints; non-self repos remain a no-op on empty deps/scope. Flags: ORCH_PREMERGE_REBASE_ALWAYS, ORCH_TASK_DEPS_ENABLED, ORCH_TASK_DEPS_SOURCE. Docs: docs/architecture/README.md, CLAUDE.md, .env.example, CHANGELOG.md, adr-0015. Tests: tests/test_orch026_*.py (64 tests); full suite 991 green. Refs: ORCH-026 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
3.5 KiB
Python
83 lines
3.5 KiB
Python
"""ORCH-026 Level A (TC-A01): proactive pre-merge rebase.
|
|
|
|
check_branch_mergeable must ALWAYS rebase the task branch onto the current
|
|
origin/main under the held merge-lease when ``premerge_rebase_always`` is on —
|
|
even when ``branch_is_behind_main`` would short-circuit (no conflict, formally
|
|
not behind). With the flag OFF the ORCH-043 short-circuit is restored 1:1.
|
|
|
|
These are pure unit tests: every merge_gate primitive is monkeypatched, so no
|
|
git/network is touched — we assert the CONTROL FLOW (was auto_rebase_onto_main
|
|
called?) and the verdict.
|
|
"""
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_orch026_premerge.db"))
|
|
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
|
|
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")
|
|
|
|
from src import merge_gate # noqa: E402
|
|
from src.qg import checks # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def patched_gate(monkeypatch):
|
|
"""Patch merge_gate primitives; record whether auto_rebase ran."""
|
|
calls = {"rebase": 0, "retest": 0, "released": 0, "behind_checked": 0}
|
|
|
|
monkeypatch.setattr(checks.settings, "merge_gate_enabled", True, raising=False)
|
|
monkeypatch.setattr(checks.settings, "merge_gate_repos", "", raising=False)
|
|
|
|
monkeypatch.setattr(merge_gate, "acquire_merge_lease",
|
|
lambda *a, **k: (True, "lease acquired"), raising=False)
|
|
|
|
def _behind(repo, branch):
|
|
calls["behind_checked"] += 1
|
|
return False # NOT behind -> ORCH-043 would short-circuit
|
|
|
|
def _rebase(repo, branch):
|
|
calls["rebase"] += 1
|
|
return True, "rebased (noop)"
|
|
|
|
def _retest(repo, branch):
|
|
calls["retest"] += 1
|
|
return True, "green"
|
|
|
|
def _release(repo, branch=None):
|
|
calls["released"] += 1
|
|
|
|
monkeypatch.setattr(merge_gate, "branch_is_behind_main", _behind, raising=False)
|
|
monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", _rebase, raising=False)
|
|
monkeypatch.setattr(merge_gate, "retest_branch", _retest, raising=False)
|
|
monkeypatch.setattr(merge_gate, "release_merge_lease", _release, raising=False)
|
|
return calls
|
|
|
|
|
|
def test_always_rebases_even_when_not_behind(patched_gate, monkeypatch):
|
|
"""premerge_rebase_always=True -> auto_rebase_onto_main ALWAYS called (AC-A2)."""
|
|
monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False)
|
|
ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x")
|
|
assert ok is True
|
|
assert patched_gate["rebase"] == 1, "rebase must run even when not behind"
|
|
assert patched_gate["retest"] == 1, "re-test must run after the proactive rebase"
|
|
|
|
|
|
def test_flag_off_short_circuits_like_orch043(patched_gate, monkeypatch):
|
|
"""premerge_rebase_always=False -> not-behind short-circuit, no rebase (AC-A7)."""
|
|
monkeypatch.setattr(checks.settings, "premerge_rebase_always", False, raising=False)
|
|
ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x")
|
|
assert ok is True
|
|
assert reason == "branch up-to-date with main"
|
|
assert patched_gate["rebase"] == 0, "must NOT rebase when not behind and flag off"
|
|
|
|
|
|
def test_disabled_gate_is_noop(monkeypatch):
|
|
"""merge_gate_enabled=False -> pass-through, no lease/rebase at all (AC-G2)."""
|
|
monkeypatch.setattr(checks.settings, "merge_gate_enabled", False, raising=False)
|
|
monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False)
|
|
ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x")
|
|
assert ok is True
|
|
assert "disabled" in reason
|