566 lines
22 KiB
Python
566 lines
22 KiB
Python
"""Continuing-task submission contracts against the signed Atlas SCM broker."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).parents[2]
|
|
SCRIPTS = ROOT / "services/hermes/scripts"
|
|
SCM_SCRIPTS = ROOT / "services/hermes/scm-common/scripts"
|
|
sys.path[:0] = [str(SCRIPTS), str(SCM_SCRIPTS)]
|
|
|
|
import execution_pool_client as client # noqa: E402
|
|
import execution_pool_protocol as protocol # noqa: E402
|
|
import execution_pool_scm as scm # noqa: E402
|
|
import scm_resume_bootstrap as resume_bootstrap # noqa: E402
|
|
import receive_pack_scan # noqa: E402
|
|
import scm_broker # noqa: E402
|
|
from testing.tests.test_hermes_scm_broker_support import _receive_command # noqa: E402
|
|
from testing.tests.test_hermes_execution_pool_mediator import ( # noqa: E402
|
|
KEY,
|
|
RESULT,
|
|
assignment,
|
|
binding,
|
|
payload,
|
|
)
|
|
|
|
|
|
def attempt_assignment(attempt, continuation_kind=""):
|
|
"""One signed assignment envelope bound to an exact retry attempt."""
|
|
return protocol.sign_envelope(
|
|
KEY, "assignment", binding(attempt=attempt), payload(continuation_kind=continuation_kind)
|
|
)
|
|
|
|
|
|
def test_submission_refs_keep_one_task_branch_across_attempts():
|
|
first = scm.submission_refs("feature/pool", 1, "a" * 40)
|
|
assert first[0] == "feature/pool"
|
|
retry = scm.submission_refs("feature/pool", 3, "b" * 40)
|
|
assert retry == first
|
|
# Every candidate stays inside a reviewed namespace the broker accepts, and
|
|
# none of them is a protected or base ref.
|
|
for candidate in first + retry:
|
|
assert receive_pack_scan.FEATURE_REF_RE.fullmatch(f"refs/heads/{candidate}")
|
|
assert candidate not in {"main", "master"}
|
|
|
|
|
|
def test_submission_refs_drop_oversized_names_and_fail_closed_when_empty():
|
|
long_branch = "feature/" + "a" * 185
|
|
candidates = scm.submission_refs(long_branch, 2, "c" * 40)
|
|
assert candidates == (long_branch,)
|
|
with pytest.raises(protocol.ProtocolError, match="reviewed branch name"):
|
|
scm.submission_refs("main", 1, "d" * 40)
|
|
|
|
|
|
def test_the_broker_accepts_the_attempt_ref_as_a_creation_and_still_refuses_updates():
|
|
zero, head = b"0" * 40, b"1" * 40
|
|
ref = b"refs/heads/feature/pool-attempt-2"
|
|
scm_broker._validate_receive_pack(_receive_command(zero, head, ref), "token")
|
|
update = _receive_command(b"2" * 40, head, ref)
|
|
with pytest.raises(scm_broker.PolicyError, match="feature-branch creation"):
|
|
scm_broker._validate_receive_pack(update, "token")
|
|
|
|
|
|
def submit_harness(tmp_path, monkeypatch, remote_refs, *, attempt=1, continuation_kind=""):
|
|
"""A Boundary whose broker advertises exactly ``remote_refs``."""
|
|
monkeypatch.setattr(scm, "WORKSPACE_ROOT", tmp_path / "workspace")
|
|
monkeypatch.setattr(scm, "SCM_ROOT", tmp_path / "state")
|
|
monkeypatch.setattr(scm, "ORDINAL", 0)
|
|
exact = attempt_assignment(attempt, continuation_kind)
|
|
destination = scm.workspace_path(exact)
|
|
destination.mkdir(parents=True)
|
|
protocol.atomic_json(scm._state_path(exact), {"baseline_sha": "a" * 40})
|
|
head = "b" * 40
|
|
monkeypatch.setattr(scm, "_workspace_identity", lambda *_a: head)
|
|
calls: list[tuple[str, ...]] = []
|
|
|
|
def run(*arguments, **_kwargs):
|
|
calls.append(arguments)
|
|
if arguments[0] == "ls-remote":
|
|
return "\n".join(
|
|
f"{sha}\trefs/heads/{ref}" for ref, sha in remote_refs.items()
|
|
)
|
|
if arguments[0] == "rev-list":
|
|
return "2"
|
|
return ""
|
|
|
|
monkeypatch.setattr(scm, "_run", run)
|
|
boundary = scm.Boundary(KEY)
|
|
monkeypatch.setattr(boundary, "_grant", lambda *_args: "signed-grant")
|
|
monkeypatch.setattr(scm.scm_broker_client, "register_task", lambda _grant: b'{"registered":true}')
|
|
monkeypatch.setattr(boundary, "_draft", lambda *args, **_kwargs: f"https://scm/pulls/{args[1]}")
|
|
return boundary, exact, calls, head
|
|
|
|
|
|
def test_a_retry_updates_the_same_task_ref(
|
|
tmp_path, monkeypatch
|
|
):
|
|
boundary, exact, calls, _head = submit_harness(
|
|
tmp_path, monkeypatch, {"wt/t_deadbeef": "c" * 40}, attempt=2
|
|
)
|
|
result = boundary.submit(exact, {"title": "retry", "body": "evidence"})
|
|
pushes = [item for item in calls if item[-4:] == ("push", "--no-thin", "hermes-broker", "HEAD:refs/heads/wt/t_deadbeef")]
|
|
assert len(pushes) == 1
|
|
assert result["branch"] == "wt/t_deadbeef"
|
|
assert result["pull_request"] == "https://scm/pulls/wt/t_deadbeef"
|
|
|
|
|
|
def test_an_already_published_head_is_adopted_without_a_second_push(
|
|
tmp_path, monkeypatch
|
|
):
|
|
boundary, exact, calls, head = submit_harness(
|
|
tmp_path, monkeypatch, {"wt/t_deadbeef": "b" * 40}, attempt=2
|
|
)
|
|
assert head == "b" * 40
|
|
result = boundary.submit(exact, {"title": "replay", "body": "evidence"})
|
|
assert [item for item in calls if "push" in item] == []
|
|
assert result["branch"] == "wt/t_deadbeef"
|
|
assert result["pull_request"].endswith("wt/t_deadbeef")
|
|
|
|
|
|
def test_no_change_review_returns_existing_pr_without_rewriting_its_handoff(tmp_path, monkeypatch):
|
|
"""A review at the already-published head must not mutate implementation prose."""
|
|
boundary, exact, calls, head = submit_harness(
|
|
tmp_path, monkeypatch, {"wt/t_deadbeef": "b" * 40}, continuation_kind="review"
|
|
)
|
|
existing = "https://scm.bstein.dev/titan/metis/pulls/42"
|
|
monkeypatch.setattr(
|
|
scm.scm_broker_client, "read",
|
|
lambda _path: __import__("json").dumps([{
|
|
"number": 42, "html_url": existing,
|
|
"head": {"ref": "wt/t_deadbeef"}, "base": {"ref": "main"},
|
|
}]).encode(),
|
|
)
|
|
monkeypatch.setattr(
|
|
scm.scm_broker_client, "update_draft",
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("review rewrote PR metadata")),
|
|
)
|
|
monkeypatch.setattr(
|
|
scm.scm_broker_client, "create_draft",
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("review created a PR")),
|
|
)
|
|
# The helper installs a simple draft stub for other cases; restore the real
|
|
# bounded discovery path for this no-change review assertion.
|
|
monkeypatch.setattr(boundary, "_draft", scm.Boundary._draft)
|
|
|
|
result = boundary.submit(exact, {"title": "Review", "body": "No changes"})
|
|
|
|
assert result["head"] == head and result["pull_request"] == existing
|
|
assert [item for item in calls if "push" in item] == []
|
|
|
|
|
|
def test_a_run_with_no_new_commits_still_reports_work_a_prior_attempt_published(
|
|
tmp_path, monkeypatch
|
|
):
|
|
boundary, exact, calls, _head = submit_harness(
|
|
tmp_path, monkeypatch, {"wt/t_deadbeef": "b" * 40}
|
|
)
|
|
|
|
real_run = scm._run
|
|
|
|
def run(*arguments, **kwargs):
|
|
if arguments[0] == "rev-list":
|
|
return "0"
|
|
return real_run(*arguments, **kwargs)
|
|
|
|
monkeypatch.setattr(scm, "_run", run)
|
|
result = boundary.submit(exact, {"title": "nothing new", "body": "evidence"})
|
|
assert [item for item in calls if "push" in item] == []
|
|
assert result["pull_request"].endswith("wt/t_deadbeef")
|
|
|
|
|
|
def test_a_run_that_produced_nothing_at_all_submits_nothing(tmp_path, monkeypatch):
|
|
boundary, exact, calls, _head = submit_harness(tmp_path, monkeypatch, {})
|
|
|
|
real_run = scm._run
|
|
|
|
def run(*arguments, **kwargs):
|
|
if arguments[0] == "rev-list":
|
|
return "0"
|
|
return real_run(*arguments, **kwargs)
|
|
|
|
monkeypatch.setattr(scm, "_run", run)
|
|
result = boundary.submit(exact, {})
|
|
assert [item for item in calls if "push" in item] == []
|
|
assert result["pull_request"] == "" and result["branch"] == "wt/t_deadbeef"
|
|
|
|
|
|
def test_submission_updates_an_existing_task_ref_without_creating_a_retry_ref(
|
|
tmp_path, monkeypatch
|
|
):
|
|
taken = {
|
|
"wt/t_deadbeef": "c" * 40,
|
|
"wt/t_deadbeef-attempt-1": "d" * 40,
|
|
f"wt/t_deadbeef-{'b' * 12}": "e" * 40,
|
|
}
|
|
boundary, exact, calls, _head = submit_harness(tmp_path, monkeypatch, taken)
|
|
result = boundary.submit(exact, {"title": "continued", "body": "evidence"})
|
|
pushes = [item for item in calls if item[-4:] == ("push", "--no-thin", "hermes-broker", "HEAD:refs/heads/wt/t_deadbeef")]
|
|
assert len(pushes) == 1
|
|
assert result["branch"] == "wt/t_deadbeef"
|
|
|
|
|
|
def test_remote_head_parsing_ignores_anything_that_is_not_a_branch(
|
|
tmp_path, monkeypatch
|
|
):
|
|
monkeypatch.setattr(
|
|
scm, "_run",
|
|
lambda *_a, **_k: "aaa\trefs/tags/v1\nbbb\trefs/heads/feature/x\ngarbage\n",
|
|
)
|
|
assert scm._remote_heads(tmp_path, ("feature/x",)) == {"feature/x": "bbb"}
|
|
|
|
|
|
def test_git_timeout_becomes_a_sanitized_retryable_error(monkeypatch):
|
|
"""A subprocess timeout must not skip terminal result submission or expose a grant."""
|
|
def timed_out(*_args, **_kwargs):
|
|
raise subprocess.TimeoutExpired(
|
|
["git", "-c", "http.extraHeader=X-Hermes-Task-Grant: secret", "push"], 900
|
|
)
|
|
|
|
monkeypatch.setattr(scm.subprocess, "run", timed_out)
|
|
with pytest.raises(RuntimeError, match="^SCM command timed out$"):
|
|
scm._run("push", "hermes-broker")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detail", "expected"),
|
|
[
|
|
(
|
|
"remote: task branch head changed; fetch and merge before retrying",
|
|
"task branch changed; fetch and merge before retrying",
|
|
),
|
|
(
|
|
"RPC failed; HTTP 403",
|
|
"SCM broker authorization rejected the branch update; "
|
|
"inspect task ownership before retrying",
|
|
),
|
|
(
|
|
"fatal: unable to access: Connection timed out",
|
|
"SCM broker transport failed; retry the preserved local commit",
|
|
),
|
|
(
|
|
"RPC failed; HTTP 400",
|
|
"SCM broker rejected the branch update; "
|
|
"inspect broker or upstream state before retrying",
|
|
),
|
|
],
|
|
)
|
|
def test_push_failure_guidance_is_sanitized_and_specific(detail, expected):
|
|
assert scm._push_failure(RuntimeError(detail)) == expected
|
|
|
|
|
|
def test_draft_discovery_finds_a_same_branch_pr_after_the_first_page(monkeypatch):
|
|
first_page = [{"head": {"ref": f"other-{index}"}, "base": {"ref": "main"}} for index in range(50)]
|
|
found = "https://scm.bstein.dev/titan/metis/pulls/51"
|
|
calls = []
|
|
|
|
def read(path):
|
|
calls.append(path)
|
|
return __import__("json").dumps(
|
|
first_page if "page=1" in path else [{
|
|
"number": 51, "html_url": found,
|
|
"head": {"ref": "wt/t_deadbeef"}, "base": {"ref": "main"},
|
|
}]
|
|
).encode()
|
|
|
|
monkeypatch.setattr(scm.scm_broker_client, "read", read)
|
|
monkeypatch.setattr(
|
|
scm.scm_broker_client, "update_draft",
|
|
lambda grant, number, title, body: __import__("json").dumps({"html_url": found}).encode(),
|
|
)
|
|
assert scm.Boundary._draft("metis", "wt/t_deadbeef", "main", "b" * 40, "safe", "body", "grant") == found
|
|
assert len(calls) == 2
|
|
|
|
|
|
def test_repair_draft_refuses_to_create_a_replacement_when_its_pr_is_not_open(monkeypatch):
|
|
monkeypatch.setattr(scm.scm_broker_client, "read", lambda _path: b"[]")
|
|
monkeypatch.setattr(
|
|
scm.scm_broker_client, "create_draft",
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("replacement PR")),
|
|
)
|
|
with pytest.raises(protocol.ProtocolError, match="no existing pull request"):
|
|
scm.Boundary._draft(
|
|
"metis", "wt/t_deadbeef", "main", "b" * 40, "repair", "evidence",
|
|
"grant", existing_only=True,
|
|
)
|
|
|
|
|
|
def test_repair_checks_its_open_pr_before_advancing_the_owned_branch(tmp_path, monkeypatch):
|
|
boundary, exact, _calls, _head = submit_harness(
|
|
tmp_path, monkeypatch, {"wt/t_deadbeef": "a" * 40}, continuation_kind="repair"
|
|
)
|
|
events = []
|
|
original_run = scm._run
|
|
|
|
def run(*arguments, **kwargs):
|
|
if "push" in arguments:
|
|
events.append("push")
|
|
return original_run(*arguments, **kwargs)
|
|
|
|
def draft(*_args, **kwargs):
|
|
events.append("pre" if kwargs.get("refresh") is False else "post")
|
|
return "https://scm/pulls/3"
|
|
|
|
monkeypatch.setattr(scm, "_run", run)
|
|
monkeypatch.setattr(boundary, "_draft", draft)
|
|
boundary.submit(exact, {"title": "repair", "body": "evidence"})
|
|
assert events == ["pre", "push", "post"]
|
|
|
|
|
|
class FailingSCM:
|
|
def __init__(self, error):
|
|
self.error = error
|
|
self.calls = 0
|
|
|
|
def submit(self, _assignment, _request):
|
|
self.calls += 1
|
|
raise self.error
|
|
|
|
def resume_artifact(self, _assignment, _request, _structured):
|
|
return {"bounded": "resume-artifact"}
|
|
|
|
|
|
def test_a_refused_submission_downgrades_the_result_instead_of_discarding_it():
|
|
failing = FailingSCM(protocol.ProtocolError("every reviewed branch name is taken"))
|
|
boundary = client.ClientBoundary(KEY, failing)
|
|
boundary.current = assignment()
|
|
boundary._post = lambda _path, _envelope: protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
finished = boundary.finish(
|
|
{
|
|
"binding": binding(),
|
|
"payload": {"structured": dict(RESULT), "returncode": 0},
|
|
"title": "safe", "body": "evidence",
|
|
}
|
|
)
|
|
assert failing.calls == 1
|
|
structured = finished["structured"]
|
|
# The run is recorded rather than unwound, and it says exactly why.
|
|
assert structured["status"] == "blocked"
|
|
assert structured["changed_files"] == ["safe.py"]
|
|
assert len(structured["blockers"]) == 1
|
|
assert "commits remain on this ordinal's workspace" in structured["blockers"][0]
|
|
assert "every reviewed branch name is taken" in structured["blockers"][0]
|
|
assert finished["ack"]["accepted"] is True
|
|
|
|
|
|
def test_a_refused_submission_is_transient_for_pool_recovery():
|
|
failing = FailingSCM(protocol.ProtocolError("SCM broker rejected the branch update"))
|
|
boundary = client.ClientBoundary(KEY, failing)
|
|
boundary.current = assignment(continuation_kind="repair", root_task_id="t_deadbeef")
|
|
posted = []
|
|
|
|
def post(_path, envelope):
|
|
posted.append(envelope)
|
|
return protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
|
|
boundary._post = post
|
|
finished = boundary.finish(
|
|
{
|
|
"binding": binding(),
|
|
"payload": {"structured": dict(RESULT), "returncode": 0, "capacity_failure": False},
|
|
"title": "safe", "body": "evidence",
|
|
}
|
|
)
|
|
assert finished["ack"]["accepted"] is True
|
|
assert finished["structured"]["status"] == "blocked"
|
|
terminal = protocol.verify_envelope(KEY, posted[0])["payload"]
|
|
assert terminal["capacity_failure"] is True
|
|
assert terminal["scm_resume"] == {"bounded": "resume-artifact"}
|
|
|
|
|
|
def test_ordinary_submission_failure_never_emits_a_continuation_receipt():
|
|
boundary = client.ClientBoundary(KEY, FailingSCM(protocol.ProtocolError("refused")))
|
|
boundary.current = assignment()
|
|
posted = []
|
|
boundary._post = lambda _path, envelope: posted.append(envelope) or protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
boundary.finish({"binding": binding(), "payload": {"structured": dict(RESULT), "returncode": 0}, "title": "safe", "body": "evidence"})
|
|
terminal = protocol.verify_envelope(KEY, posted[0])["payload"]
|
|
assert terminal["capacity_failure"] is True and "scm_resume" not in terminal
|
|
|
|
|
|
def test_real_resume_receipt_keeps_completed_evidence_after_submission_mutates_result(tmp_path, monkeypatch):
|
|
"""The signed receipt must not alias the blocked terminal result in memory."""
|
|
monkeypatch.setattr(scm, "WORKSPACE_ROOT", tmp_path / "workspace")
|
|
monkeypatch.setattr(scm, "SCM_ROOT", tmp_path / "state")
|
|
monkeypatch.setattr(scm, "ORDINAL", 0)
|
|
exact = protocol.sign_envelope(
|
|
KEY, "assignment", binding(), payload(root_task_id="t_deadbeef", continuation_kind="repair")
|
|
)
|
|
destination = scm.workspace_path(exact)
|
|
destination.mkdir(parents=True)
|
|
protocol.atomic_json(scm._state_path(exact), {"baseline_sha": "a" * 40})
|
|
monkeypatch.setattr(scm, "_workspace_identity", lambda *_args: "b" * 40)
|
|
monkeypatch.setattr(scm, "_run", lambda *args, **_kwargs: "")
|
|
broker = scm.Boundary(KEY)
|
|
monkeypatch.setattr(
|
|
broker, "submit", lambda *_args, **_kwargs: (_ for _ in ()).throw(protocol.ProtocolError("refused"))
|
|
)
|
|
boundary = client.ClientBoundary(KEY, broker)
|
|
boundary.current = exact
|
|
posted = []
|
|
boundary._post = lambda _path, envelope: posted.append(envelope) or protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
boundary.finish({"binding": binding(), "payload": {"structured": dict(RESULT), "returncode": 0}, "title": "safe", "body": "evidence"})
|
|
result_envelope = next(item for item in posted if item["kind"] == "result")
|
|
artifact = protocol.verify_envelope(KEY, result_envelope)["payload"]["scm_resume"]
|
|
assert artifact["structured"]["status"] == "completed"
|
|
assert artifact["result_digest"] == __import__("hashlib").sha256(
|
|
protocol.canonical_json({name: artifact[name] for name in ("structured", "title", "body")})
|
|
).hexdigest()
|
|
|
|
|
|
def test_mediator_bootstrap_recovers_one_unique_completed_log_result(tmp_path, monkeypatch):
|
|
exact = protocol.sign_envelope(
|
|
KEY, "assignment", binding(), payload(root_task_id="t_deadbeef", continuation_kind="repair")
|
|
)
|
|
log = tmp_path / "session-state" / "metis" / "t_deadbeef" / "42" / ".log"
|
|
# The live worker uses `<run>.log`; make the same task-bound parent safely.
|
|
log = log.parent.parent / "42.log"
|
|
log.parent.mkdir(parents=True)
|
|
completed = dict(RESULT)
|
|
log.write_bytes(protocol.canonical_json(completed) + b"\nnoise\n" + protocol.canonical_json(completed) + b"\n")
|
|
monkeypatch.setattr(resume_bootstrap, "ROOT", tmp_path)
|
|
assert resume_bootstrap._completed_result(exact) == completed
|
|
seen = {}
|
|
monkeypatch.setattr(
|
|
scm.Boundary, "resume_artifact",
|
|
lambda _self, assignment, request, structured: seen.update(
|
|
assignment=assignment, request=request, structured=structured
|
|
) or {"receipt": "only"},
|
|
)
|
|
assert resume_bootstrap.bootstrap(KEY, exact) == {"receipt": "only"}
|
|
assert seen["structured"] == completed and seen["request"]["title"] == completed["summary"]
|
|
|
|
|
|
def test_mediator_bootstrap_rejects_a_symlinked_log_ancestor(tmp_path, monkeypatch):
|
|
exact = protocol.sign_envelope(KEY, "assignment", binding(), payload())
|
|
outside = tmp_path / "outside"
|
|
outside.mkdir()
|
|
session_root = tmp_path / "session-state"
|
|
session_root.mkdir()
|
|
(session_root / "metis").symlink_to(outside, target_is_directory=True)
|
|
monkeypatch.setattr(resume_bootstrap, "ROOT", tmp_path)
|
|
with pytest.raises(protocol.ProtocolError, match="unsafe"):
|
|
resume_bootstrap._completed_result(exact)
|
|
|
|
|
|
def test_publication_lease_signs_the_exact_binding_and_stops_cleanly():
|
|
boundary = client.ClientBoundary(KEY, FailingSCM(protocol.ProtocolError("unused")))
|
|
calls = []
|
|
|
|
def post(path, envelope):
|
|
calls.append((path, envelope))
|
|
return protocol.sign_envelope(KEY, "ack", binding(), {"accepted": True})
|
|
|
|
boundary._post = post
|
|
checkpoint, close = boundary._publication_lease(binding())
|
|
checkpoint()
|
|
close()
|
|
assert len(calls) == 1 and calls[0][0] == "/v1/heartbeat"
|
|
verified = protocol.verify_envelope(KEY, calls[0][1], expected_kind="heartbeat")
|
|
assert {name: verified[name] for name in binding()} == binding()
|
|
|
|
|
|
def test_publication_lease_renews_during_a_slow_scm_step_without_sleeping(monkeypatch):
|
|
boundary = client.ClientBoundary(KEY, FailingSCM(protocol.ProtocolError("unused")))
|
|
calls = []
|
|
|
|
class Event:
|
|
def __init__(self):
|
|
self.waits = 0
|
|
|
|
def wait(self, _seconds):
|
|
self.waits += 1
|
|
return self.waits > 1
|
|
|
|
def set(self):
|
|
return None
|
|
|
|
class Thread:
|
|
def __init__(self, *, target, daemon):
|
|
self.target = target
|
|
|
|
def start(self):
|
|
self.target()
|
|
|
|
def join(self, timeout):
|
|
assert timeout == 1
|
|
|
|
monkeypatch.setattr(client.threading, "Event", Event)
|
|
monkeypatch.setattr(client.threading, "Thread", Thread)
|
|
boundary._post = lambda path, envelope: calls.append((path, envelope)) or protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True}
|
|
)
|
|
_checkpoint, close = boundary._publication_lease(binding())
|
|
close()
|
|
assert [path for path, _envelope in calls] == ["/v1/heartbeat", "/v1/heartbeat"]
|
|
|
|
|
|
def test_lost_publication_checkpoint_prevents_the_push(tmp_path, monkeypatch):
|
|
boundary, exact, calls, _head = submit_harness(tmp_path, monkeypatch, {"wt/t_deadbeef": "a" * 40})
|
|
count = 0
|
|
|
|
def checkpoint():
|
|
nonlocal count
|
|
count += 1
|
|
if count == 3:
|
|
raise protocol.ProtocolError("SCM publication lease was lost")
|
|
|
|
with pytest.raises(protocol.ProtocolError, match="lease was lost"):
|
|
boundary.submit(exact, {"title": "safe", "body": "evidence"}, checkpoint=checkpoint)
|
|
assert not any("push" in call for call in calls)
|
|
|
|
|
|
def test_worker_cannot_spoof_a_transient_publication_retry_result():
|
|
boundary = client.ClientBoundary(KEY, FailingSCM(protocol.ProtocolError("unused")))
|
|
boundary.current = assignment(payload={"scm_resume": {}})
|
|
posted = []
|
|
boundary._post = lambda _path, envelope: posted.append(envelope) or protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
blocked = {**RESULT, "status": "blocked", "blockers": ["policy refusal"]}
|
|
boundary.finish({"binding": binding(), "payload": {
|
|
"structured": blocked, "returncode": 1, "publication_retry_transient": True,
|
|
}})
|
|
terminal = protocol.verify_envelope(KEY, posted[0])["payload"]
|
|
assert "publication_retry_transient" not in terminal
|
|
|
|
|
|
def test_a_successful_submission_records_both_the_draft_and_the_exact_branch():
|
|
class Recording:
|
|
def submit(self, _assignment, _request):
|
|
return {
|
|
"workspace": "/workspace/runs/metis/t_deadbeef/42",
|
|
"branch": "wt/t_deadbeef-attempt-2",
|
|
"pull_request": "https://scm/pulls/9",
|
|
}
|
|
|
|
boundary = client.ClientBoundary(KEY, Recording())
|
|
boundary.current = assignment()
|
|
boundary._post = lambda _path, _envelope: protocol.sign_envelope(
|
|
KEY, "ack", binding(), {"accepted": True, "duplicate": False}
|
|
)
|
|
finished = boundary.finish(
|
|
{
|
|
"binding": binding(),
|
|
"payload": {"structured": dict(RESULT), "returncode": 0},
|
|
"title": "safe", "body": "evidence",
|
|
}
|
|
)
|
|
assert finished["structured"]["status"] == "completed"
|
|
assert finished["structured"]["artifacts"] == [
|
|
"https://scm/pulls/9", "branch:wt/t_deadbeef-attempt-2",
|
|
]
|