e74b2f3832
#4 a missing/corrupt .since OR a failed room_read now resumes from the newest id / backs off — never resets to 0 (which re-ran the ENTIRE history through the model). room_read/_read_since return None on failure to distinguish it from an empty room. #2 posts are rc-checked; the offset advances past a message only after its reply actually lands (3 retries) — a transient lmcp/sic failure retries, never silently drops. #3 dispatch/run exceptions no longer skip the message (offset is reply-gated, not advanced-then-maybe-fail). #5 offset is persisted per fully-handled message — a restart mid-batch resumes exactly, no duplicate ack / re-run. #6 lurker reply capped at 6000 chars (long Fable reviews no longer blow room limits). tests/test_worker_reliability.py locks the #4/#2 core (read-fail != empty; rc checks). 6/6. Also: bp timeouts for testdesigner/py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""Reviewer findings #2 / #4 — bullpen_worker must distinguish a FAILED room_read from an
|
|
empty room, and check post success. (#4: `except: since=0` replayed all history when the
|
|
offset read failed; the root cause was room_read swallowing failure as [].)"""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[1]
|
|
_spec = importlib.util.spec_from_file_location("bw", REPO / "lib" / "bullpen_worker.py")
|
|
bw = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(bw)
|
|
|
|
|
|
class FakeCP:
|
|
def __init__(self, rc=0, stdout=""):
|
|
self.returncode = rc
|
|
self.stdout = stdout
|
|
|
|
|
|
def test_ok_checks_returncode():
|
|
assert bw._ok(FakeCP(rc=0)) is True
|
|
assert bw._ok(FakeCP(rc=1)) is False
|
|
assert bw._ok(None) is False
|
|
|
|
|
|
def test_read_failure_is_none_not_empty(monkeypatch):
|
|
# #4: a failed read must NOT look like an empty room (which seeded since=0 -> replay)
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=1, stdout=""))
|
|
assert bw._read_since(0) is None
|
|
|
|
|
|
def test_read_success_empty_is_empty_list(monkeypatch):
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout=""))
|
|
assert bw._read_since(0) == []
|
|
|
|
|
|
def test_read_parses_jsonl(monkeypatch):
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout='{"id":1}\n \n{"id":2}\n'))
|
|
assert [m["id"] for m in bw._read_since(0)] == [1, 2]
|
|
|
|
|
|
def test_newest_id_from_room(monkeypatch):
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout='{"id":5}\n{"id":9}\n{"id":3}\n'))
|
|
assert bw._newest_id() == 9
|
|
|
|
|
|
def test_newest_id_is_zero_only_on_empty_not_on_failure(monkeypatch):
|
|
# empty room -> 0 is fine; a FAILED read -> 0 too, but never a replay because room_read=None
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=1))
|
|
assert bw._newest_id() == 0
|
|
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout=""))
|
|
assert bw._newest_id() == 0
|