"""Public writes own a transaction; internal helpers never open public operations.""" from dataclasses import dataclass from decimal import Decimal, InvalidOperation from functools import wraps from uuid import UUID from django.core.exceptions import ValidationError from django.db import IntegrityError, connection, transaction from finance.models import AuditEvent @dataclass(frozen=True) class Actor: kind: str identifier: str class DomainError(ValueError): def __init__(self, code): self.code = code super().__init__(code) def money(value, *, allow_zero=False): if not isinstance(value, (Decimal, str, int)) or isinstance(value, bool): raise DomainError("invalid_amount") try: result = Decimal(value) except (InvalidOperation, ValueError): raise DomainError("invalid_amount") from None if not result.is_finite() or result.as_tuple().exponent < -2 or result.adjusted() >= 16: raise DomainError("invalid_amount") if not allow_zero and result == 0: raise DomainError("zero_amount") return result def identity(value): try: return UUID(str(value)) except (ValueError, TypeError, AttributeError): raise DomainError("invalid_identifier") from None def lock(model, identifier, *, active=False): try: obj = model.objects.select_for_update().get(pk=identity(identifier)) except model.DoesNotExist: raise DomainError("not_found") from None if active and obj.archived: raise DomainError("archived_reference") return obj def save(obj): obj.full_clean() obj.save() return obj def audit(actor, operation, outcome, target_id=None, fields=(), error_code=""): AuditEvent.objects.create(actor_kind=actor.kind, actor_identifier=actor.identifier, operation=operation, target_id=target_id, outcome=outcome, changed_fields=list(fields), error_code=error_code) def write(operation): def decorate(function): @wraps(function) def wrapped(*args, actor, **kwargs): if not isinstance(actor, Actor) or actor.kind not in {"HUMAN", "APPLICATION", "AGENT"} or not isinstance(actor.identifier, str) or not actor.identifier.strip() or len(actor.identifier) > 200: raise DomainError("invalid_actor") if connection.in_atomic_block: raise DomainError("transaction_boundary") try: with transaction.atomic(durable=True): result, fields = function(*args, **kwargs) audit(actor, operation, "SUCCESS", result.pk, fields) return result except (DomainError, ValidationError, IntegrityError) as exc: code = exc.code if isinstance(exc, DomainError) else ("conflict" if isinstance(exc, IntegrityError) else "validation_error") # The failed mutation transaction has exited before this audit. # Audit persistence errors propagate: do not conceal a database outage. with transaction.atomic(durable=True): audit(actor, operation, "FAILURE", error_code=code) raise DomainError(code) from None return wrapped return decorate