"""Persistence contract. The surface is deliberately minimal at M0 (learners + attempts) and grows with real needs: review items and error profiles arrive with M3/M4, when the SQLite/Supabase implementations replace the in-memory one behind this same Protocol. """ from typing import Protocol, runtime_checkable from tutor.domain.models import Attempt, Learner @runtime_checkable class Repository(Protocol): async def save_learner(self, learner: Learner) -> None: ... async def get_learner(self, learner_id: str) -> Learner | None: ... async def save_attempt(self, attempt: Attempt) -> None: ... async def list_attempts(self, learner_id: str) -> list[Attempt]: ...