db96c8ced7
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>
96 lines
4.9 KiB
Python
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()
|