"""Native-shaped regression tests for coordinator-owned PR continuations.""" from __future__ import annotations from contextlib import nullcontext import json from pathlib import Path import sqlite3 import sys import pytest from testing.tests.test_hermes_cli_support import HERMES, _load sys.path.insert(0, str(HERMES / "scm-common/scripts")) state = _load("supervisor_state") continuation = _load("kanban_continue_pr") class NativeKanban: """Match live ``kanban_db``'s keyword-only create API without metadata.""" def __init__(self) -> None: self.created: list[dict] = [] self.by_key: dict[str, str] = {} self.comments: list[tuple] = [] def scoped_current_board(self, _board: str): return nullcontext() def connect(self, *, board: str): class Connection: def close(self): return None return Connection() def get_task(self, _conn, task_id: str): return {"id": task_id} if task_id == "root" else None def create_task(self, _conn, *, title, body=None, assignee=None, created_by=None, workspace_kind="scratch", workspace_path=None, branch_name=None, tenant=None, priority=0, parents=(), triage=False, idempotency_key=None, max_runtime_seconds=None, skills=None, max_retries=None, goal_mode=False, goal_max_turns=None, initial_status="running", session_id=None, board=None, project_id=None): assert branch_name is None assert workspace_kind == "scratch" assert idempotency_key if idempotency_key in self.by_key: return self.by_key[idempotency_key] task_id = f"child-{len(self.created) + 1}" self.by_key[idempotency_key] = task_id self.created.append({ "id": task_id, "title": title, "body": body, "assignee": assignee, "created_by": created_by, "parents": tuple(parents), "idempotency_key": idempotency_key, "initial_status": initial_status, }) return task_id def add_comment(self, _conn, *args): self.comments.append(args) @pytest.fixture def trusted_root(tmp_path: Path, monkeypatch): """Make a real SQLite board DB holding a signed root lineage record.""" board = "titan-iac" monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards") path = state.KANBAN_ROOT / board / "kanban.db" lineage = state.Lineage( root_task_id="root", project="atlas-iac", branch="hermes/root", pull_request="https://scm.bstein.dev/titan/atlas-iac/pulls/7", base_branch="main", ) state.record_submission(board, "root", lineage, "a" * 40, path=path) state.set_ready(board, "root", "a" * 40, path=path) assert state.get_root(board, "root") == lineage return board, lineage, state.KANBAN_ROOT / board / state.STATE_FILE def _pr(head: str) -> bytes: return json.dumps({ "state": "open", "head": {"ref": "hermes/root", "sha": head, "repo": {"full_name": "titan/atlas-iac"}}, "base": {"ref": "main", "repo": {"full_name": "titan/atlas-iac"}}, }).encode() def test_queue_uses_real_sqlite_lineage_and_native_create_signature(trusted_root, monkeypatch): board, _lineage, path = trusted_root native = NativeKanban() monkeypatch.setattr(continuation.scm_broker_client, "read", lambda _path: _pr("b" * 40)) child, created = continuation.queue(native, board=board, root_task="root", objective="fix review") assert (child, created) == ("child-1", True) assert native.created[0]["parents"] == ("root",) assert native.created[0]["initial_status"] == "running" assert "metadata" not in native.created[0] assert state.get_live_head(board, "root", path=path) == "b" * 40 assert state.get_child(board, child, path=path)["parent_task_id"] == "root" with state._connect(board, path) as connection: ready = connection.execute( "SELECT ready_for_human_merge,ready_commit FROM supervisor_roots" ).fetchone() assert tuple(ready) == (0, "") same, created = continuation.queue(native, board=board, root_task="root", objective="fix review") assert (same, created) == (child, False) assert len(native.created) == 1 def test_interrupted_recording_recovers_through_native_idempotency(trusted_root, monkeypatch): board, _lineage, path = trusted_root native = NativeKanban() monkeypatch.setattr(continuation.scm_broker_client, "read", lambda _path: _pr("a" * 40)) original = continuation.supervisor_state.record_child monkeypatch.setattr( continuation.supervisor_state, "record_child", lambda *_args, **_kwargs: (_ for _ in ()).throw(OSError("simulated restart")), ) with pytest.raises(OSError, match="simulated restart"): continuation.queue(native, board=board, root_task="root", objective="repair flaky test") assert len(native.created) == 1 monkeypatch.setattr(continuation.supervisor_state, "record_child", original) child, created = continuation.queue(native, board=board, root_task="root", objective="repair flaky test") assert (child, created) == ("child-1", True) assert len(native.created) == 1 assert state.get_child(board, child, path=path) is not None def test_submission_cannot_rewrite_root_and_new_head_clears_ready(trusted_root): board, lineage, path = trusted_root replacement = state.Lineage( root_task_id="root", project="atlas-iac", branch="attacker/ref", pull_request=lineage.pull_request, base_branch="main", ) with pytest.raises(ValueError, match="immutable"): state.record_submission(board, "root", replacement, "c" * 40, path=path) state.record_submission(board, "repair-child", lineage, "c" * 40, path=path) with state._connect(board, path) as connection: row = connection.execute( "SELECT live_pr_head,ready_for_human_merge,ready_commit FROM supervisor_roots" ).fetchone() assert tuple(row) == ("c" * 40, 0, "") def test_present_malformed_continuation_state_fails_closed(trusted_root): board, _lineage, path = trusted_root state.record_child(board, "child", "root", "root", "repair", "a" * 40, "fix", path=path) with state._connect(board, path) as connection: connection.execute( "UPDATE supervisor_children SET cycle=? WHERE board=? AND child_task_id=?", ("not-a-cycle", board, "child"), ) with pytest.raises(state.SupervisorStateError, match="malformed"): state.get_child(board, "child", path=path) def test_default_state_sidecar_imports_healthy_legacy_rows_once(tmp_path, monkeypatch): """The first isolated state DB preserves native rows without sharing its file.""" board = "titan-iac" monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards") legacy = state.KANBAN_ROOT / board / "kanban.db" lineage = state.Lineage("root", "feature/root", "https://scm.bstein.dev/titan/atlas-iac/pulls/9", "atlas-iac", "main") state.record_submission(board, "root", lineage, "a" * 40, path=legacy) state.set_ready(board, "root", "a" * 40, path=legacy) state.record_child(board, "repair", "root", "root", "repair", "a" * 40, "repair", path=legacy) assert state.get_root(board, "root") == lineage assert state.get_child(board, "repair")["parent_task_id"] == "root" sidecar = state.KANBAN_ROOT / board / state.STATE_FILE assert sidecar.is_file() and sidecar != legacy state.record_submission(board, "root", lineage, "b" * 40) assert state.get_live_head(board, "root") == "b" * 40 assert state.get_live_head(board, "root", path=legacy) == "a" * 40 def test_default_state_refuses_corrupt_legacy_without_empty_sidecar(tmp_path, monkeypatch): """A damaged native DB cannot silently erase supervisor authority.""" board = "soteria" monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards") legacy = state.KANBAN_ROOT / board / "kanban.db" legacy.parent.mkdir(parents=True) legacy.write_bytes(b"not a sqlite database") with pytest.raises(state.SupervisorStateError, match="unreadable"): state.get_root(board, "root") assert not (state.KANBAN_ROOT / board / state.STATE_FILE).exists() def test_default_state_imports_additive_legacy_schema_and_rejects_orphans(tmp_path, monkeypatch): """Older valid rows keep their safe defaults; orphan children stop migration.""" board = "atlas-iac" monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards") legacy = state.KANBAN_ROOT / board / "kanban.db" legacy.parent.mkdir(parents=True) schema = """ CREATE TABLE supervisor_roots ( board TEXT, root_task_id TEXT, project TEXT, branch TEXT, pull_request TEXT, base_branch TEXT, live_pr_head TEXT ); CREATE TABLE supervisor_children ( board TEXT, child_task_id TEXT, root_task_id TEXT, parent_task_id TEXT, kind TEXT, head_commit TEXT, objective_digest TEXT ); """ with sqlite3.connect(legacy) as connection: connection.executescript(schema) connection.execute( "INSERT INTO supervisor_roots VALUES(?,?,?,?,?,?,?)", (board, "root", "atlas-iac", "feature/root", "pull/1", "main", "a" * 40), ) connection.execute( "INSERT INTO supervisor_children VALUES(?,?,?,?,?,?,?)", (board, "child", "root", "root", "repair", "a" * 40, "digest"), ) assert state.get_child(board, "child")["cycle"] == 1 with state._connect(board) as connection: assert connection.execute("SELECT ready_for_human_merge,ready_commit FROM supervisor_roots").fetchone() == (0, "") orphan_board = "soteria" orphan = state.KANBAN_ROOT / orphan_board / "kanban.db" orphan.parent.mkdir() with sqlite3.connect(orphan) as connection: connection.executescript(schema) connection.execute( "INSERT INTO supervisor_children VALUES(?,?,?,?,?,?,?)", (orphan_board, "child", "missing", "missing", "repair", "a" * 40, "digest"), ) with pytest.raises(state.SupervisorStateError, match="malformed"): state.get_root(orphan_board, "missing") assert not (state.KANBAN_ROOT / orphan_board / state.STATE_FILE).exists()