# [DEF:TransactionCore:Module] # @TIER: CRITICAL # @SEMANTICS: Finance, ACID, Transfer, Ledger # @PURPOSE: Core banking transaction processor with ACID guarantees. # @LAYER: Domain (Core) # @RELATION: DEPENDS_ON -> [DEF:Infra:PostgresDB] # # @INVARIANT: Total system balance must remain constant (Double-Entry Bookkeeping). # @INVARIANT: Negative transfers are strictly forbidden. # --- Test Specifications --- # @TEST_CONTRACT: TransferRequestDTO -> TransferResultDTO # @TEST_SCENARIO: sufficient_funds -> Returns COMPLETED, balances updated. # @TEST_FIXTURE: sufficient_funds -> file:./__tests__/fixtures/transfers.json#happy_path # @TEST_EDGE: insufficient_funds -> Throws BusinessRuleViolation("INSUFFICIENT_FUNDS"). # @TEST_EDGE: negative_amount -> Throws BusinessRuleViolation("Transfer amount must be positive."). # @TEST_EDGE: concurrency_conflict -> Throws DBTransactionError. # # @TEST_INVARIANT: total_balance_constant -> VERIFIED_BY: [sufficient_funds, concurrency_conflict] # @TEST_INVARIANT: negative_transfer_forbidden -> VERIFIED_BY: [negative_amount] from decimal import Decimal from typing import NamedTuple # GRACE: Импорт глобального логгера с семантическими методами from ...core.logger import logger, belief_scope from ...core.db import atomic_transaction, get_balance, update_balance from ...core.audit import log_audit_trail from ...core.exceptions import BusinessRuleViolation class TransferResult(NamedTuple): tx_id: str status: str new_balance: Decimal # [DEF:execute_transfer:Function] # @PURPOSE: Atomically move funds between accounts with audit trails. # @DATA_CONTRACT: Input -> (sender_id: str, receiver_id: str, amount: Decimal), Output -> TransferResult # @PRE: amount > 0; sender != receiver; sender_balance >= amount. # @POST: sender_balance -= amount; receiver_balance += amount; Audit Record Created. # @SIDE_EFFECT: Database mutation (Rows locked), Audit IO. # # @UX_STATE: Success -> Returns 200 OK + Transaction Receipt. # @UX_STATE: Error(LowBalance) -> 422 Unprocessable -> UI shows "Top-up needed" modal. def execute_transfer(sender_id: str, receiver_id: str, amount: Decimal) -> TransferResult: # Guard: Input Validation (Вне belief_scope, так как это trivial проверка) if amount <= Decimal("0.00"): raise BusinessRuleViolation("Transfer amount must be positive.") if sender_id == receiver_id: raise BusinessRuleViolation("Cannot transfer to self.") # GRACE: Используем strict Context Manager без 'as context' with belief_scope("execute_transfer"): # GRACE: [REASON] - Жесткая дедукция, начало алгоритма logger.reason("Initiating transfer", extra={"from": sender_id, "to": receiver_id, "amount": amount}) try: # @RELATION: CALLS -> atomic_transaction with atomic_transaction(): current_balance = get_balance(sender_id, for_update=True) if current_balance < amount: # GRACE: [EXPLORE] - Отклонение от Happy Path (фолбэк/ошибка) logger.explore("Insufficient funds validation hit", extra={"balance": current_balance}) raise BusinessRuleViolation("INSUFFICIENT_FUNDS") # Mutation new_src_bal = update_balance(sender_id, -amount) new_dst_bal = update_balance(receiver_id, +amount) # Audit tx_id = log_audit_trail("TRANSFER", sender_id, receiver_id, amount) # GRACE:[REFLECT] - Сверка с @POST перед возвратом logger.reflect("Transfer committed successfully", extra={"tx_id": tx_id, "new_balance": new_src_bal}) return TransferResult(tx_id, "COMPLETED", new_src_bal) except BusinessRuleViolation as e: # Explicit re-raise for UI mapping raise e except Exception as e: # GRACE: [EXPLORE] - Неожиданный сбой logger.explore("Critical Transfer Failure", exc_info=e) raise RuntimeError("TRANSACTION_ABORTED") from e #[/DEF:execute_transfer:Function] # [/DEF:TransactionCore:Module]