cadrumo.adapters.persistence.profile.invoices module

Governed-persistence repository for the invoice catalogue.

InvoiceCatalogueRepository is the sanctioned read/write path for the InvoiceCatalogue. It stores the catalogue as an encrypted byte object via SecureObjectRepository at FINANCIAL SensitivityClass using an Envelope wrapper; no plaintext invoice row, JSON catalogue, or envelope file lands on disk. The Envelope-wrapping mechanic itself is owned by ProfileEnvelopedModelSecurePersistence (_secure_enveloped_document.py); this class retains only the invoice bucket-ownership guard, which the shared kernel has no domain knowledge of.

This concrete repository is the persistence adapter behind the read-side InvoiceCatalogueRepositoryProtocol. It lives in the persistence adapter (not in domain.invoices) because its secure-object coupling is SQL/crypto-bound; the domain package owns only the typed InvoiceCatalogue model, its narrow port, and the InvoicePersistenceError boundary error.

class InvoiceCatalogueRepository(*, bucket_id=None, objects=None)[source]

Bases: object

Repository over encrypted SQL-backed InvoiceCatalogue storage.

adapters.persistence.storage.secure_object_namespaces.INVOICE_CATALOGUE_NAMESPACE is the central profile-local namespace, schema-version, sensitivity, and singleton-key contract for the encrypted invoice catalogue row. The InvoiceCatalogue payload is wrapped in Envelope before SecureObjectRepository persists it, and to_secure_object_write() exposes the same write for transaction/event co-commit paths. This class exposes the concrete load/save implementation behind InvoiceCatalogueRepositoryProtocol, composing ProfileEnvelopedModelSecurePersistence for the shared envelope/exists/load/save mechanic and retaining only the invoice-specific bucket-ownership guard.

Parameters:
property bucket_id: str | None

Return the profile bucket id when this repository resolved one.

exists()[source]

Return whether an invoice catalogue has been persisted.

Return type:

bool

load()[source]

Return the persisted catalogue or an empty catalogue if absent.

Return type:

InvoiceCatalogue

Returns:

The deserialised InvoiceCatalogue, or a fresh empty instance when no database object is present.

Raises:
  • ClassificationError – If the persisted object’s classification is not the sensitivity INVOICE_CATALOGUE_NAMESPACE declares.

  • EnvelopeVersionError – If the envelope schema version is higher than the consumer supports.

save(catalogue)[source]

Persist catalogue atomically under the file lock.

The on-disk database value is an encrypted Envelope BLOB at the SensitivityClass FINANCIAL classification declared by adapters.persistence.storage.secure_object_namespaces.INVOICE_CATALOGUE_NAMESPACE. No plaintext invoice row lands on disk.

Parameters:

catalogue (InvoiceCatalogue) – The InvoiceCatalogue to persist.

Return type:

None

mutate(mutation)[source]

Apply mutation to the stored catalogue as one guarded unit of work.

The catalogue is a SINGLETON row, so adding one invoice is really read-whole-catalogue, rebuild, write whole catalogue. Two operators adding DIFFERENT invoices at once both read the same catalogue and the later write discards the earlier invoice – silently, because the duplicate-identity check never sees the other invoice. A dropped invoice under-declares, which is the failure this repository exists to make impossible.

The bucket-ownership guard runs on the mutation’s OWN result rather than once before it, so a mutation that introduces a foreign invoice is refused on every attempt instead of only the first.

Parameters:

mutation (Callable[[InvoiceCatalogue], InvoiceCatalogue]) – Builds the next catalogue from the current one. Raising refuses the whole mutation, unretried.

Return type:

InvoiceCatalogue

Returns:

The catalogue as written.

Core types: InvoiceCatalogue.

load_revisioned()[source]

Return the catalogue and the revision id it was read at.

The read a guarded co-commit needs. Linking and reconciliation both write this catalogue inside a batch with the transaction store, so neither can use the self-committing mutate(); without the revision the batch rewrites the whole singleton row and drops an invoice another caller added in between.

Return type:

tuple[InvoiceCatalogue, str]

load_revision()[source]

Return the catalogue’s stored revision without decrypting it.

The absent catalogue has the absent-object revision, the same one load_revisioned() reports for it. None means the stored row predates recorded revisions, and a caller must treat it as changed.

Return type:

str | None

to_secure_object_write(catalogue, *, expected_revision_id=None)[source]

Return the secure-object upsert for catalogue without committing it.

The returned SecureObjectWrite carries the same Envelope and SensitivityClass classification that save() would persist directly.

Parameters:
  • catalogue (InvoiceCatalogue) – The InvoiceCatalogue to serialise.

  • expected_revision_id (str | None) – The revision load_revisioned() reported for the catalogue this one was derived from. This is a SINGLETON row, so without it the write lands over any invoice another caller committed since the read, and a dropped invoice under-declares.

Return type:

SecureObjectWrite