Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""Regression: an authoritative final that prefix-extends a SPLIT
|
||||
delivery reconciles by suffix, not by full resend (PR 85796 review
|
||||
round 2, finding 3).
|
||||
|
||||
The _FINAL_TEXT adoption guard refuses wholesale adoption on split
|
||||
turns — correct (#78541: sealed heads would repeat inside the tail) but
|
||||
previously absolute: a post-split verifier footer never entered the
|
||||
ledger, delivered_final_matches() reported a mismatch, and the gateway
|
||||
resent the ENTIRE body+footer after the split chunks. Now the strictly
|
||||
prefix-extending case appends only the missing suffix to the live tail
|
||||
and the ledger; non-prefix rewrites keep the full-resend fallback.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
|
||||
|
||||
|
||||
def _make_adapter():
|
||||
from gateway.platforms.base import BasePlatformAdapter, SendResult
|
||||
|
||||
A = type("SplitAdapter", (BasePlatformAdapter,), {"MAX_MESSAGE_LENGTH": 4096})
|
||||
A.__abstractmethods__ = frozenset()
|
||||
a = A.__new__(A)
|
||||
a._typing_paused = set()
|
||||
a._fatal_error_message = None
|
||||
a.send_calls = []
|
||||
a.edit_calls = []
|
||||
|
||||
async def _send(chat_id, content, reply_to=None, metadata=None, **kw):
|
||||
a.send_calls.append(content)
|
||||
return SendResult(success=True, message_id=f"m{len(a.send_calls)}")
|
||||
a.send = _send
|
||||
|
||||
async def _edit(chat_id, message_id, content, **kw):
|
||||
a.edit_calls.append({"id": message_id, "content": content})
|
||||
return SendResult(success=True, message_id=message_id)
|
||||
a.edit_message = _edit
|
||||
return a
|
||||
|
||||
|
||||
BODY = "the split answer body"
|
||||
FOOTER = "\n\n⚠️ File-mutation verifier: 1 file(s) were NOT modified this turn."
|
||||
|
||||
|
||||
def _consumer(adapter):
|
||||
cfg = StreamConsumerConfig(
|
||||
transport="edit", chat_type="dm",
|
||||
edit_interval=0.01, buffer_threshold=1, cursor="",
|
||||
)
|
||||
return GatewayStreamConsumer(adapter, "D1", cfg)
|
||||
|
||||
|
||||
class TestSplitSuffixReconcile:
|
||||
@pytest.mark.asyncio
|
||||
async def test_prefix_extending_final_delivers_only_suffix(self):
|
||||
adapter = _make_adapter()
|
||||
sc = _consumer(adapter)
|
||||
task = asyncio.create_task(sc.run())
|
||||
sc.on_delta(BODY)
|
||||
await asyncio.sleep(0.06)
|
||||
# Simulate an overflow-adopted split mid-turn: heads sealed, the
|
||||
# ledger holds full text, _accumulated holds the tail only.
|
||||
sc._turn_split_delivery = True
|
||||
sc.finish(BODY + FOOTER)
|
||||
await task
|
||||
# The recorded payload reconciles with the authoritative final —
|
||||
# the gateway will NOT resend the body+footer.
|
||||
assert sc.delivered_final_matches(BODY + FOOTER) is True
|
||||
# And the footer actually reached the platform (rode the final
|
||||
# edit/send), rather than reconciling vacuously.
|
||||
all_payloads = adapter.send_calls + [
|
||||
e["content"] for e in adapter.edit_calls
|
||||
]
|
||||
assert any(FOOTER.strip() in p for p in all_payloads)
|
||||
# The body was NOT re-delivered whole after the split: no payload
|
||||
# equals the full body+footer via a fresh send while heads were
|
||||
# already on screen — the suffix rode the existing tail.
|
||||
assert all(
|
||||
p.startswith(BODY) or FOOTER.strip() in p for p in all_payloads
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_prefix_rewrite_keeps_mismatch(self):
|
||||
"""A rewritten final (not a prefix extension) must still report a
|
||||
mismatch so the gateway's full resend delivers the true final."""
|
||||
adapter = _make_adapter()
|
||||
sc = _consumer(adapter)
|
||||
task = asyncio.create_task(sc.run())
|
||||
sc.on_delta(BODY)
|
||||
await asyncio.sleep(0.06)
|
||||
sc._turn_split_delivery = True
|
||||
rewritten = "a completely different final answer"
|
||||
sc.finish(rewritten)
|
||||
await task
|
||||
assert sc.delivered_final_matches(rewritten) is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unsplit_turn_adoption_unchanged(self):
|
||||
adapter = _make_adapter()
|
||||
sc = _consumer(adapter)
|
||||
task = asyncio.create_task(sc.run())
|
||||
sc.on_delta(BODY)
|
||||
await asyncio.sleep(0.06)
|
||||
sc.finish(BODY + FOOTER)
|
||||
await task
|
||||
assert sc.delivered_final_matches(BODY + FOOTER) is True
|
||||
Reference in New Issue
Block a user