Files
hermes-webui/tests/test_recovered_anchor_settle_duplication.py
T
alfred-rootson b71c88f292 fix(workspace): preserve remote POSIX paths across Session & streaming callsites (#7131) (#7168)
Closes the remote POSIX workspace path preservation gap across Session and streaming. Profile-aware runtime path resolution.

Co-authored-by: alfred-rootson <alfred.wojtasordzik@gmail.com>
2026-09-15 15:37:11 -07:00

597 lines
27 KiB
Python

"""Geometric duplication of recovered reasoning-only rows at turn settle.
Refs #7388 / #7416 (linear journal replay). This is the INDEPENDENT geometric
leg: each settled turn re-appended every empty recovered anchor once more.
"""
from __future__ import annotations
import copy
import json
import os
import pathlib
import pytest
from api.models import Session
from api.streaming import (
_assign_stable_message_ids,
_merge_display_messages_after_agent_result,
_restore_display_reasoning_metadata,
_settle_result_messages,
)
from api import streaming as _streaming
STREAM_ID = "34d53038e2d3485cb67cd90ef8408faf"
def _recovered_anchor(reasoning: str, ts: int = 1788356394) -> dict:
return {
"role": "assistant",
"content": "",
"timestamp": ts,
"_recovered_from_run_journal": True,
"_recovered_stream_id": STREAM_ID,
"reasoning": reasoning,
}
def _is_clone(message) -> bool:
return bool(
isinstance(message, dict)
and message.get("_recovered_from_run_journal")
and message.get("role") == "assistant"
and not (message.get("content") or "")
)
def _clone_blocks(messages) -> list[int]:
blocks, run = [], 0
for message in messages:
if _is_clone(message):
run += 1
elif run:
blocks.append(run)
run = 0
if run:
blocks.append(run)
return blocks
def _compacted_history() -> tuple[list[dict], list[dict]]:
"""Display transcript with a recovered anchor + a COMPACTED model context.
The anchor's turn was compressed out of the context, so its API-safe
position lies beyond the end of ``result_messages`` (production shape).
"""
display = [
{"role": "user", "content": "reload the webui", "timestamp": 1788356170},
{"role": "assistant", "content": "Restarting the WebUI unit.", "timestamp": 1788356390},
_recovered_anchor("The command was killed with SIGTERM"),
{"role": "user", "content": "did it come back?", "timestamp": 1788357000},
{"role": "assistant", "content": "Yes, it is up.", "timestamp": 1788357010},
]
context = [
{"role": "user", "content": "did it come back?", "timestamp": 1788357000},
{"role": "assistant", "content": "Yes, it is up.", "timestamp": 1788357010},
]
return display, context
def _settle_turn(display, context, turn: int):
msg_text = f"follow-up {turn}"
ts = 1788440000 + turn * 10
result = copy.deepcopy(context) + [
{"role": "user", "content": msg_text, "timestamp": ts},
{"role": "assistant", "content": f"reply {turn}", "timestamp": ts + 5},
]
restored = _restore_display_reasoning_metadata(copy.deepcopy(display), copy.deepcopy(result))
merged = _merge_display_messages_after_agent_result(
copy.deepcopy(display), copy.deepcopy(context), restored, msg_text, source="webui",
)
return merged, result
def test_restore_display_reasoning_does_not_append_unaligned_rows_past_result_end():
display, context = _compacted_history()
result = copy.deepcopy(context) + [
{"role": "user", "content": "follow-up 1"},
{"role": "assistant", "content": "reply 1"},
]
restored = _restore_display_reasoning_metadata(display, copy.deepcopy(result))
# The anchor's API-safe slot (2) is beyond the compacted result's own
# rows, so nothing may be appended after the new reply.
assert [m.get("content") for m in restored] == [m.get("content") for m in result]
assert not any(_is_clone(m) for m in restored)
def test_settled_turns_do_not_double_recovered_anchors():
display, context = _compacted_history()
assert sum(_is_clone(m) for m in display) == 1
for turn in range(1, 5):
display, context = _settle_turn(display, context, turn)
assert sum(_is_clone(m) for m in display) == 1, (
f"turn {turn}: clone blocks {_clone_blocks(display)}"
)
# Original anchor stays where it was, right after its own reply.
assert _is_clone(display[2])
assert display[-1]["content"] == "reply 4"
def test_reasoning_only_row_still_restored_when_its_successor_is_present():
previous = [
{"role": "user", "content": "old turn", "timestamp": 1},
{"role": "assistant", "content": "", "timestamp": 2, "reasoning": "visible thinking card"},
{"role": "assistant", "content": "old answer", "timestamp": 3},
]
updated = [
{"role": "user", "content": "old turn"},
{"role": "assistant", "content": "old answer"},
{"role": "user", "content": "new turn"},
{"role": "assistant", "content": "new answer"},
]
restored = _restore_display_reasoning_metadata(previous, updated)
assert [m.get("content") for m in restored] == [
"old turn", "", "old answer", "new turn", "new answer",
]
assert restored[1]["reasoning"] == "visible thinking card"
def _repeated_prompt_history(n_anchors: int) -> tuple[list[dict], list[dict]]:
display = [
{"role": "user", "content": "older turn", "timestamp": 1788356100, "id": 1},
{"role": "assistant", "content": "older answer", "timestamp": 1788356200, "id": 2},
*[_recovered_anchor(f"recovered thinking {i}", 1788356394 + i) for i in range(n_anchors)],
{"role": "user", "content": "continue", "timestamp": 1788357000, "id": 3},
{"role": "assistant", "content": "first answer", "timestamp": 1788357010, "id": 4},
]
context = [copy.deepcopy(display[-2]), copy.deepcopy(display[-1])]
return display, context
def _settle_session_turn(session, turn: int, prompt: str, answer: str):
"""Production order: ids minted on result rows, then restore -> merge."""
previous = list(session.messages)
previous_context = list(session.context_messages)
ts = 1788440000 + turn * 10
result = copy.deepcopy(previous_context) + [
{"role": "user", "content": prompt, "timestamp": ts},
{"role": "assistant", "content": answer, "timestamp": ts + 5},
]
_settle_result_messages(session, previous, previous_context, result, prompt, "webui", None)
def test_repeated_prompt_with_distinct_stable_id_does_not_realign_anchors(monkeypatch):
monkeypatch.setattr(_streaming, "_annotate_media_snapshots_for_settled_messages", lambda m: None)
display, context = _repeated_prompt_history(64)
session = Session(session_id="a" * 12, title="t", messages=copy.deepcopy(display))
session.context_messages = copy.deepcopy(context)
assert sum(_is_clone(m) for m in session.messages) == 64
# Guard the fixture: the new "continue" row is a DIFFERENT turn (id 5) that
# is content-identical to the historical successor (id 3).
probe = copy.deepcopy(context) + [
{"role": "user", "content": "continue", "timestamp": 1788440000},
{"role": "assistant", "content": "a different new answer", "timestamp": 1788440005},
]
_assign_stable_message_ids(probe, display, context)
assert (display[-2]["id"], probe[2]["id"]) == (3, 5)
assert _streaming._message_identity(display[-2]) == _streaming._message_identity(probe[2])
for turn in range(1, 4):
_settle_session_turn(session, turn, "continue", f"a different new answer {turn}")
assert sum(_is_clone(m) for m in session.messages) == 64, (
f"turn {turn}: clone blocks {_clone_blocks(session.messages)}"
)
assert session.messages[-1]["content"] == f"a different new answer {turn}"
# Anchors stay in their original block, right after "older answer".
assert _clone_blocks(session.messages) == [64]
assert session.messages[1]["content"] == "older answer" and _is_clone(session.messages[2])
def test_successor_alignment_prefers_stable_ids_over_content():
previous = [
{"role": "user", "content": "older turn", "timestamp": 1, "id": 1},
{"role": "assistant", "content": "older answer", "timestamp": 2, "id": 2},
{"role": "assistant", "content": "", "timestamp": 3, "reasoning": "card"},
{"role": "user", "content": "continue", "timestamp": 4, "id": 3},
{"role": "assistant", "content": "first answer", "timestamp": 5, "id": 4},
]
def restore(prev, successor_id, prompt="continue"):
row = {"role": "user", "content": prompt}
if successor_id is not None:
row["id"] = successor_id
updated = [
{"role": "user", "content": "older turn", "id": 1},
{"role": "assistant", "content": "older answer", "id": 2},
row,
{"role": "assistant", "content": "new answer"},
]
restored = _restore_display_reasoning_metadata(copy.deepcopy(prev), updated)
return [m.get("content") for m in restored]
restored_shape = ["older turn", "older answer", "", "continue", "new answer"]
skipped_shape = ["older turn", "older answer", "continue", "new answer"]
assert restore(previous, 3) == restored_shape
assert restore(previous, 5) == skipped_shape
# Exactly one side carries an id -> rejected, never a content fallback.
# (A workspace-prefixed prompt is identity-equal but escapes id carry-forward.)
prefixed = "[Workspace::v1: /tmp/ws] continue"
assert _streaming._message_identity({"role": "user", "content": prefixed}) == (
_streaming._message_identity(previous[3])
)
assert restore(previous, None, prefixed) == [
"older turn", "older answer", prefixed, "new answer",
]
idless = copy.deepcopy(previous)
del idless[3]["id"]
assert restore(idless, 3) == skipped_shape
# Neither side carries an id -> legacy content identity still restores.
assert restore(idless, None) == restored_shape
_REAL_SIDECAR = os.environ.get("HERMES_WEBUI_REAL_CLONE_SIDECAR", "")
def _assistant_successor_history(n_anchors: int) -> tuple[list[dict], list[dict]]:
"""64 anchors before a historical assistant ``"Done."`` (id 2); compacted context."""
display = [
{"role": "user", "content": "do it", "timestamp": 1788356100, "id": 1},
*[_recovered_anchor(f"recovered thinking {i}", 1788356394 + i) for i in range(n_anchors)],
{"role": "assistant", "content": "Done.", "timestamp": 1788357010, "id": 2},
]
context = [copy.deepcopy(display[0]), copy.deepcopy(display[-1])]
return display, context
def _settle_replacing_result(monkeypatch, display, context, result, prompt):
"""Result that REPLACES the compacted context (does not extend it)."""
monkeypatch.setattr(_streaming, "_annotate_media_snapshots_for_settled_messages", lambda m: None)
session = Session(session_id="d" * 12, title="t", messages=copy.deepcopy(display))
session.context_messages = copy.deepcopy(context)
_settle_result_messages(
session, list(session.messages), list(session.context_messages), result, prompt, "webui", None,
)
return session
def test_historical_assistant_successor_with_same_content_does_not_forge_current_id(monkeypatch):
display, context = _assistant_successor_history(64)
assert sum(_is_clone(m) for m in display) == 64
result = [
{"role": "user", "content": "do it again", "timestamp": 1788440000},
{"role": "assistant", "content": "Done.", "timestamp": 1788440005},
]
session = _settle_replacing_result(monkeypatch, display, context, result, "do it again")
assert sum(_is_clone(m) for m in session.messages) == 64, _clone_blocks(session.messages)
# The distinct current "Done." must NOT inherit the historical successor's id 2.
assert result[-1]["id"] != 2 and _streaming._is_stable_message_id(result[-1]["id"])
assert _clone_blocks(session.messages) == [64]
assert session.messages[-1]["content"] == "Done." and session.messages[-2]["content"] == "do it again"
def test_whole_repeated_user_assistant_pair_does_not_forge_or_double(monkeypatch):
display, context = _assistant_successor_history(64)
result = [
{"role": "user", "content": "do it", "timestamp": 1788440000},
{"role": "assistant", "content": "Done.", "timestamp": 1788440005},
]
session = _settle_replacing_result(monkeypatch, display, context, result, "do it")
assert sum(_is_clone(m) for m in session.messages) == 64, _clone_blocks(session.messages)
assert [m["id"] for m in result] != [1, 2]
assert result[-1]["id"] != 2
assert _clone_blocks(session.messages) == [64]
def test_assistant_only_result_has_boundary_zero_and_inherits_nothing(monkeypatch):
display, context = _assistant_successor_history(4)
result = [{"role": "assistant", "content": "Done.", "timestamp": 1788440005}]
session = _settle_replacing_result(monkeypatch, display, [context[-1]], result, "do it again")
assert result[0]["id"] != 2
assert sum(_is_clone(m) for m in session.messages) == 4
def _current_only_collision_result() -> list[dict]:
"""Compacted CURRENT-only result whose text echoes the old context."""
return [
{"role": "user", "content": "do it", "timestamp": 1788440000},
{"role": "assistant", "content": "Done.", "timestamp": 1788440005},
{"role": "assistant", "content": "Verified.", "timestamp": 1788440010},
]
def test_current_only_collision_without_authority_fails_closed_to_boundary_zero(monkeypatch):
display, context = _assistant_successor_history(64)
result = _current_only_collision_result()
assert _streaming._messages_have_prefix(result, context) # identity ignores ids/ts
assert _streaming._active_turn_boundary(copy.deepcopy(result), context, None, "do it") == 0
session = _settle_replacing_result(monkeypatch, display, context, result, "do it")
assert sum(_is_clone(m) for m in session.messages) == 64, _clone_blocks(session.messages)
assert _clone_blocks(session.messages) == [64]
assert session.messages[-1]["content"] == "Verified."
def test_current_only_collision_with_exact_authority_keeps_one_block(monkeypatch):
display, context = _assistant_successor_history(64)
result = _current_only_collision_result()
identity = _streaming._resolve_active_turn_authority(
{"token": None, "text": "do it", "current_turn_user_idx": None, "turn_id": ""},
result={"messages": result, "current_turn_user_idx": 0, "turn_id": "turn-1"},
)
assert _streaming._active_turn_boundary_is_valid(identity)
assert _streaming._active_turn_boundary(copy.deepcopy(result), context, identity, "do it") == 0
monkeypatch.setattr(_streaming, "_annotate_media_snapshots_for_settled_messages", lambda m: None)
session = Session(session_id="f" * 12, title="t", messages=copy.deepcopy(display))
session.context_messages = copy.deepcopy(context)
_settle_result_messages(
session, list(session.messages), list(session.context_messages), result, "do it", "webui", identity,
)
assert _clone_blocks(session.messages) == [64]
def test_append_only_result_control_keeps_boundary_after_prefix(monkeypatch):
display, context = _assistant_successor_history(64)
result = copy.deepcopy(context) + [
{"role": "user", "content": "do it", "timestamp": 1788440000},
{"role": "assistant", "content": "Verified.", "timestamp": 1788440010},
]
assert _streaming._active_turn_boundary(copy.deepcopy(result), context, None, "do it") == 2
session = _settle_replacing_result(monkeypatch, display, context, result, "do it")
assert _clone_blocks(session.messages) == [64]
assert session.messages[-1]["content"] == "Verified."
def test_prefix_without_current_user_at_or_after_it_fails_closed():
boundary = _streaming._active_turn_boundary
prev = [{"role": "user", "content": "do it"}, {"role": "assistant", "content": "Done."}]
# Historical user matches the prompt, but no current user row follows the prefix.
assert boundary(prev + [{"role": "assistant", "content": "Verified."}], prev, None, "do it") == 0
# A prompt-matching user AFTER the prefix is the current turn.
assert boundary(prev + [{"role": "user", "content": "do it"}], prev, None, "do it") == 2
def test_active_turn_boundary_contract():
boundary = _streaming._active_turn_boundary
prev = [{"role": "user", "content": "a"}, {"role": "assistant", "content": "b"}]
assert boundary([], prev, None, "x") == 0
assert boundary([{"role": "assistant", "content": "Done."}], prev, None, "x") == 0
extended = prev + [{"role": "user", "content": "x"}, {"role": "assistant", "content": "y"}]
assert boundary(extended, prev, None, "x") == 2
replaced = [{"role": "user", "content": "x"}, {"role": "assistant", "content": "y"}]
assert boundary(replaced, prev, None, "x") == 0
tokened = [{"role": "user", "content": "x", "_active_turn_token": "tok"}, {"role": "assistant", "content": "y"}]
assert boundary(prev + tokened, prev, {"token": "tok"}, "x") == 2
def _identity_hole_history(successor_id) -> tuple[list[dict], list[dict]]:
"""Historical successor carries ``successor_id``; anchor sits right before it."""
display = [
{"role": "user", "content": "older turn", "timestamp": 1},
{"role": "assistant", "content": "older answer", "timestamp": 2},
{"role": "assistant", "content": "", "timestamp": 3, "reasoning": "historical card"},
{"role": "user", "content": "continue", "timestamp": 4, "id": successor_id},
{"role": "assistant", "content": "first answer", "timestamp": 5},
]
context = [copy.deepcopy(display[-2]), copy.deepcopy(display[-1])]
return display, context
def _reasoning_cards(messages) -> list:
return [m.get("reasoning") for m in messages]
def _settled_session(monkeypatch, display, context, result_ids=()):
monkeypatch.setattr(_streaming, "_annotate_media_snapshots_for_settled_messages", lambda m: None)
session = Session(session_id="b" * 12, title="t", messages=copy.deepcopy(display))
session.context_messages = copy.deepcopy(context)
result = copy.deepcopy(context) + [
{"role": "user", "content": "continue", "timestamp": 10},
{"role": "assistant", "content": "new answer", "timestamp": 15},
]
for row, rid in zip(result, result_ids, strict=False):
if rid is not None:
row["id"] = rid
_settle_result_messages(
session, list(session.messages), list(session.context_messages), result, "continue", "webui", None,
)
return session, result
# The new "continue" is a distinct turn: exactly one historical card may survive.
_ONE_CARD = [None, None, "historical card", None, None, None, None]
def test_bool_successor_id_never_matches_integer_one(monkeypatch):
display, context = _identity_hole_history(True)
session, result = _settled_session(monkeypatch, display, context, result_ids=(None, None, 1, 2))
assert True == 1 and result[2]["id"] == 1 # noqa: E712 - the hole being closed
assert _reasoning_cards(session.messages) == _ONE_CARD
assert all(type(m["id"]) is int for m in result) # replayed True row re-minted
def test_bool_successor_id_never_matches_minted_integer_one(monkeypatch):
display, context = _identity_hole_history(True)
session, result = _settled_session(monkeypatch, display, context)
assert _reasoning_cards(session.messages) == _ONE_CARD
assert all(type(m["id"]) is int for m in result)
def test_float_successor_id_never_matches_integer_one(monkeypatch):
display, context = _identity_hole_history(1.0)
session, result = _settled_session(monkeypatch, display, context, result_ids=(None, None, 1, 2))
assert 1.0 == 1 and result[2]["id"] == 1
assert _reasoning_cards(session.messages) == _ONE_CARD
assert all(type(m["id"]) is int for m in result)
def test_reused_positive_id_cannot_establish_successor_ownership(monkeypatch):
display, context = _identity_hole_history(7)
display[0]["id"] = 7 # the same id is owned by two API-safe historical rows
session, result = _settled_session(monkeypatch, display, context, result_ids=(7, 8, 7, 9))
assert [m["id"] for m in result] == [7, 8, 7, 9]
assert _reasoning_cards(session.messages) == _ONE_CARD
def test_unique_stable_id_control_still_restores_before_its_own_successor(monkeypatch):
display, context = _identity_hole_history(3)
display[0]["id"], display[1]["id"], display[4]["id"] = 1, 2, 4
context[1]["id"] = 4
session, result = _settled_session(monkeypatch, display, context)
assert [m["id"] for m in result] == [3, 4, 5, 6]
assert _reasoning_cards(session.messages) == _ONE_CARD
assert session.messages[2]["reasoning"] == "historical card"
def test_bool_ids_do_not_survive_save_load_into_minted_collision(tmp_path, monkeypatch):
from api import models, run_journal
sessions = tmp_path / "sessions"
sessions.mkdir()
monkeypatch.setattr(run_journal, "_default_session_dir", lambda: sessions)
monkeypatch.setattr(models, "SESSION_DIR", sessions)
monkeypatch.setattr(models, "SESSION_INDEX_FILE", sessions / "_index.json")
monkeypatch.setattr(models, "SESSIONS", type(models.SESSIONS)())
display, context = _identity_hole_history(True)
persisted = Session(session_id="c" * 12, title="t", messages=copy.deepcopy(display))
persisted.context_messages = copy.deepcopy(context)
persisted.save()
loaded = Session.load("c" * 12)
assert loaded is not None and loaded.messages[3]["id"] is True # survives save/load
session, result = _settled_session(
monkeypatch, loaded.messages, loaded.context_messages, result_ids=(None, None, 1, 2),
)
assert result[2]["id"] == 1 and _reasoning_cards(session.messages) == _ONE_CARD
session.save()
reloaded = Session.load("b" * 12)
assert reloaded is not None
assert _reasoning_cards(reloaded.messages) == _ONE_CARD
assert [m["id"] for m in reloaded.messages[-2:]] == [1, 2]
assert all(type(m["id"]) is int for m in result) # replayed True row re-minted
def _isolated_session_store(tmp_path, monkeypatch):
from api import models, run_journal
sessions = tmp_path / "sessions"
sessions.mkdir()
monkeypatch.setattr(run_journal, "_default_session_dir", lambda: sessions)
monkeypatch.setattr(models, "SESSION_DIR", sessions)
monkeypatch.setattr(models, "SESSION_INDEX_FILE", sessions / "_index.json")
monkeypatch.setattr(models, "SESSIONS", type(models.SESSIONS)())
return sessions
def test_idless_successor_card_survives_settle_save_load(tmp_path, monkeypatch):
"""Persisted-display pin: the helper-level id skip never drops the card."""
_isolated_session_store(tmp_path, monkeypatch)
display, context = _identity_hole_history(None)
del display[3]["id"] # legacy transcript: successor predates stable ids
context = [copy.deepcopy(display[-2]), copy.deepcopy(display[-1])]
persisted = Session(session_id="e" * 12, title="t", messages=copy.deepcopy(display))
persisted.context_messages = copy.deepcopy(context)
persisted.save()
loaded = Session.load("e" * 12)
assert loaded is not None and "id" not in loaded.messages[3]
session, result = _settled_session(monkeypatch, loaded.messages, loaded.context_messages)
assert result[0]["id"] is not None # minted before restore: helper-level skip applies
session.save()
reloaded = Session.load("b" * 12)
assert reloaded is not None
assert _reasoning_cards(reloaded.messages) == _ONE_CARD
assert reloaded.messages[2]["reasoning"] == "historical card"
assert [m["content"] for m in reloaded.messages[-2:]] == ["continue", "new answer"]
class _FakePostHandler:
def __init__(self):
self.status, self.headers, self.body, self.wfile = None, {}, bytearray(), self
def send_response(self, status):
self.status = status
def send_header(self, name, value):
self.headers[name] = value
def end_headers(self):
pass
def write(self, data):
self.body.extend(data)
def test_handle_chat_sync_passes_result_turn_authority_to_settlement(tmp_path, monkeypatch):
import sys
from types import SimpleNamespace
from api import models, routes
_isolated_session_store(tmp_path, monkeypatch)
monkeypatch.setattr(routes, "SESSION_INDEX_FILE", models.SESSION_INDEX_FILE)
monkeypatch.setattr(routes, "get_session", models.get_session)
monkeypatch.setattr(routes, "title_from", models.title_from)
monkeypatch.setattr(routes, "get_config", lambda: {"model": "m", "provider": "p"})
monkeypatch.setattr(routes, "resolve_trusted_workspace", lambda value, **_kw: tmp_path)
monkeypatch.setattr(routes, "load_settings", lambda: {})
monkeypatch.setattr(routes, "_resolve_cli_toolsets", lambda: [])
monkeypatch.setattr(routes, "_agent_runtime_barrier_response", lambda **_k: None)
monkeypatch.setattr(_streaming, "_annotate_media_snapshots_for_settled_messages", lambda m: None)
display, context = _assistant_successor_history(4)
session = Session(
session_id="g" * 12, workspace=str(tmp_path), messages=copy.deepcopy(display),
context_messages=copy.deepcopy(context), model="m", model_provider="p",
)
session.save(touch_updated_at=False)
# Whole history re-sent as CURRENT rows; the Agent says the turn starts at 0.
result_rows = copy.deepcopy(context) + [
{"role": "user", "content": "do it"}, {"role": "assistant", "content": "Verified."},
]
for row in result_rows:
row.pop("id", None)
row.pop("timestamp", None)
class FakeAgent:
def __init__(self, **_kwargs):
pass
def run_conversation(self, **_kwargs):
return {
"messages": result_rows, "final_response": "Verified.", "completed": True,
"current_turn_user_idx": 0, "turn_id": "turn-sync-1",
}
monkeypatch.setitem(sys.modules, "run_agent", SimpleNamespace(AIAgent=FakeAgent))
handler = _FakePostHandler()
routes._handle_chat_sync(
handler, {"session_id": "g" * 12, "message": "do it", "workspace": str(tmp_path)},
)
assert handler.status == 200
# Boundary 0 from exact authority: no historical id (1/2) forged onto current rows.
assert result_rows[0]["id"] != 1 and result_rows[1]["id"] != 2
reloaded = Session.load("g" * 12)
assert reloaded is not None
assert _clone_blocks(reloaded.messages) == [4]
assert reloaded.messages[-1]["content"] == "Verified."
@pytest.mark.skipif(
not _REAL_SIDECAR or not pathlib.Path(_REAL_SIDECAR).is_file(),
reason="set HERMES_WEBUI_REAL_CLONE_SIDECAR to a captured sidecar to run",
)
def test_real_captured_sidecar_does_not_double_per_settled_turn():
data = json.loads(pathlib.Path(_REAL_SIDECAR).read_text())
display = copy.deepcopy(data["messages"])
context = copy.deepcopy(data.get("context_messages") or [])
before = sum(_is_clone(m) for m in display)
assert before > 0
for turn in range(1, 3):
display, context = _settle_turn(display, context, turn)
assert sum(_is_clone(m) for m in display) == before, _clone_blocks(display)