195 lines
8.7 KiB
Python
195 lines
8.7 KiB
Python
"""Regression coverage for explicit legacy PR root migration."""
|
|
from __future__ import annotations
|
|
|
|
from contextlib import nullcontext
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from testing.tests.test_hermes_cli_support import HERMES, _load
|
|
|
|
|
|
ROOT = Path(__file__).parents[2]
|
|
sys.path.insert(0, str(HERMES / "scm-common/scripts"))
|
|
state = _load("supervisor_state")
|
|
seed = _load("seed_legacy_scm_roots")
|
|
retry = sys.modules["publication_retry"]
|
|
bootstrap = _load("bootstrap_soteria_publication_retry")
|
|
|
|
|
|
class NativeKanban:
|
|
"""Minimal native task lookup surface used by the operator seed."""
|
|
|
|
def __init__(self, task_ids: set[str]) -> None:
|
|
self.task_ids = task_ids
|
|
|
|
def scoped_current_board(self, _board: str):
|
|
return nullcontext()
|
|
|
|
def connect(self, *, board: str):
|
|
return type("Connection", (), {"close": lambda self: None})()
|
|
|
|
def get_task(self, _connection, task_id: str):
|
|
return {"id": task_id} if task_id in self.task_ids else None
|
|
|
|
|
|
def _pull(root, head: str | None = None) -> bytes:
|
|
"""Build only the canonical open PR shape the seed accepts."""
|
|
return json.dumps({
|
|
"state": "open",
|
|
"head": {"ref": root.ref, "sha": head or root.head,
|
|
"repo": {"full_name": f"titan/{root.project}"}},
|
|
"base": {"ref": root.base, "repo": {"full_name": f"titan/{root.project}"}},
|
|
}).encode()
|
|
|
|
|
|
def test_seed_registry_exactly_matches_flux_broker_adoptions():
|
|
"""The board integrity table and broker ledger share one reviewed scope."""
|
|
document = yaml.safe_load(
|
|
(ROOT / "services/hermes-scm-broker/task-branch-adoptions-configmap.yaml").read_text()
|
|
)
|
|
deployed = json.loads(document["data"]["task-branch-adoptions.json"])
|
|
expected = {f"{root.project}/{root.ref}": root.adoption() for root in seed.ROOTS}
|
|
assert deployed == expected
|
|
assert "t_cf89a2ec" in {root.root_task_id for root in seed.ROOTS}
|
|
assert deployed["atlas-iac/feature/hermes-next-hux"]["latest_head"] == (
|
|
"98c7c6184f6edfe3cdac228529c2287584db3006"
|
|
)
|
|
|
|
|
|
def test_seed_requires_native_root_and_matching_live_canonical_pr(tmp_path, monkeypatch):
|
|
"""A stale PR or fabricated root leaves the board-local integrity DB untouched."""
|
|
root = seed.ROOTS[1]
|
|
monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards")
|
|
missing = NativeKanban(set())
|
|
assert seed.seed_root(missing, root, lambda _path: (_ for _ in ()).throw(AssertionError)) == "missing-task"
|
|
|
|
native = NativeKanban({root.root_task_id})
|
|
assert seed.seed_root(native, root, lambda _path: _pull(root, "a" * 40)) == "live-pr-mismatch"
|
|
assert state.get_root(root.board, root.root_task_id) is None
|
|
|
|
assert seed.seed_root(native, root, lambda _path: _pull(root)) == "seeded"
|
|
assert state.get_root(root.board, root.root_task_id) == root.lineage
|
|
assert state.get_live_head(root.board, root.root_task_id) == root.head
|
|
|
|
|
|
def test_seed_rerun_preserves_same_owned_newer_state_and_approval(tmp_path, monkeypatch):
|
|
"""A static migration record never rewinds a later verified continuation head."""
|
|
root = seed.ROOTS[1]
|
|
monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards")
|
|
native = NativeKanban({root.root_task_id})
|
|
assert seed.seed_root(native, root, lambda _path: _pull(root)) == "seeded"
|
|
newer = "b" * 40
|
|
state.record_submission(root.board, root.root_task_id, root.lineage, newer)
|
|
state.set_ready(root.board, root.root_task_id, newer)
|
|
|
|
assert seed.seed_root(native, root, lambda _path: (_ for _ in ()).throw(AssertionError)) == "already-seeded"
|
|
assert state.get_live_head(root.board, root.root_task_id) == newer
|
|
with state._connect(root.board) as connection:
|
|
assert connection.execute(
|
|
"SELECT ready_for_human_merge,ready_commit FROM supervisor_roots"
|
|
).fetchone() == (1, newer)
|
|
|
|
|
|
def test_publication_bootstrap_accepts_only_the_verified_historical_blocked_run(monkeypatch):
|
|
"""The run-8 migration accepts its old blocked terminal state, not a live run."""
|
|
lineage = state.Lineage(
|
|
bootstrap.ROOT, "hermes-repair/cache", "https://scm.bstein.dev/titan/soteria/pulls/3",
|
|
"soteria", "main",
|
|
)
|
|
monkeypatch.setattr(
|
|
bootstrap.supervisor_state, "get_child",
|
|
lambda _board, _child: {"root_task_id": bootstrap.ROOT, "kind": "repair", "lineage": lineage},
|
|
)
|
|
|
|
class Connection:
|
|
def execute(self, _sql, _args):
|
|
return SimpleNamespace(fetchone=lambda: (8, "blocked", "blocked"))
|
|
|
|
def close(self):
|
|
return None
|
|
|
|
kanban = SimpleNamespace(
|
|
scoped_current_board=lambda _board: nullcontext(),
|
|
connect=lambda **_kwargs: Connection(),
|
|
get_task=lambda _connection, _task: {"status": "blocked", "current_run_id": None},
|
|
parent_ids=lambda _connection, _task: [bootstrap.ROOT],
|
|
)
|
|
monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=kanban))
|
|
assert bootstrap._native_guard() == lineage
|
|
|
|
kanban.connect = lambda **_kwargs: type("BadConnection", (), {
|
|
"execute": lambda _self, _sql, _args: SimpleNamespace(fetchone=lambda: (9, "blocked", "blocked")),
|
|
"close": lambda _self: None,
|
|
})()
|
|
with pytest.raises(ValueError, match="exact blocked run"):
|
|
bootstrap._native_guard()
|
|
|
|
kanban.connect = lambda **_kwargs: type("BadOutcomeConnection", (), {
|
|
"execute": lambda _self, _sql, _args: SimpleNamespace(fetchone=lambda: (8, "blocked", "failed")),
|
|
"close": lambda _self: None,
|
|
})()
|
|
with pytest.raises(ValueError, match="exact blocked run"):
|
|
bootstrap._native_guard()
|
|
|
|
|
|
def test_publication_retry_is_bound_once_and_never_falls_back_to_a_model(tmp_path, monkeypatch):
|
|
"""A mediator receipt can power one fresh ordinal-pinned publication only."""
|
|
board, root_id, child_id, baseline, head = (
|
|
"soteria", "t_root", "t_child", "a" * 40, "b" * 40
|
|
)
|
|
lineage = state.Lineage(
|
|
root_id, "hermes-repair/cache", "https://scm.bstein.dev/titan/soteria/pulls/3",
|
|
"soteria", "main",
|
|
)
|
|
monkeypatch.setattr(state, "KANBAN_ROOT", tmp_path / "boards")
|
|
state.record_submission(board, root_id, lineage, baseline)
|
|
state.record_child(board, child_id, root_id, root_id, "repair", baseline, "replace cache literals")
|
|
structured = {
|
|
"status": "completed", "summary": "Replace cache literals.",
|
|
"changed_files": ["internal/k8s/job_manifests.go"], "tests_run": ["go test ./..."],
|
|
"artifacts": [], "findings": [], "blockers": [],
|
|
}
|
|
title, body = "Replace cache literals", "verified completion evidence"
|
|
receipt = {
|
|
"source": {
|
|
"board": board, "task_id": child_id, "run_id": "8", "worker_ordinal": 0,
|
|
"attempt": 1, "root_task_id": root_id,
|
|
"repo_url": "https://scm.bstein.dev/titan/soteria.git",
|
|
"branch": lineage.branch, "base_branch": "main",
|
|
},
|
|
"baseline_sha": baseline, "head": head, "title": title, "body": body,
|
|
"structured": structured, "result_digest": retry.receipt_digest(structured, title, body),
|
|
}
|
|
binding = {name: receipt["source"][name] for name in (
|
|
"board", "task_id", "run_id", "worker_ordinal", "attempt"
|
|
)}
|
|
state.record_publication_retry(board, child_id, binding, receipt)
|
|
assert state.publication_retry(board, child_id, "")["head"] == head
|
|
state.issue_publication_retry(board, child_id, "9")
|
|
assert state.publication_retry(board, child_id, "9")["source"]["worker_ordinal"] == 0
|
|
with pytest.raises(retry.PublicationRetryError, match="already issued"):
|
|
state.publication_retry(board, child_id, "10")
|
|
monkeypatch.setattr(state.time, "time", lambda: 1_000)
|
|
assert state.reissue_publication_retry(board, child_id, "9") is True
|
|
# A failed native park can replay the same terminal result without spending
|
|
# another retry or extending the backoff.
|
|
assert state.reissue_publication_retry(board, child_id, "9") is True
|
|
state.issue_publication_retry(board, child_id, "9")
|
|
with pytest.raises(retry.PublicationRetryError, match="backoff"):
|
|
state.publication_retry(board, child_id, "")
|
|
monkeypatch.setattr(state.time, "time", lambda: 1_301)
|
|
assert state.publication_retry(board, child_id, "")["head"] == head
|
|
state.issue_publication_retry(board, child_id, "10")
|
|
# Completion advances the trusted root head before the terminal receipt is
|
|
# marked resolved; a finalize replay must not revalidate its old baseline.
|
|
state.record_submission(board, child_id, lineage, head)
|
|
state.issue_publication_retry(board, child_id, "10")
|
|
state.resolve_publication_retry(board, child_id, "10")
|
|
state.resolve_publication_retry(board, child_id, "10")
|
|
assert state.publication_retry(board, child_id, "") is None
|