* fix(#6611): make regeneration transaction canonical * fix(#6611): close cross-provider regeneration blockers * fix(#6611): harden regeneration transaction boundaries * fix(#6611): bind canonical context and fork lineage * fix(#6611): restore active turn token stamping * test(#6611): make regeneration regressions CI portable * test(#6611): preserve the tracked regeneration error fixture * fix(#6611): preserve extracted session harness contracts * fix(regeneration): preserve concurrent and imported turn ownership (#6611) * fix(regeneration): honor imported turn ownership safely (#6611) * test(regeneration): cover both session lock outcomes (#6611) * test(regeneration): clear unused transaction fixtures (#6611) * fix(#6611): consume public active-turn marker * ci: re-trigger (Playwright install hung ~2.5h on GH runners, warm-cache retry) --------- Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com> Co-authored-by: n <a@n>
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
import copy
|
|
import pytest
|
|
|
|
from api.models import Session
|
|
from api.session_ops import RegenerationUnavailable, apply_regeneration_plan, plan_regeneration, regeneration_revision
|
|
|
|
|
|
def _session():
|
|
return Session(
|
|
session_id="settlement6611",
|
|
messages=[
|
|
{"role": "user", "content": "old", "id": "u0", "_source": "webui"},
|
|
{"role": "assistant", "content": "old answer"},
|
|
{"role": "user", "content": "latest", "id": "u1", "_source": "webui"},
|
|
{"role": "assistant", "content": "error"},
|
|
],
|
|
context_messages=[
|
|
{"role": "user", "content": "old", "id": "u0"},
|
|
{"role": "assistant", "content": "old answer"},
|
|
{"role": "user", "content": "latest", "id": "u1"},
|
|
{"role": "assistant", "content": "error"},
|
|
],
|
|
)
|
|
|
|
|
|
def test_recovered_prefix_survives_regeneration_truncation():
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
assert apply_regeneration_plan(session, plan)
|
|
assert [row["content"] for row in session.messages] == ["old", "old answer", "latest"]
|
|
|
|
|
|
def test_stale_revision_rejects_without_mutation():
|
|
session = _session()
|
|
before = copy.deepcopy(session.__dict__)
|
|
with pytest.raises(RegenerationUnavailable):
|
|
plan_regeneration(session, expected_revision="stale-revision")
|
|
assert session.__dict__ == before
|
|
|
|
|
|
def test_double_fire_revision_changes_after_winner():
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
assert apply_regeneration_plan(session, plan)
|
|
assert regeneration_revision(session) != plan.revision
|
|
|
|
|
|
def test_trailing_tool_state_is_not_regenerable_as_an_older_exchange():
|
|
session = Session(
|
|
session_id="settlement6611-tool-tail",
|
|
messages=[
|
|
{"role": "user", "content": "prompt", "_source": "webui"},
|
|
{"role": "assistant", "content": "visible answer"},
|
|
{"role": "tool", "content": "unfinished tool result"},
|
|
],
|
|
context_messages=[
|
|
{"role": "user", "content": "prompt"},
|
|
{"role": "assistant", "content": "visible answer"},
|
|
{"role": "tool", "content": "unfinished tool result"},
|
|
],
|
|
)
|
|
try:
|
|
plan_regeneration(session)
|
|
except RegenerationUnavailable as exc:
|
|
assert exc.code == "no_regenerable_turn"
|
|
else:
|
|
raise AssertionError("trailing tool state was treated as a completed exchange")
|