diff --git a/src/cleveragents/infrastructure/database/models.py b/src/cleveragents/infrastructure/database/models.py index eb45ee2ca..d3fb1bcdc 100644 --- a/src/cleveragents/infrastructure/database/models.py +++ b/src/cleveragents/infrastructure/database/models.py @@ -1307,9 +1307,7 @@ class InvariantModel(Base): # type: ignore[misc] scope = Column(String(20), nullable=False) source_name = Column(String(255), nullable=False) active = Column(Boolean, nullable=False, default=True, server_default="1") - non_overridable = Column( - Boolean, nullable=False, default=False, server_default="0" - ) + non_overridable = Column(Boolean, nullable=False, default=False, server_default="0") created_at = Column(String(30), nullable=False) def to_domain(self) -> Invariant: diff --git a/src/cleveragents/infrastructure/database/repositories.py b/src/cleveragents/infrastructure/database/repositories.py index 9c1f0177a..9811cf466 100644 --- a/src/cleveragents/infrastructure/database/repositories.py +++ b/src/cleveragents/infrastructure/database/repositories.py @@ -112,7 +112,6 @@ from cleveragents.infrastructure.database.models import ( CorrectionAttemptModel, DebugAttemptModel, DecisionModel, - InvariantModel, LifecycleActionModel, LifecyclePlanModel, NamespacedProjectModel, @@ -141,8 +140,6 @@ if TYPE_CHECKING: CorrectionAttemptRecord, ) from cleveragents.domain.models.core.decision import Decision - from cleveragents.domain.models.core.invariant import Invariant - from cleveragents.domain.models.core.correction import ( CORRECTION_ATTEMPT_TERMINAL_STATES, @@ -6091,144 +6088,11 @@ class CorrectionAttemptRepository: # --------------------------------------------------------------------------- -# InvariantRepository — Standalone Invariant Persistence +# InvariantRepository -- re-exported from dedicated module # --------------------------------------------------------------------------- - - -class InvariantRepository: - """Repository for standalone invariant persistence. - - Implements CRUD operations for invariants with database backing, - enabling invariants to persist across process restarts. - - Uses a session-factory pattern: each public method obtains its own - session from the factory, ensuring proper session lifecycle management. - - All mutating methods flush (but do NOT commit); the caller or a - ``UnitOfWork`` wrapper is responsible for committing the transaction. - """ - - def __init__(self, session_factory: Callable[[], Session]) -> None: - """Initialise with a callable that returns a new SQLAlchemy Session.""" - self._session_factory = session_factory - - def _session(self) -> Session: - """Convenience helper to obtain a session.""" - return self._session_factory() - - @database_retry - def add(self, invariant: Invariant) -> Invariant: - """Persist a new invariant to the database. - - Args: - invariant: An ``Invariant`` domain model instance. - - Returns: - The same ``Invariant`` after persistence. - - Raises: - DatabaseError: On transient or unexpected DB errors. - """ - - session = self._session() - try: - db_model = InvariantModel.from_domain(invariant) - session.add(db_model) - session.flush() - session.commit() - return invariant - except (OperationalError, SQLAlchemyDatabaseError) as exc: - session.rollback() - raise DatabaseError(f"Failed to add invariant: {exc}") from exc - - @database_retry - def list( - self, - scope: str | None = None, - source_name: str | None = None, - ) -> list[Invariant]: - """List invariants, optionally filtered by scope and/or source_name. - - Args: - scope: Filter by scope (None = all scopes). - source_name: Filter by source name (None = all sources). - - Returns: - List of ``Invariant`` domain objects. - - Raises: - DatabaseError: On transient or unexpected DB errors. - """ - session = self._session() - try: - query = session.query(InvariantModel).filter_by(active=True) - - if scope is not None: - query = query.filter_by(scope=scope) - - if source_name is not None: - query = query.filter_by(source_name=source_name) - - rows = query.all() - return [row.to_domain() for row in rows] - except (OperationalError, SQLAlchemyDatabaseError) as exc: - raise DatabaseError(f"Failed to list invariants: {exc}") from exc - - @database_retry - def get_by_id(self, invariant_id: str) -> Invariant | None: - """Retrieve an invariant by its ULID. - - Args: - invariant_id: The ULID of the invariant. - - Returns: - The ``Invariant`` domain object, or ``None`` if not found. - - Raises: - DatabaseError: On transient or unexpected DB errors. - """ - session = self._session() - try: - row = session.query(InvariantModel).filter_by(id=invariant_id).first() - if row is None: - return None - return row.to_domain() - except (OperationalError, SQLAlchemyDatabaseError) as exc: - raise DatabaseError( - f"Failed to get invariant {invariant_id}: {exc}" - ) from exc - - @database_retry - def soft_delete(self, invariant_id: str) -> Invariant: - """Soft-delete an invariant by setting active=False. - - Args: - invariant_id: The ULID of the invariant to delete. - - Returns: - The updated ``Invariant`` domain object. - - Raises: - DatabaseError: On transient or unexpected DB errors. - """ - from cleveragents.core.exceptions import NotFoundError - - session = self._session() - try: - row = session.query(InvariantModel).filter_by(id=invariant_id).first() - if row is None: - raise NotFoundError( - resource_type="invariant", - resource_id=invariant_id, - ) - row.active = False # type: ignore[assignment] - session.flush() - session.commit() - return row.to_domain() - except NotFoundError: - raise - except (OperationalError, SQLAlchemyDatabaseError) as exc: - session.rollback() - raise DatabaseError( - f"Failed to soft-delete invariant {invariant_id}: {exc}" - ) from exc +# InvariantRepository has been extracted into its own module to keep +# repositories.py focused. The re-export below preserves backward +# compatibility for existing imports. +from cleveragents.infrastructure.database.invariant_repository import ( # noqa: E402, F401 + InvariantRepository, +)