Files
hermes-agent/tests/tools/test_bot_live_owner_delivery.py
T
FalconOrtiz db96c8ced7 fix: admit Bot Chat deliveries through the live session owner
Adapt FalconOrtiz's owner-mailbox proposal from #101564 onto the current
notification poller and topical modules. The durable mailbox is cross-process
ingress only: the existing owner admits its normal prompt turn after the
current turn and human FIFO clear. Retain immutable receipts, stable admission
identities, capability and lease fencing, compression lineage, and disable
blind recovery replay of imported turns.

The original stale server hunks and expiring receipt protocol were rebuilt
rather than cherry-picked: the current facade decomposition and durable busy
admission contract differ. Credit the earlier owner-mailbox work in #100544
and durable producer work in #100319.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
Co-authored-by: 686f6c61 <github@00b.tech>
2026-09-07 16:48:29 -07:00

96 lines
4.9 KiB
Python

"""Durable mailbox invariants, using real disk and exec boundaries."""
import json
import os
import subprocess
import sys
import pytest
@pytest.mark.parametrize("terminal_status", ["settled", "failed", "cancelled"])
def test_delivery_is_idempotent_fenced_and_permanent(tmp_path, terminal_status):
from tools import bot_live_delivery as mailbox
owner = dict(profile_home=str(tmp_path.resolve()), session_id="chat",
lease_id="lease", live_session_id="live")
delivery_id = "a" * 32
queued = mailbox.deliver_to_live_owner(tmp_path, owner, "hello", delivery_id=delivery_id)
assert queued["status"] == "queued"
assert mailbox.deliver_to_live_owner(tmp_path, owner, "hello", delivery_id=delivery_id) == queued
with pytest.raises(ValueError):
mailbox.deliver_to_live_owner(tmp_path, owner, "different", delivery_id=delivery_id)
assert mailbox.claim_pending_delivery(tmp_path, dict(owner, lease_id="other")) is None
assert mailbox.claim_pending_delivery(tmp_path, dict(owner, live_session_id="other")) is None
script = (
"import json,sys; from tools.bot_live_delivery import claim_pending_delivery; "
"print(json.dumps(claim_pending_delivery(sys.argv[1],json.loads(sys.argv[2]))))"
)
children = [subprocess.Popen([sys.executable, "-c", script, str(tmp_path), json.dumps(owner)],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True) for _ in range(2)]
results = []
for child in children:
out, err = child.communicate(timeout=30)
assert child.returncode == 0, err
results.append(json.loads(out))
claims = [r for r in results if r is not None]
assert len(claims) == 1 and claims[0]["message"] == "hello"
assert mailbox.read_delivery_result(tmp_path, delivery_id)["status"] == "claimed"
assert mailbox.claim_pending_delivery(tmp_path, owner) is None
receipt = mailbox.complete_delivery(tmp_path, delivery_id, status=terminal_status, reply="answer")
assert mailbox.read_delivery_result(tmp_path, delivery_id) == receipt
assert mailbox.complete_delivery(tmp_path, delivery_id, status=terminal_status, reply="answer") == receipt
with pytest.raises(ValueError):
mailbox.complete_delivery(tmp_path, delivery_id, status=terminal_status, reply="rewrite")
assert mailbox.deliver_to_live_owner(tmp_path, owner, "hello", delivery_id=delivery_id) == receipt
assert mailbox.claim_pending_delivery(tmp_path, owner) is None
if os.name != "nt":
for path in (tmp_path / "runtime" / mailbox.DELIVERY_DIR_NAME).iterdir():
assert path.stat().st_mode & 0o077 == 0
def test_fifo_survives_clock_rollback(tmp_path, monkeypatch):
from tools import bot_live_delivery as mailbox
owner = dict(profile_home=str(tmp_path.resolve()), session_id="chat",
lease_id="lease", live_session_id="live")
for timestamp, message in ((100, "first"), (90, "second")):
monkeypatch.setattr(mailbox.time, "time_ns", lambda: timestamp)
mailbox.deliver_to_live_owner(tmp_path, owner, message)
assert mailbox.claim_pending_delivery(tmp_path, owner)["message"] == "first"
assert mailbox.claim_pending_delivery(tmp_path, owner)["message"] == "second"
@pytest.mark.parametrize("capable", [True, False])
def test_only_canonical_capable_owner_receives_across_compression(tmp_path, capable):
from hermes_state import SessionDB
from hermes_cli.active_sessions import try_acquire_active_session, transfer_active_session
from tools import bot_live_delivery as mailbox
db = SessionDB(db_path=tmp_path / "state.db")
db.create_session(session_id="chat", source="cli")
db.set_session_title("chat", "Bot Chat")
meta = dict(live_session_id="live", bot_live_delivery_consumer=capable)
lease, refusal = try_acquire_active_session(session_id="chat", surface="desktop", config={},
registry_home=tmp_path, metadata=meta)
assert refusal is None
try:
owner = mailbox.find_canonical_live_owner(tmp_path)
if not capable:
assert owner is None
return
assert owner["lease_id"] == lease.lease_id
queued = mailbox.deliver_to_live_owner(tmp_path, owner, "before compression")
db.end_session("chat", "compression")
db.create_session(session_id="tip", source="cli", parent_session_id="chat")
assert transfer_active_session(lease, session_id="tip", metadata=meta)
current = mailbox.find_canonical_live_owner(tmp_path)
assert current["session_id"] == "tip"
claim = mailbox.claim_pending_delivery(tmp_path, current)
assert claim["delivery_id"] == queued["delivery_id"]
assert claim["session_id"] == "chat"
assert mailbox.claim_pending_delivery(tmp_path, current) is None
finally:
lease.release()
db.close()