"""TDD tests for derived-agent auto-provisioning. When a profile page is visited for an agent that only exists as an `agent_id` string in commit metadata (never formally registered), we: 1. Compute a deterministic genesis identity_id from the handle. 2. Upsert a real `musehub_identities` row (idempotent). 3. Return the real identity_id so the Spectral Sigil route can serve the SVG. Tests are RED-first. Each assertion drives one concrete implementation decision. New symbols: musehub.core.genesis.compute_derived_agent_id musehub.services.derived_agent_provisioner.ensure_agent_identity musehub.services.derived_agent_provisioner.provision_if_derived """ from __future__ import annotations import re from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from muse.core.types import blob_id, long_id from musehub.main import app # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _SONNET_HANDLE = "claude-sonnet-4-6" _OPUS_HANDLE = "claude-opus-4-7" _CUSTOM_HANDLE = "my-custom-agent-42" _KNOWN_FIRST_SEEN = datetime(2026, 1, 31, 12, 0, 0, tzinfo=timezone.utc) def _expected_id(handle: str) -> str: """Restate the genesis formula so tests catch any deviation in implementation.""" return blob_id(f"agent_handle\x00{handle}".encode()) # --------------------------------------------------------------------------- # Module imports — must exist # --------------------------------------------------------------------------- def test_genesis_module_exports_compute_derived_agent_id() -> None: from musehub.core.genesis import compute_derived_agent_id # noqa: F401 assert callable(compute_derived_agent_id) def test_provisioner_module_importable() -> None: from musehub.services import derived_agent_provisioner # noqa: F401 def test_ensure_agent_identity_importable() -> None: from musehub.services.derived_agent_provisioner import ensure_agent_identity # noqa: F401 assert callable(ensure_agent_identity) def test_provision_if_derived_importable() -> None: from musehub.services.derived_agent_provisioner import provision_if_derived # noqa: F401 assert callable(provision_if_derived) # --------------------------------------------------------------------------- # compute_derived_agent_id — deterministic genesis formula # --------------------------------------------------------------------------- def test_compute_derived_agent_id_returns_sha256_prefixed() -> None: from musehub.core.genesis import compute_derived_agent_id result = compute_derived_agent_id(_SONNET_HANDLE) assert result.startswith("sha256:") def test_compute_derived_agent_id_hex_part_is_64_chars() -> None: from musehub.core.genesis import compute_derived_agent_id result = compute_derived_agent_id(_SONNET_HANDLE) hex_part = result[len("sha256:"):] assert len(hex_part) == 64 assert re.fullmatch(r"[0-9a-f]{64}", hex_part) def test_compute_derived_agent_id_is_deterministic() -> None: from musehub.core.genesis import compute_derived_agent_id assert compute_derived_agent_id(_SONNET_HANDLE) == compute_derived_agent_id(_SONNET_HANDLE) def test_compute_derived_agent_id_differs_by_handle() -> None: from musehub.core.genesis import compute_derived_agent_id assert compute_derived_agent_id(_SONNET_HANDLE) != compute_derived_agent_id(_OPUS_HANDLE) assert compute_derived_agent_id(_OPUS_HANDLE) != compute_derived_agent_id(_CUSTOM_HANDLE) def test_compute_derived_agent_id_matches_known_formula() -> None: """Nail the exact formula so any future refactor breaks loudly.""" from musehub.core.genesis import compute_derived_agent_id assert compute_derived_agent_id(_SONNET_HANDLE) == _expected_id(_SONNET_HANDLE) assert compute_derived_agent_id(_OPUS_HANDLE) == _expected_id(_OPUS_HANDLE) assert compute_derived_agent_id(_CUSTOM_HANDLE) == _expected_id(_CUSTOM_HANDLE) # --------------------------------------------------------------------------- # ensure_agent_identity — upsert into musehub_identities # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_ensure_agent_identity_creates_row(db_session: AsyncSession) -> None: from musehub.db.musehub_identity_models import MusehubIdentity from musehub.services.derived_agent_provisioner import ensure_agent_identity result = await ensure_agent_identity( db_session, handle=_SONNET_HANDLE, agent_model="claude-sonnet-4-6", first_seen_at=_KNOWN_FIRST_SEEN, ) await db_session.commit() row = (await db_session.execute( select(MusehubIdentity).where(MusehubIdentity.handle == _SONNET_HANDLE) )).scalar_one_or_none() assert row is not None assert result.handle == _SONNET_HANDLE @pytest.mark.asyncio async def test_ensure_agent_identity_sets_correct_identity_id(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import ensure_agent_identity result = await ensure_agent_identity( db_session, handle=_SONNET_HANDLE, agent_model="claude-sonnet-4-6", first_seen_at=_KNOWN_FIRST_SEEN, ) assert result.identity_id == _expected_id(_SONNET_HANDLE) @pytest.mark.asyncio async def test_ensure_agent_identity_sets_identity_type_agent(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import ensure_agent_identity result = await ensure_agent_identity( db_session, handle=_SONNET_HANDLE, agent_model="claude-sonnet-4-6", first_seen_at=_KNOWN_FIRST_SEEN, ) assert result.identity_type == "agent" @pytest.mark.asyncio async def test_ensure_agent_identity_stores_agent_model(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import ensure_agent_identity result = await ensure_agent_identity( db_session, handle=_SONNET_HANDLE, agent_model="claude-sonnet-4-6", first_seen_at=_KNOWN_FIRST_SEEN, ) assert result.agent_model == "claude-sonnet-4-6" @pytest.mark.asyncio async def test_ensure_agent_identity_model_none_is_accepted(db_session: AsyncSession) -> None: from musehub.services.derived_agent_provisioner import ensure_agent_identity result = await ensure_agent_identity( db_session, handle=_CUSTOM_HANDLE, agent_model=None, first_seen_at=None, ) assert result.identity_type == "agent" assert result.agent_model is None @pytest.mark.asyncio async def test_ensure_agent_identity_is_idempotent(db_session: AsyncSession) -> None: """Calling twice with the same handle must not raise and must not duplicate.""" from musehub.db.musehub_identity_models import MusehubIdentity from musehub.services.derived_agent_provisioner import ensure_agent_identity await ensure_agent_identity(db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN) await db_session.commit() await ensure_agent_identity(db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN) await db_session.commit() rows = (await db_session.execute( select(MusehubIdentity).where(MusehubIdentity.handle == _SONNET_HANDLE) )).scalars().all() assert len(rows) == 1 @pytest.mark.asyncio async def test_ensure_agent_identity_returns_existing_row_unchanged(db_session: AsyncSession) -> None: """If the row already exists, return it without touching model or timestamps.""" from musehub.db.musehub_identity_models import MusehubIdentity from musehub.services.derived_agent_provisioner import ensure_agent_identity first = await ensure_agent_identity(db_session, _SONNET_HANDLE, "v1-model", _KNOWN_FIRST_SEEN) await db_session.commit() original_id = first.identity_id second = await ensure_agent_identity(db_session, _SONNET_HANDLE, "v2-model-different", None) assert second.identity_id == original_id @pytest.mark.asyncio async def test_ensure_agent_identity_different_handles_create_separate_rows(db_session: AsyncSession) -> None: from musehub.db.musehub_identity_models import MusehubIdentity from musehub.services.derived_agent_provisioner import ensure_agent_identity await ensure_agent_identity(db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN) await ensure_agent_identity(db_session, _OPUS_HANDLE, "claude-opus-4-7", _KNOWN_FIRST_SEEN) await db_session.commit() rows = (await db_session.execute( select(MusehubIdentity).where( MusehubIdentity.handle.in_([_SONNET_HANDLE, _OPUS_HANDLE]) ) )).scalars().all() assert len(rows) == 2 ids = {r.identity_id for r in rows} assert len(ids) == 2 # distinct genesis IDs # --------------------------------------------------------------------------- # provision_if_derived — the _resolve_identity integration hook # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_provision_if_derived_provisions_derived_agent(db_session: AsyncSession) -> None: """Given a derived identity dict, provision_if_derived upserts and updates user_id.""" from musehub.db.musehub_identity_models import MusehubIdentity from musehub.services.derived_agent_provisioner import provision_if_derived derived_identity = { "handle": _SONNET_HANDLE, "type": "agent", "user_id": None, "agent_model": "claude-sonnet-4-6", "is_derived": True, "member_since": _KNOWN_FIRST_SEEN, } updated = await provision_if_derived(db_session, derived_identity) await db_session.commit() assert updated["user_id"] == _expected_id(_SONNET_HANDLE) assert updated["is_derived"] is False row = (await db_session.execute( select(MusehubIdentity).where(MusehubIdentity.handle == _SONNET_HANDLE) )).scalar_one_or_none() assert row is not None @pytest.mark.asyncio async def test_provision_if_derived_skips_non_agent(db_session: AsyncSession) -> None: """Humans and orgs are not auto-provisioned.""" from musehub.services.derived_agent_provisioner import provision_if_derived human_identity = { "handle": "gabriel", "type": "human", "user_id": None, "is_derived": True, "member_since": _KNOWN_FIRST_SEEN, } updated = await provision_if_derived(db_session, human_identity) assert updated["user_id"] is None assert updated["is_derived"] is True @pytest.mark.asyncio async def test_provision_if_derived_skips_already_registered(db_session: AsyncSession) -> None: """If user_id is already set (registered agent), don't touch it.""" from musehub.services.derived_agent_provisioner import provision_if_derived existing_id = long_id("a" * 64) registered = { "handle": _SONNET_HANDLE, "type": "agent", "user_id": existing_id, "is_derived": False, "member_since": _KNOWN_FIRST_SEEN, } updated = await provision_if_derived(db_session, registered) assert updated["user_id"] == existing_id # --------------------------------------------------------------------------- # Avatar route — sigil is served after provisioning # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_avatar_route_serves_sigil_for_provisioned_agent(db_session: AsyncSession) -> None: """After ensure_agent_identity, the avatar route returns 200 image/svg+xml.""" from musehub.services.derived_agent_provisioner import ensure_agent_identity identity = await ensure_agent_identity( db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN ) await db_session.commit() hex_part = identity.identity_id[len("sha256:"):] async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get(f"/avatars/sha256/{hex_part}.svg") assert response.status_code == 200 assert "image/svg+xml" in response.headers["content-type"] @pytest.mark.asyncio async def test_avatar_route_sigil_reflects_agent_archetype(db_session: AsyncSession) -> None: """The generated SVG must use the agent archetype (polygon/path for hex shape).""" import xml.etree.ElementTree as ET from musehub.services.derived_agent_provisioner import ensure_agent_identity identity = await ensure_agent_identity( db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN ) await db_session.commit() hex_part = identity.identity_id[len("sha256:"):] async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get(f"/avatars/sha256/{hex_part}.svg") root = ET.fromstring(response.content) paths = root.findall(".//{http://www.w3.org/2000/svg}path") or root.findall(".//path") polygons = root.findall(".//{http://www.w3.org/2000/svg}polygon") or root.findall(".//polygon") assert len(paths) + len(polygons) >= 1, "agent sigil must contain path or polygon" @pytest.mark.asyncio async def test_avatar_sigil_is_deterministic_for_provisioned_agent(db_session: AsyncSession) -> None: """Two requests for the same provisioned agent return identical SVG bytes.""" from musehub.services.derived_agent_provisioner import ensure_agent_identity identity = await ensure_agent_identity( db_session, _SONNET_HANDLE, "claude-sonnet-4-6", _KNOWN_FIRST_SEEN ) await db_session.commit() hex_part = identity.identity_id[len("sha256:"):] async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: r1 = await client.get(f"/avatars/sha256/{hex_part}.svg") r2 = await client.get(f"/avatars/sha256/{hex_part}.svg") assert r1.content == r2.content