"""TDD — agent_model must reflect the most recently used model, not a frozen genesis value (musehub#132). Covers: AM_01 latest_agent_model_id returns the newest commit's model_id AM_02 latest_agent_model_id returns None when the agent has no commits AM_03 _resolve_identity (registered MusehubIdentity row) returns the live model, not the stale cached identity.agent_model column AM_04 _resolve_identity (registered row, agent has no commits yet) falls back to the stored identity.agent_model AM_05 _resolve_identity (derived agent, no identity row) returns the newest commit's model, not the oldest AM_06 build_profile_manifest returns the live model for agent identities """ from __future__ import annotations from datetime import datetime, timedelta, timezone import pytest from sqlalchemy.ext.asyncio import AsyncSession from musehub.db.musehub_identity_models import MusehubIdentity from musehub.db.musehub_repo_models import MusehubCommit from muse.core.types import blob_id, long_id def _utc(offset_days: int = 0) -> datetime: return datetime.now(tz=timezone.utc) + timedelta(days=offset_days) async def _add_commit( session: AsyncSession, seed: str, *, agent_id: str, model_id: str, timestamp: datetime, ) -> None: session.add(MusehubCommit( commit_id=blob_id(f"commit-{seed}".encode()), branch="dev", parent_ids=[], message=f"feat: {seed}", author="gabriel", timestamp=timestamp, agent_id=agent_id, model_id=model_id, )) await session.commit() # --------------------------------------------------------------------------- # latest_agent_model_id # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_latest_agent_model_id_returns_newest_commit(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import latest_agent_model_id await _add_commit(db_session, "old", agent_id="claude-code", model_id="claude-sonnet-4-6", timestamp=_utc(-10)) await _add_commit(db_session, "new", agent_id="claude-code", model_id="claude-sonnet-5", timestamp=_utc(-1)) result = await latest_agent_model_id(db_session, "claude-code") assert result == "claude-sonnet-5" @pytest.mark.asyncio async def test_latest_agent_model_id_returns_none_without_commits(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import latest_agent_model_id result = await latest_agent_model_id(db_session, "never-committed-agent") assert result is None # --------------------------------------------------------------------------- # _resolve_identity — registered MusehubIdentity row (branch 1) # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_resolve_identity_registered_agent_uses_live_model(db_session: AsyncSession) -> None: from musehub.api.routes.musehub.ui_user_profile import _resolve_identity db_session.add(MusehubIdentity( identity_id=long_id("a" * 64), handle="claude-code", identity_type="agent", agent_model="claude-sonnet-4-6", # stale seed from first-ever provisioning created_at=_utc(-30), )) await db_session.commit() await _add_commit(db_session, "old", agent_id="claude-code", model_id="claude-sonnet-4-6", timestamp=_utc(-10)) await _add_commit(db_session, "new", agent_id="claude-code", model_id="claude-sonnet-5", timestamp=_utc(-1)) identity = await _resolve_identity(db_session, "claude-code") assert identity is not None assert identity["agent_model"] == "claude-sonnet-5" @pytest.mark.asyncio async def test_resolve_identity_registered_agent_falls_back_when_no_commits(db_session: AsyncSession) -> None: from musehub.api.routes.musehub.ui_user_profile import _resolve_identity db_session.add(MusehubIdentity( identity_id=long_id("b" * 64), handle="fresh-agent", identity_type="agent", agent_model="claude-sonnet-5", created_at=_utc(-1), )) await db_session.commit() identity = await _resolve_identity(db_session, "fresh-agent") assert identity is not None assert identity["agent_model"] == "claude-sonnet-5" @pytest.mark.asyncio async def test_resolve_identity_human_unaffected(db_session: AsyncSession) -> None: """Live-model recomputation must only apply to agent identities.""" from musehub.api.routes.musehub.ui_user_profile import _resolve_identity db_session.add(MusehubIdentity( identity_id=long_id("c" * 64), handle="gabriel", identity_type="human", created_at=_utc(-30), )) await db_session.commit() identity = await _resolve_identity(db_session, "gabriel") assert identity is not None assert identity["agent_model"] is None # --------------------------------------------------------------------------- # _resolve_identity — derived agent, no MusehubIdentity row (branch 4) # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_resolve_identity_derived_agent_uses_newest_commit(db_session: AsyncSession) -> None: from musehub.api.routes.musehub.ui_user_profile import _resolve_identity await _add_commit(db_session, "old", agent_id="mix-engine-7", model_id="claude-opus-4-6", timestamp=_utc(-10)) await _add_commit(db_session, "new", agent_id="mix-engine-7", model_id="claude-opus-5", timestamp=_utc(-1)) identity = await _resolve_identity(db_session, "mix-engine-7") assert identity is not None assert identity["is_derived"] is True assert identity["agent_model"] == "claude-opus-5" # member_since must still reflect the earliest commit, not the latest assert identity["member_since"] == _utc(-10).replace(microsecond=identity["member_since"].microsecond) # --------------------------------------------------------------------------- # build_profile_manifest — agent identities # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_build_profile_manifest_uses_live_model_for_agent(db_session: AsyncSession) -> None: from musehub.services.musehub_profile import build_profile_manifest db_session.add(MusehubIdentity( identity_id=long_id("d" * 64), handle="claude-code", identity_type="agent", agent_model="claude-sonnet-4-6", created_at=_utc(-30), )) await db_session.commit() await _add_commit(db_session, "old", agent_id="claude-code", model_id="claude-sonnet-4-6", timestamp=_utc(-10)) await _add_commit(db_session, "new", agent_id="claude-code", model_id="claude-sonnet-5", timestamp=_utc(-1)) manifest = await build_profile_manifest(db_session, "claude-code") assert manifest is not None assert manifest.agent_model == "claude-sonnet-5"