Source code for cadrumo.adapters.persistence.operations.financial_operand_custody
"""Durable filesystem custody checkpoints for transient financial operands.The store holds one versioned checkpoint per wait and nothing more. Checkpointsare non-sensitive by construction - the operand contract keeps every amount outof the record - so this is an ordinary durable file rather than encryptedsecure storage, which is the same posture the owner lease takes.Every advance is a compare-and-swap under an exclusive lock. Two supervisorpaths that both believe they should release one wait will both present the samepredecessor; the first swap wins and the second is refused, so the buffer iscleared once."""from__future__importannotationsfromtypingimportTYPE_CHECKING,overridefrom....application.operations.financial_operand_custodyimport(OperationFinancialOperandCustodyCheckpoint,)from....application.operations.persistence.financial_operand_custodyimport(OperationFinancialOperandCustodyConflictError,OperationFinancialOperandCustodyRepository,)from....core.configimportSettingsfrom....core.locksimportexclusive_file_lockfrom....core.storage_taxonomyimportStorageCategoryfrom....core.storage_taxonomy_locationsimportstorage_pathfrom..storage.errorsimportRepositoryErrorifTYPE_CHECKING:frompathlibimportPath_CUSTODY_DIRECTORY="financial_operand_custody"def_checkpoint_path(root:Path,interaction_id:str)->Path:"""Return the one file backing a wait, refusing a traversing identifier."""ifnotinteraction_idor"/"ininteraction_idor"\\"ininteraction_idorinteraction_id.startswith("."):raiseRepositoryError(f"invalid custody interaction identifier: {interaction_id!r}")returnroot/f"{interaction_id}.json"
[docs]classOperationFinancialOperandCustodyFilesystemRepository(OperationFinancialOperandCustodyRepository):"""One durable checkpoint per wait, advanced only by compare-and-swap."""def__init__(self,*,root:Path|None=None,settings:Settings|None=None)->None:"""Bind the directory this repository keeps its checkpoints in. ``settings`` resolves the default location, so a caller that already resolved a storage root gets custody beside that root's journal rather than beside whatever the ambient settings point at. """self._root=(rootifrootisnotNoneelsestorage_path(StorageCategory.OPERATION_JOURNAL,settings=settings)/_CUSTODY_DIRECTORY)self._root.mkdir(parents=True,exist_ok=True)
[docs]@overrideasyncdefread(self,interaction_id:str)->OperationFinancialOperandCustodyCheckpoint|None:"""Return the latest checkpoint for one wait, or ``None`` if never opened."""path=_checkpoint_path(self._root,interaction_id)ifnotpath.is_file():returnNonereturnOperationFinancialOperandCustodyCheckpoint.model_validate_json(path.read_text(encoding="utf-8"))
[docs]@overrideasyncdefopen(self,checkpoint:OperationFinancialOperandCustodyCheckpoint)->None:"""Record the opening checkpoint, refusing a wait that already exists."""path=_checkpoint_path(self._root,checkpoint.interaction_id)withexclusive_file_lock(path.with_suffix(".lock")):ifpath.exists():raiseOperationFinancialOperandCustodyConflictError(f"custody for interaction {checkpoint.interaction_id!r} is already open")path.write_text(checkpoint.model_dump_json(),encoding="utf-8")
[docs]@overrideasyncdefadvance(self,predecessor:OperationFinancialOperandCustodyCheckpoint,successor:OperationFinancialOperandCustodyCheckpoint,)->None:"""Replace ``predecessor`` with ``successor`` only if it is still current."""ifsuccessor.interaction_id!=predecessor.interaction_id:raiseRepositoryError("a custody advance cannot move a checkpoint to another interaction")path=_checkpoint_path(self._root,predecessor.interaction_id)withexclusive_file_lock(path.with_suffix(".lock")):ifnotpath.is_file():raiseOperationFinancialOperandCustodyConflictError(f"custody for interaction {predecessor.interaction_id!r} is not open")stored=OperationFinancialOperandCustodyCheckpoint.model_validate_json(path.read_text(encoding="utf-8"))ifstored!=predecessor:raiseOperationFinancialOperandCustodyConflictError(f"custody for interaction {predecessor.interaction_id!r} advanced under this caller")path.write_text(successor.model_dump_json(),encoding="utf-8")
[docs]@overrideasyncdefunsettled(self)->tuple[OperationFinancialOperandCustodyCheckpoint,...]:"""Return every wait left in a non-terminal state, for restart reconciliation."""checkpoints=[OperationFinancialOperandCustodyCheckpoint.model_validate_json(path.read_text(encoding="utf-8"))forpathinsorted(self._root.glob("*.json"))]returntuple(checkpointforcheckpointincheckpointsifnotcheckpoint.is_terminal)