112 lines
4.4 KiB
Python
112 lines
4.4 KiB
Python
"""The canonical Bot Chat's title is its identity — renames must be refused.
|
|
|
|
Bot Mode resolves a bot's forever-chat by exact-title lookup on
|
|
(profile, "Bot Chat") every time it opens; there is no session-id pointer.
|
|
A user rename therefore orphans the whole conversation: resolution misses,
|
|
the next click mints an empty replacement, and UNIQUE(title) then blocks
|
|
renaming the original back (#92473).
|
|
|
|
The guard lives in SessionDB._set_session_title — the single write path
|
|
every rename surface funnels through (gateway session.title RPC, /title,
|
|
CLI rename, REST) — and keys on hidden + exact canonical title so ordinary
|
|
sessions a user happens to call "Bot Chat" stay freely renameable.
|
|
"""
|
|
import pytest
|
|
|
|
from hermes_state import SessionDB
|
|
|
|
|
|
@pytest.fixture
|
|
def db(tmp_path):
|
|
return SessionDB(tmp_path / "state.db")
|
|
|
|
|
|
def _make_canonical(db, session_id="forever"):
|
|
db.create_session(session_id, source="desktop")
|
|
assert db.set_session_title(session_id, SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
assert db.set_session_hidden(session_id, True)
|
|
return session_id
|
|
|
|
|
|
def test_user_rename_of_canonical_bot_chat_is_refused(db):
|
|
sid = _make_canonical(db)
|
|
with pytest.raises(ValueError, match="canonical Bot Chat"):
|
|
db.set_session_title(sid, "My cool chat")
|
|
# Identity intact: exact-title lookup still finds the forever chat.
|
|
row = db.get_session_by_title(SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
assert row and row["id"] == sid
|
|
|
|
|
|
def test_clearing_the_canonical_title_is_refused(db):
|
|
sid = _make_canonical(db)
|
|
with pytest.raises(ValueError, match="canonical Bot Chat"):
|
|
db.set_session_title(sid, "")
|
|
row = db.get_session_by_title(SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
assert row and row["id"] == sid
|
|
|
|
|
|
def test_rewriting_the_same_canonical_title_is_a_noop_not_an_error(db):
|
|
# The plugin's eager session.title write re-asserts the canonical title
|
|
# on creation paths; that must never start failing.
|
|
sid = _make_canonical(db)
|
|
assert db.set_session_title(sid, SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
|
|
|
|
def test_visible_session_titled_bot_chat_stays_renameable(db):
|
|
# hidden discriminates the registry row: a normal visible session the
|
|
# user happened to name "Bot Chat" is not canonical and renames freely.
|
|
db.create_session("ordinary", source="cli")
|
|
assert db.set_session_title("ordinary", SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
assert db.set_session_title("ordinary", "renamed away")
|
|
assert db.get_session("ordinary")["title"] == "renamed away"
|
|
|
|
|
|
def test_auto_titler_still_cannot_touch_the_canonical_row(db):
|
|
# Pre-existing provenance contract, re-pinned here: user-authority title
|
|
# outranks derived/llm, so the turn-start auto-titler can never displace
|
|
# the registry name.
|
|
sid = _make_canonical(db)
|
|
assert not db.set_auto_title(sid, "Chat about groceries", source=SessionDB.TITLE_SOURCE_LLM)
|
|
row = db.get_session_by_title(SessionDB.CANONICAL_BOT_CHAT_TITLE)
|
|
assert row and row["id"] == sid
|
|
|
|
|
|
def test_auto_titler_cannot_rename_derived_canonical_bot_chat(db):
|
|
# #99517: the guard must be provenance-blind. A derived (rank 0) canonical
|
|
# title loses to an llm (rank 1) auto-title on precedence alone, so the
|
|
# identity check — not precedence — has to stop the write.
|
|
db.create_session("derived", source="desktop")
|
|
assert db._set_session_title(
|
|
"derived",
|
|
SessionDB.CANONICAL_BOT_CHAT_TITLE,
|
|
source=SessionDB.TITLE_SOURCE_DERIVED,
|
|
)
|
|
assert db.set_session_hidden("derived", True)
|
|
|
|
assert not db.set_auto_title(
|
|
"derived",
|
|
"Renamed by titler",
|
|
source=SessionDB.TITLE_SOURCE_LLM,
|
|
)
|
|
row = db.get_session("derived")
|
|
assert row["title"] == SessionDB.CANONICAL_BOT_CHAT_TITLE
|
|
assert row["title_source"] == SessionDB.TITLE_SOURCE_DERIVED
|
|
|
|
|
|
def test_auto_titler_can_rename_visible_derived_bot_chat(db):
|
|
# Control: hidden is still the discriminator — a visible session that
|
|
# merely carries the text "Bot Chat" upgrades derived -> llm as usual.
|
|
db.create_session("visible", source="desktop")
|
|
assert db._set_session_title(
|
|
"visible",
|
|
SessionDB.CANONICAL_BOT_CHAT_TITLE,
|
|
source=SessionDB.TITLE_SOURCE_DERIVED,
|
|
)
|
|
|
|
assert db.set_auto_title(
|
|
"visible",
|
|
"Renamed by titler",
|
|
source=SessionDB.TITLE_SOURCE_LLM,
|
|
)
|
|
assert db.get_session("visible")["title"] == "Renamed by titler"
|