Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""Regression: per-TURN stream/card identity (PR 85796 review, B2).
|
||||
|
||||
Keying coordination state on the thread anchor alone is simultaneously:
|
||||
|
||||
- too coarse: two parallel turns replying INSIDE ONE Slack thread share
|
||||
thread_ts — turn A's final sealed turn B's stream with A's content
|
||||
while A's own stream stayed open (live probe on the review branch);
|
||||
- too fragile: a flat DM with no thread metadata degraded to the bare
|
||||
chat id, re-creating the original finding-#10 collision.
|
||||
|
||||
The key now prefers the triggering inbound message id (message_id /
|
||||
reply_to_message_id — per-turn by construction), falling back to the
|
||||
thread anchor, then the bare chat. Legacy callers with placement-only
|
||||
metadata still seal via _match_open_draft's single-open-stream fallback,
|
||||
but NEVER when multiple streams are open (a duplicate message is
|
||||
recoverable; sealing someone else's stream is not).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.gateway.relay.test_relay_live_cards import _connected_adapter
|
||||
|
||||
|
||||
class RecordingTransport:
|
||||
def __init__(self):
|
||||
self.ops = []
|
||||
self._n = 0
|
||||
|
||||
async def send_outbound(self, payload, platform=None):
|
||||
self.ops.append(dict(payload))
|
||||
self._n += 1
|
||||
return {"success": True, "message_id": f"ts.{self._n}"}
|
||||
|
||||
|
||||
def _adapter():
|
||||
adapter, _ = _connected_adapter()
|
||||
t = RecordingTransport()
|
||||
adapter._transport = t
|
||||
return adapter, t
|
||||
|
||||
|
||||
class TestSameThreadParallelTurns:
|
||||
@pytest.mark.asyncio
|
||||
async def test_two_turns_in_one_thread_do_not_collide(self):
|
||||
"""The exact B2 probe: same thread_ts, distinct triggering message
|
||||
ids -> each turn seals its OWN stream with its OWN content."""
|
||||
adapter, t = _adapter()
|
||||
md_a = {"thread_ts": "1700.100", "message_id": "1700.111"}
|
||||
md_b = {"thread_ts": "1700.100", "message_id": "1700.222"}
|
||||
await adapter.send_draft("C1", 11, "A partial", metadata=md_a)
|
||||
await adapter.send_draft("C1", 12, "B partial", metadata=md_b)
|
||||
ra = await adapter.send("C1", "A final", metadata=dict(md_a))
|
||||
rb = await adapter.send("C1", "B final", metadata=dict(md_b))
|
||||
assert ra.success and rb.success
|
||||
seals = [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
assert {(s["draft_id"], s["content"]) for s in seals} == {
|
||||
(11, "A final"),
|
||||
(12, "B final"),
|
||||
}, seals
|
||||
assert not [o for o in t.ops if o["op"] == "send"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_card_ids_distinct_per_turn_in_one_thread(self):
|
||||
adapter, t = _adapter()
|
||||
md_a = {"thread_ts": "1700.100", "message_id": "1700.111"}
|
||||
md_b = {"thread_ts": "1700.100", "message_id": "1700.222"}
|
||||
tasks = [{"id": "t1", "title": "x", "status": "in_progress"}]
|
||||
await adapter.send_native_task_card_progress(
|
||||
"C1", tasks, reply_to=None, metadata=md_a
|
||||
)
|
||||
await adapter.send_native_task_card_progress(
|
||||
"C1", tasks, reply_to=None, metadata=md_b
|
||||
)
|
||||
cards = [o for o in t.ops if o["op"] == "task_card"]
|
||||
assert len({c["card_id"] for c in cards}) == 2, cards
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_card_stop_uses_same_key_as_send(self):
|
||||
adapter, t = _adapter()
|
||||
md = {"thread_ts": "1700.100", "message_id": "1700.111"}
|
||||
tasks = [{"id": "t1", "title": "x", "status": "in_progress"}]
|
||||
await adapter.send_native_task_card_progress(
|
||||
"C1", tasks, reply_to=None, metadata=md
|
||||
)
|
||||
await adapter.stop_native_task_card_progress("C1", metadata=md)
|
||||
card_ids = {
|
||||
o["card_id"]
|
||||
for o in t.ops
|
||||
if o["op"] in ("task_card", "task_card_stop")
|
||||
}
|
||||
assert len(card_ids) == 1, t.ops
|
||||
|
||||
|
||||
class TestFlatDmKeying:
|
||||
@pytest.mark.asyncio
|
||||
async def test_flat_dm_without_thread_metadata_still_seals(self):
|
||||
"""Flat DM: no thread anchor anywhere, but the consumer stamps
|
||||
reply_to_message_id on frames and the final alike."""
|
||||
adapter, t = _adapter()
|
||||
md = {"reply_to_message_id": "evt.1"}
|
||||
await adapter.send_draft("D1", 5, "partial", metadata=md)
|
||||
r = await adapter.send("D1", "final", metadata=dict(md))
|
||||
assert r.success
|
||||
seals = [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
assert len(seals) == 1 and seals[0]["content"] == "final"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parallel_flat_dm_turns_keyed_by_message_id(self):
|
||||
adapter, t = _adapter()
|
||||
md_a = {"reply_to_message_id": "evt.a"}
|
||||
md_b = {"reply_to_message_id": "evt.b"}
|
||||
await adapter.send_draft("D1", 21, "A partial", metadata=md_a)
|
||||
await adapter.send_draft("D1", 22, "B partial", metadata=md_b)
|
||||
await adapter.send("D1", "A final", metadata=dict(md_a))
|
||||
await adapter.send("D1", "B final", metadata=dict(md_b))
|
||||
seals = [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
assert {(s["draft_id"], s["content"]) for s in seals} == {
|
||||
(21, "A final"),
|
||||
(22, "B final"),
|
||||
}
|
||||
|
||||
|
||||
class TestLegacyPlacementOnlyCallers:
|
||||
@pytest.mark.asyncio
|
||||
async def test_single_open_stream_absorbs_identityless_final(self):
|
||||
"""A resolver-lane send with NO turn identity still seals when the
|
||||
chat has exactly one open stream (legacy compatibility)."""
|
||||
adapter, t = _adapter()
|
||||
md = {"thread_ts": "1700.9", "message_id": "1700.910"}
|
||||
await adapter.send_draft("C1", 31, "partial", metadata=md)
|
||||
r = await adapter.send("C1", "final", metadata=None)
|
||||
assert r.success
|
||||
seals = [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
assert len(seals) == 1 and seals[0]["draft_id"] == 31
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_thread_anchored_placement_only_final_seals(self):
|
||||
"""Review r2, finding 5: metadata carrying ONLY a thread anchor is
|
||||
placement info, not turn identity — with one open turn-keyed
|
||||
stream in the chat, the final must absorb into it, not post a
|
||||
plain duplicate beside the live stream."""
|
||||
adapter, t = _adapter()
|
||||
md_frames = {"thread_ts": "1700.9", "message_id": "1700.910"}
|
||||
await adapter.send_draft("C1", 32, "partial", metadata=md_frames)
|
||||
r = await adapter.send("C1", "final", metadata={"thread_ts": "1700.9"})
|
||||
assert r.success
|
||||
seals = [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
assert len(seals) == 1 and seals[0]["draft_id"] == 32, (
|
||||
"placement-only final must seal the single open stream"
|
||||
)
|
||||
assert not [o for o in t.ops if o["op"] == "send"]
|
||||
assert not adapter._open_draft_by_chat
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_open_streams_identityless_send_stays_plain(self):
|
||||
"""With several open streams, an identity-less send must NOT guess:
|
||||
plain send (recoverable) over sealing the wrong stream (not)."""
|
||||
adapter, t = _adapter()
|
||||
await adapter.send_draft(
|
||||
"C1", 41, "A", metadata={"message_id": "m.1"}
|
||||
)
|
||||
await adapter.send_draft(
|
||||
"C1", 42, "B", metadata={"message_id": "m.2"}
|
||||
)
|
||||
r = await adapter.send("C1", "ambiguous final", metadata=None)
|
||||
assert r.success
|
||||
assert [o["op"] for o in t.ops][-1] == "send"
|
||||
assert not [o for o in t.ops if o["op"] == "draft" and o.get("final")]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_placement_only_with_multiple_open_streams_stays_plain(self):
|
||||
adapter, t = _adapter()
|
||||
await adapter.send_draft(
|
||||
"C1", 43, "A", metadata={"thread_ts": "1700.9", "message_id": "m.3"}
|
||||
)
|
||||
await adapter.send_draft(
|
||||
"C1", 44, "B", metadata={"thread_ts": "1700.9", "message_id": "m.4"}
|
||||
)
|
||||
r = await adapter.send("C1", "ambiguous", metadata={"thread_ts": "1700.9"})
|
||||
assert r.success
|
||||
assert [o["op"] for o in t.ops][-1] == "send"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_id_mismatch_never_falls_back(self):
|
||||
"""A caller WITH a message id whose key misses must not steal the
|
||||
one open stream — its identity is authoritative (different turn)."""
|
||||
adapter, t = _adapter()
|
||||
await adapter.send_draft("C1", 45, "A", metadata={"message_id": "m.5"})
|
||||
r = await adapter.send("C1", "other turn", metadata={"message_id": "m.OTHER"})
|
||||
assert r.success
|
||||
assert [o["op"] for o in t.ops][-1] == "send"
|
||||
assert adapter._open_draft_by_chat, "stream must survive the mismatch"
|
||||
Reference in New Issue
Block a user