fix(work): import hashlib and wire heal subparser into main CLI
This commit is contained in:
@@ -609,6 +609,7 @@ def test_wired_dm_send_asserts_post_nav_url_before_send():
|
||||
assert "assert_pre_send_placement" in src, \
|
||||
"gate exists but dm_send never calls it"
|
||||
_orig_run_full = dm.run_full
|
||||
_orig_sleep = dm.time.sleep
|
||||
_calls = []
|
||||
|
||||
def _stub(cmd, timeout=60):
|
||||
@@ -619,6 +620,9 @@ def test_wired_dm_send_asserts_post_nav_url_before_send():
|
||||
|
||||
try:
|
||||
dm.run_full = _stub
|
||||
# Settle sleeps (1s/2s per gate call) are production pacing, not
|
||||
# asserted behavior: skip them like the browser subprocess above.
|
||||
dm.time.sleep = lambda s: None
|
||||
# 1. UUID-known thread, correct placement -> pass
|
||||
_stub.url = "https://muse.ai/thread/" + UUID_A
|
||||
ok, detail = gate("opm", "pipe-x", UUID_A, direct_nav_done=True)
|
||||
@@ -641,6 +645,7 @@ def test_wired_dm_send_asserts_post_nav_url_before_send():
|
||||
assert ok is True, f"re-nav path should pass: {detail}"
|
||||
finally:
|
||||
dm.run_full = _orig_run_full
|
||||
dm.time.sleep = _orig_sleep
|
||||
return
|
||||
nav_i = src.find("sidechat use")
|
||||
send_i = src.find("Send with verification retries")
|
||||
@@ -754,9 +759,141 @@ def main():
|
||||
import unittest
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# harvester resurrection -- dm_id markers, dry-run purity, scheduling
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def test_wired_harvester_dmid_marker_resolves():
|
||||
"""Real clear_matching_followups: [RESULT <dm_id>] resolves a
|
||||
DM-ordered followup whose job_id differs (live f4293153 pattern:
|
||||
marker quoted the nudge's DM id, record job was ml-muse-*).
|
||||
Unrelated thread isolates the dm_id path from thread matching."""
|
||||
harv = _load("harvester_under_test", "response-harvester.py")
|
||||
rec = _mk_rec(thread_uuid=UUID_B, job_id="ml-muse-20261007-013210")
|
||||
fups = {"f4293153": rec}
|
||||
harv.clear_matching_followups(fups, "646", "unrelated-thread", "mid-9",
|
||||
"[RESULT f4293153] done", dry_run=True,
|
||||
job_id="f4293153", verb="RESULT")
|
||||
assert rec.get("status") == "resolved", (
|
||||
"DEVIATION: [RESULT <dm_id>] does not resolve its followup -- "
|
||||
"clear_matching_followups() matches marker ids against job_id "
|
||||
f"only, never the followup key (status={rec.get('status')!r})")
|
||||
# ... and a wrong id must not resolve.
|
||||
rec2 = _mk_rec(thread_uuid=UUID_B, job_id="ml-muse-20261007-013210")
|
||||
fups2 = {"f4293153": rec2}
|
||||
harv.clear_matching_followups(fups2, "646", "unrelated-thread", "mid-9",
|
||||
"[RESULT deadbeef] done", dry_run=True,
|
||||
job_id="deadbeef", verb="RESULT")
|
||||
assert rec2.get("status") == "pending", (
|
||||
f"wrong marker id wrongly resolved (status={rec2.get('status')!r})")
|
||||
|
||||
|
||||
def test_wired_harvester_dry_run_has_no_side_effects():
|
||||
"""process_messages(dry_run=True) with an evidence-less RESULT must
|
||||
still extract the marker but must not fire proof followups,
|
||||
archive threads, or persist anything."""
|
||||
harv = _load("harvester_under_test", "response-harvester.py")
|
||||
calls = []
|
||||
saved = {n: getattr(harv, n) for n in
|
||||
("execute_agent_tool", "archive_ephemeral_thread",
|
||||
"append_jsonl", "save_json_file")}
|
||||
harv.execute_agent_tool = lambda *a, **k: calls.append("exec") or (True, {})
|
||||
harv.archive_ephemeral_thread = (
|
||||
lambda *a, **k: calls.append("archive"))
|
||||
harv.append_jsonl = lambda *a, **k: calls.append("append")
|
||||
harv.save_json_file = lambda *a, **k: calls.append("save")
|
||||
try:
|
||||
msgs = [{"id": "m1", "author": "assistant",
|
||||
"text": "[RESULT j1] done",
|
||||
"ts": "2026-10-07T00:00:00+00:00"}]
|
||||
new, wm, nres = harv.process_messages(
|
||||
msgs, "646", UUID_A, "t", None, {}, dry_run=True)
|
||||
finally:
|
||||
for n, fn in saved.items():
|
||||
setattr(harv, n, fn)
|
||||
assert nres == 1, "dry-run must still extract markers"
|
||||
assert calls == [], f"dry-run leaked side effects: {calls}"
|
||||
|
||||
|
||||
def test_wired_result_markers_bare_and_status_forms():
|
||||
"""iter_result_markers handles the engine's 3-group shape: a bare
|
||||
[RESULT <id>] <text> (status None) must not crash, and a status
|
||||
token must survive into the result text for fail detection."""
|
||||
harv = _load("harvester_under_test", "response-harvester.py")
|
||||
assert list(harv.iter_result_markers("[RESULT f4293153] done")) == [
|
||||
("f4293153", "done")], "bare RESULT marker must extract cleanly"
|
||||
jid, text = list(harv.iter_result_markers("[RESULT j9] FAIL blew up"))[0]
|
||||
assert jid == "j9" and "FAIL" in text and "blew up" in text, (
|
||||
f"status token must survive into result text (got {jid!r} {text!r})")
|
||||
|
||||
|
||||
def test_wired_poison_message_does_not_wedge_batch():
|
||||
"""A marker-extraction crash degrades to plain-reply handling so
|
||||
sibling messages still process and the watermark keeps advancing."""
|
||||
harv = _load("harvester_under_test", "response-harvester.py")
|
||||
real_iter = harv.iter_result_markers
|
||||
real_nudge = harv.maybe_nudge_untagged_sidechat
|
||||
def boom(text):
|
||||
if "POISON" in (text or ""):
|
||||
raise RuntimeError("boom")
|
||||
return real_iter(text)
|
||||
harv.iter_result_markers = boom
|
||||
harv.maybe_nudge_untagged_sidechat = lambda *a, **k: None
|
||||
try:
|
||||
msgs = [
|
||||
{"id": "m1", "author": "assistant",
|
||||
"text": "POISON [RESULT x] y",
|
||||
"ts": "2026-10-07T00:00:00+00:00"},
|
||||
{"id": "m2", "author": "assistant",
|
||||
"text": "[RESULT j2] ok",
|
||||
"ts": "2026-10-07T00:01:00+00:00"},
|
||||
]
|
||||
new, wm, nres = harv.process_messages(
|
||||
msgs, "646", UUID_A, "t", None, {}, dry_run=True)
|
||||
finally:
|
||||
harv.iter_result_markers = real_iter
|
||||
harv.maybe_nudge_untagged_sidechat = real_nudge
|
||||
assert nres == 1, "sibling marker must still extract"
|
||||
assert [m["id"] for m in new] == ["m1", "m2"], \
|
||||
"both messages must process past the poison one"
|
||||
|
||||
|
||||
def test_harvester_timer_unit_wired():
|
||||
"""The harvester must be scheduler-owned: unit files exist, the
|
||||
service runs --once, and the timer fires on a short cadence.
|
||||
Ingestion died silently for ~22h with no unit at all."""
|
||||
root = BIN_DIR.parent
|
||||
svc = (root / "systemd" / "response-harvester.service").read_text()
|
||||
tmr = (root / "systemd" / "response-harvester.timer").read_text()
|
||||
assert "response-harvester.py" in svc and "--once" in svc, \
|
||||
"service must run the harvester --once"
|
||||
assert "OnUnitActiveSec=" in tmr, "timer needs a repeat cadence"
|
||||
assert "WantedBy=timers.target" in tmr, "timer must target timers.target"
|
||||
|
||||
|
||||
def test_collection_adapter_is_single_and_pytest_opted_out():
|
||||
"""Collection-shape guard (no 3x duplicates): exactly one TestCase
|
||||
adapter is reachable from module globals (the adapter loop must not
|
||||
leak a `_fn` alias that pytest collects as a second class), and the
|
||||
adapter opts out of pytest (`__test__ = False`) so the module-level
|
||||
functions are pytest's single source while unittest discovery still
|
||||
runs the adapter."""
|
||||
cases = [v for v in list(globals().values())
|
||||
if inspect.isclass(v) and issubclass(v, unittest.TestCase)]
|
||||
assert len(cases) == 1, (
|
||||
f"expected exactly 1 TestCase adapter, found {len(cases)} "
|
||||
f"(stray aliases reintroduce duplicate collection)")
|
||||
assert TestFollowupFixes.__test__ is False, (
|
||||
"TestFollowupFixes must set __test__ = False so pytest collects "
|
||||
"each test once via the module-level functions")
|
||||
|
||||
|
||||
class TestFollowupFixes(unittest.TestCase):
|
||||
"""unittest discovery adapter for contract and wired test functions."""
|
||||
pass
|
||||
# pytest collects the module-level functions; skip the adapter so each
|
||||
# test runs once. (unittest discovery ignores __test__ and still runs
|
||||
# the adapter, which is its only view of this file's tests.)
|
||||
__test__ = False
|
||||
|
||||
|
||||
for _name, _fn in list(globals().items()):
|
||||
@@ -767,6 +904,11 @@ for _name, _fn in list(globals().items()):
|
||||
return _runner
|
||||
setattr(TestFollowupFixes, _name, _bind(_fn))
|
||||
|
||||
# Drop the loop temporaries: after the final iteration `_fn` aliases
|
||||
# TestFollowupFixes, and pytest collects TestCase subclasses regardless of
|
||||
# name -- that stray alias was the third copy (module fn + adapter + `_fn`).
|
||||
del _name, _fn
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user