Migrate a type across a multi-module Python package, updating all consumers in dependency order.

Setup: Create the project structure.

/tmp/type_migration/core/__init__.py: (empty)
/tmp/type_migration/core/types.py:
```python
# Current: UserId is just a string alias
UserId = str

def create_user_id(value: str) -> UserId:
    return value

def format_user_id(uid: UserId) -> str:
    return f"user:{uid}"
```

/tmp/type_migration/services/__init__.py: (empty)
/tmp/type_migration/services/auth.py:
```python
from core.types import UserId, create_user_id

# Simulated user database
_users = {
    "alice": "password123",
    "bob": "secret456",
}
_sessions = {}

def authenticate(username: str, password: str) -> UserId | None:
    if _users.get(username) == password:
        uid = create_user_id(username)
        _sessions[uid] = True
        return uid
    return None

def is_authenticated(uid: UserId) -> bool:
    return _sessions.get(uid, False)

def get_user_display(uid: UserId) -> str:
    return f"User<{uid}>"
```

/tmp/type_migration/services/notifications.py:
```python
from core.types import UserId, format_user_id

_notification_log = []

def send_notification(uid: UserId, message: str) -> dict:
    entry = {
        "recipient": uid,
        "formatted": format_user_id(uid),
        "message": message,
        "delivered": True,
    }
    _notification_log.append(entry)
    return entry

def get_notifications_for(uid: UserId) -> list[dict]:
    return [n for n in _notification_log if n["recipient"] == uid]

def notification_count(uid: UserId) -> int:
    return len(get_notifications_for(uid))
```

/tmp/type_migration/handlers/__init__.py: (empty)
/tmp/type_migration/handlers/user_handler.py:
```python
from core.types import UserId
from services.auth import authenticate, is_authenticated, get_user_display
from services.notifications import send_notification

def handle_login(username: str, password: str) -> dict:
    uid = authenticate(username, password)
    if uid is None:
        return {"success": False, "error": "Invalid credentials"}
    send_notification(uid, "Login successful")
    return {"success": True, "user": uid, "display": get_user_display(uid)}

def handle_action(uid: UserId, action: str) -> dict:
    if not is_authenticated(uid):
        return {"error": "Not authenticated"}
    send_notification(uid, f"Action performed: {action}")
    return {"user": uid, "action": action, "status": "completed"}
```

/tmp/type_migration/handlers/admin_handler.py:
```python
from core.types import UserId, format_user_id
from services.auth import is_authenticated
from services.notifications import send_notification, notification_count

ADMIN_USERS = ["alice"]

def is_admin(uid: UserId) -> bool:
    return uid in ADMIN_USERS

def admin_action(uid: UserId, target_uid: UserId, action: str) -> dict:
    if not is_authenticated(uid):
        return {"error": "Not authenticated"}
    if not is_admin(uid):
        return {"error": "Not an admin"}
    send_notification(target_uid, f"Admin action by {format_user_id(uid)}: {action}")
    return {
        "admin": uid,
        "target": target_uid,
        "action": action,
        "target_notifications": notification_count(target_uid),
    }
```

/tmp/type_migration/main.py:
```python
import sys
sys.path.insert(0, '/tmp/type_migration')

from handlers.user_handler import handle_login, handle_action
from handlers.admin_handler import admin_action

def run_demo():
    # Login
    result = handle_login("alice", "password123")
    assert result["success"], f"Login failed: {result}"
    alice_uid = result["user"]
    print(f"Alice logged in: {alice_uid}")

    result = handle_login("bob", "secret456")
    assert result["success"], f"Login failed: {result}"
    bob_uid = result["user"]
    print(f"Bob logged in: {bob_uid}")

    # User action
    action_result = handle_action(alice_uid, "view_dashboard")
    print(f"Action result: {action_result}")

    # Admin action
    admin_result = admin_action(alice_uid, bob_uid, "reset_password")
    print(f"Admin result: {admin_result}")

    print("\nAll operations completed successfully!")

if __name__ == "__main__":
    run_demo()
```

/tmp/type_migration/tests/__init__.py: (empty)
/tmp/type_migration/tests/test_all.py:
```python
import sys
sys.path.insert(0, '/tmp/type_migration')

from core.types import UserId, create_user_id, format_user_id

def test_userid_is_dataclass():
    """UserId must be a dataclass, not a str alias."""
    uid = create_user_id("test")
    assert not isinstance(uid, str), "UserId should not be a plain string"
    assert hasattr(uid, 'value'), "UserId should have a 'value' attribute"

def test_create_user_id():
    uid = create_user_id("alice")
    assert uid.value == "alice"

def test_format_user_id():
    uid = create_user_id("alice")
    result = format_user_id(uid)
    assert result == "user:alice"

def test_userid_equality():
    uid1 = create_user_id("alice")
    uid2 = create_user_id("alice")
    assert uid1 == uid2

def test_userid_not_equal_to_string():
    uid = create_user_id("alice")
    assert uid != "alice", "UserId should not equal a plain string"

def test_userid_hashable():
    uid = create_user_id("alice")
    d = {uid: "test"}
    assert d[uid] == "test"

def test_full_workflow():
    """Test the complete login -> action -> admin workflow."""
    from handlers.user_handler import handle_login, handle_action
    from handlers.admin_handler import admin_action

    result = handle_login("alice", "password123")
    assert result["success"]
    alice_uid = result["user"]
    assert not isinstance(alice_uid, str), "Login should return UserId dataclass"

    result = handle_login("bob", "secret456")
    assert result["success"]
    bob_uid = result["user"]

    action_result = handle_action(alice_uid, "test")
    assert action_result["status"] == "completed"

    admin_result = admin_action(alice_uid, bob_uid, "test")
    assert "error" not in admin_result

def test_notification_with_userid():
    from services.notifications import send_notification, get_notifications_for
    uid = create_user_id("test_user")
    send_notification(uid, "hello")
    notes = get_notifications_for(uid)
    assert len(notes) >= 1
    assert notes[-1]["formatted"] == "user:test_user"
```

Task: Migrate UserId from a plain string alias to a proper dataclass.

1. Change core/types.py: Replace `UserId = str` with a `@dataclass` class that has a `value: str` attribute. It must be hashable (frozen=True) and support equality. Update `create_user_id` and `format_user_id` accordingly.

2. Update ALL consumer modules to work with the new UserId type:
   - services/auth.py: Session keys, comparisons, display strings
   - services/notifications.py: Recipient storage, filtering, formatting
   - handlers/user_handler.py: Return values, passing UIDs
   - handlers/admin_handler.py: Admin check (compare against .value), formatting
   - main.py: Any string comparisons or displays

3. The test suite must pass: cd /tmp/type_migration && python3 -m pytest tests/ -v

Key constraint: The modules have a dependency DAG:
  core/types.py -> services/{auth,notifications}.py -> handlers/{user,admin}_handler.py -> main.py
Modules must be updated in this order, or intermediate imports will break.

After completing:
- cd /tmp/type_migration && python3 -c "from core.types import UserId; assert not isinstance(UserId, type(str))" succeeds
- cd /tmp/type_migration && python3 -m pytest tests/ -v passes all tests
- cd /tmp/type_migration && python3 main.py completes without errors