Implement worker coordination runtime and finish WP-0003
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07b5b-ea58-7ad2-bdbb-0b1c995cfc35
This commit is contained in:
parent
214964ccb8
commit
628f984a10
23 changed files with 3025 additions and 544 deletions
191
tests/test_adapters.py
Normal file
191
tests/test_adapters.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import json
|
||||
import socket
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
import pytest
|
||||
|
||||
from coordination_engine.adapters import Hub, SafetyError, Tamq, socket_request
|
||||
from coordination_engine.config import Config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def peer(tmp_path):
|
||||
path = tmp_path / "tamq.sock"
|
||||
server = socket.socket(socket.AF_UNIX)
|
||||
server.bind(str(path))
|
||||
path.chmod(0o600)
|
||||
server.listen()
|
||||
server.settimeout(0.05)
|
||||
state = {
|
||||
"requests": [],
|
||||
"messages": {},
|
||||
"endpoints": [{"endpoint_id": "tmux-amq-123", "repos": '["demo"]'}],
|
||||
"protocol": "0.1",
|
||||
"capabilities": ["bounded_delivery_ack_v1", "idempotent_send_v1"],
|
||||
}
|
||||
stop = threading.Event()
|
||||
|
||||
def run():
|
||||
while not stop.is_set():
|
||||
try:
|
||||
conn, _ = server.accept()
|
||||
except socket.timeout:
|
||||
continue
|
||||
with conn:
|
||||
request = json.loads(conn.makefile("rb").readline())
|
||||
state["requests"].append(request)
|
||||
op = request["op"]
|
||||
response = {"ok": True}
|
||||
if op == "ping":
|
||||
response.update(
|
||||
protocol=state["protocol"], capabilities=state["capabilities"]
|
||||
)
|
||||
elif op == "endpoints":
|
||||
response["endpoints"] = state["endpoints"]
|
||||
elif op == "send":
|
||||
key = request["idempotency_key"]
|
||||
state["messages"].setdefault(
|
||||
key, "message-" + str(len(state["messages"]))
|
||||
)
|
||||
response.update(message_id=state["messages"][key], state="pending")
|
||||
elif op == "message":
|
||||
response["message"] = {
|
||||
"state": "injected",
|
||||
"message_id": request["message_id"],
|
||||
}
|
||||
conn.sendall((json.dumps(response) + "\n").encode())
|
||||
|
||||
thread = threading.Thread(target=run)
|
||||
thread.start()
|
||||
yield path, state
|
||||
stop.set()
|
||||
thread.join(timeout=2)
|
||||
server.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def adapter(peer, monkeypatch):
|
||||
path, state = peer
|
||||
monkeypatch.setattr(
|
||||
"coordination_engine.adapters.registry",
|
||||
lambda: {"demo": "/demo", "coordination-engine": "/coordination"},
|
||||
)
|
||||
return Tamq(Config(tamq_socket=path).validate()), state
|
||||
|
||||
|
||||
def test_socket_protocol_and_stable_send(adapter):
|
||||
tamq, state = adapter
|
||||
lease = {"repo": "demo", "lease_id": "lease", "trigger_id": "trigger"}
|
||||
first = tamq.wake(lease, "Inspect work")
|
||||
second = tamq.wake(lease, "Inspect work")
|
||||
assert first["message_id"] == second["message_id"]
|
||||
assert len(state["messages"]) == 1
|
||||
send = next(r for r in state["requests"] if r["op"] == "send")
|
||||
assert send["metadata"] == {"lease_id": "lease", "trigger_id": "trigger"}
|
||||
assert send["endpoint_id"] == "tmux-amq-123"
|
||||
assert tamq.receipt(first["message_id"])["state"] == "injected"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field,value",
|
||||
[
|
||||
("protocol", "1.0"),
|
||||
("capabilities", []),
|
||||
(
|
||||
"endpoints",
|
||||
[
|
||||
{"endpoint_id": "a", "repos": '["demo"]'},
|
||||
{"endpoint_id": "b", "repos": '["demo"]'},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_protocol_and_ambiguity_gate_before_send(adapter, field, value):
|
||||
tamq, state = adapter
|
||||
state[field] = value
|
||||
with pytest.raises(SafetyError):
|
||||
tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect")
|
||||
assert not state["messages"]
|
||||
|
||||
|
||||
def test_registry_refresh_each_wake(adapter, monkeypatch):
|
||||
tamq, state = adapter
|
||||
monkeypatch.setattr("coordination_engine.adapters.registry", lambda: {})
|
||||
with pytest.raises(SafetyError):
|
||||
tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect")
|
||||
assert not state["requests"]
|
||||
|
||||
|
||||
def test_insecure_socket_rejected(peer):
|
||||
path, state = peer
|
||||
path.chmod(0o666)
|
||||
with pytest.raises(SafetyError):
|
||||
socket_request(path, {"op": "ping"})
|
||||
assert not state["requests"]
|
||||
|
||||
|
||||
def test_hub_http_snapshot_and_sanitized_projection():
|
||||
posts = []
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/repos/"):
|
||||
data = [{"id": "r", "slug": "demo"}]
|
||||
elif self.path == "/workplans/":
|
||||
data = [{"id": "p", "repo_id": "r", "status": "active"}]
|
||||
elif self.path.startswith("/tasks/"):
|
||||
data = [{"id": "t", "workplan_id": "p", "status": "todo"}]
|
||||
else:
|
||||
data = []
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data).encode())
|
||||
|
||||
def do_POST(self):
|
||||
posts.append(
|
||||
json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
||||
)
|
||||
self.send_response(201)
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"id":"receipt"}')
|
||||
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
try:
|
||||
hub = Hub(
|
||||
Config(
|
||||
api_base=f"http://127.0.0.1:{server.server_port}", repos=["demo"]
|
||||
).validate()
|
||||
)
|
||||
snapshot = hub.snapshot()
|
||||
assert snapshot["repos"] == {"r": "demo"}
|
||||
assert snapshot["tasks"][0]["id"] == "t"
|
||||
hub.project({"id": "r1", "state": "offered", "reason": "wake_attempt"})
|
||||
assert posts[0]["detail"]["id"] == "r1"
|
||||
assert posts[0]["event_type"] == "coordination_receipt"
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
server.server_close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field,value",
|
||||
[
|
||||
("protocol", "0.invalid"),
|
||||
("capabilities", None),
|
||||
("endpoints", None),
|
||||
("endpoints", [{"endpoint_id": "e", "repos": None}]),
|
||||
],
|
||||
)
|
||||
def test_malformed_peer_stops_before_send(adapter, field, value):
|
||||
tamq, state = adapter
|
||||
state[field] = value
|
||||
with pytest.raises(SafetyError):
|
||||
tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect")
|
||||
assert not state["messages"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue