Files
Aegis/backend/app/domain/exceptions.py
Kitos 6d18a5417d
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
feat(phase-34): resolve blocking tech debt — Redis, domain exceptions, indexes, CI
Foundational changes required before any new feature work can begin.

- 0.1 Redis infrastructure: add redis:7-alpine to docker-compose dev and prod,
  REDIS_URL config, singleton client in app/infrastructure/redis_client.py
- 0.2 Token blacklist on Redis SEC-001: replace in-memory dict with Redis SETEX
  keyed by jti, auto-expiring TTL derived from token exp
- 0.3 Database indexes SR-006: Alembic migration b019 with 5 composite indexes
  for scoring, MTTD/MTTR, remediation, and notification queries
- 0.4 Domain exceptions TD-003: app/domain/exceptions.py with typed errors,
  error_handler middleware mapping them to HTTP, services decoupled from FastAPI
- 0.5 Fix silenced exceptions TD-007: replace 4 bare except-pass blocks in
  test_workflow_service with logger.warning with exc_info
- 0.6 CI pipeline TD-009: GitHub Actions workflow with Postgres and Redis
  service containers, ruff lint, pytest; ruff.toml for baseline config
2026-02-17 15:43:05 +01:00

68 lines
2.1 KiB
Python

"""Domain exceptions for Aegis business logic.
These exceptions are raised by service-layer code and automatically
mapped to HTTP responses by the error-handler middleware registered
in ``app.main``. This keeps the service layer free from any HTTP
or framework coupling.
"""
class DomainException(Exception):
"""Base for all domain exceptions."""
def __init__(self, message: str, code: str = "DOMAIN_ERROR"):
self.message = message
self.code = code
super().__init__(message)
class EntityNotFoundError(DomainException):
"""Raised when a requested entity does not exist."""
def __init__(self, entity: str, identifier: str):
super().__init__(f"{entity} not found: {identifier}", "NOT_FOUND")
self.entity = entity
self.identifier = identifier
class DuplicateEntityError(DomainException):
"""Raised when creating an entity that already exists."""
def __init__(self, entity: str, field: str, value: str):
super().__init__(
f"{entity} with {field}='{value}' already exists",
"DUPLICATE",
)
class InvalidTransitionError(DomainException):
"""Raised when a state-machine transition is not allowed."""
def __init__(
self,
current_state: str,
target_state: str,
valid_transitions: list[str] | None = None,
):
msg = f"Cannot transition from '{current_state}' to '{target_state}'"
if valid_transitions:
msg += f". Valid transitions: {valid_transitions}"
super().__init__(msg, "INVALID_TRANSITION")
self.current_state = current_state
self.target_state = target_state
self.valid_transitions = valid_transitions or []
class InvalidOperationError(DomainException):
"""Raised when an operation is invalid in the current context."""
def __init__(self, message: str):
super().__init__(message, "INVALID_OPERATION")
class AuthorizationError(DomainException):
"""Raised when the user lacks permissions for an action."""
def __init__(self, message: str = "Insufficient permissions"):
super().__init__(message, "FORBIDDEN")