Source code for cadrumo.adapters.outbound.storage.records
"""Pydantic records and enums for the outbound storage abstraction.Provider records anchor the storage boundary:- :class:`adapters.outbound.storage.protocol.StorageProvider` - synchronous bytes-in / bytes-out provider Protocol.- :class:`ProviderKind` - closed enum naming the v1 backends.- :class:`ProviderObjectMetadata` - per-object metadata returned by listing / fetching operations. Carries the object key HMAC, namespace, byte size, content hash, and backend-native identifier.- :class:`ProviderProbeReport` - health-probe result. Reports whether the backend is reachable, writable, and (when applicable) bound to the expected root folder. The ``read_only`` field surfaces whether the probe ran in read-only mode (no sentinel-write round-trip).- :class:`RemoteMirrorObjectManifest`, :class:`RemoteMirrorNamespaceManifest`, :class:`RemoteMirrorIssue`, and :class:`RemoteMirrorInspection` - immutable manifest and inspection records for remote ciphertext mirror reconciliation by :mod:`adapters.outbound.storage.mirror_manifest`."""from__future__importannotationsfromdatetimeimportdatetimefromenumimportStrEnumfrompydanticimportBaseModel,Field,NonNegativeInt,model_validatorfrom....core.errors.hierarchyimportpydantic_validation_boundaryfrom....core.identity.digestimportContentDigestfrom....core.modelsimportSTRICT_FROZEN_CONFIG# Every identifier below is a SHA-256 hex digest produced by# ``core.hashing.sha256_hex``, so each is the canonical# :data:`~core.identity.digest.ContentDigest` shape under a distinct semantic name.# A length-only constraint accepted uppercase and non-hex 64-character# strings that the canonical alias refuses, letting a malformed digest reach# a persisted manifest and surface only when a later pass recomputes it.#: HMAC-SHA-256 hex digest naming one object at the remote provider boundary._ObjectKeyHmac=ContentDigest#: SHA-256 hex digest of the mirrored ciphertext bytes._CiphertextHash=ContentDigest#: SHA-256 hex digest identifying one secure-object storage revision._StorageRevisionId=ContentDigest
[docs]classProviderKind(StrEnum):"""Closed storage backend selector for :func:`adapters.outbound.storage.factory.get_storage_provider`."""LOCAL_FILESYSTEM="local_filesystem"GOOGLE_DRIVE="google_drive"
[docs]classRemoteMirrorIssueKind(StrEnum):"""Remote ciphertext mirror degradation classes carried by :class:`RemoteMirrorIssue`."""PARTIAL_UPLOAD="partial_upload"PARTIAL_DOWNLOAD="partial_download"STALE_MIRROR="stale_mirror"REVISION_CONFLICT="revision_conflict"
[docs]classProviderObjectMetadata(BaseModel):"""Per-object metadata returned by storage-provider operations. Returned by :class:`adapters.outbound.storage.protocol.StorageProvider` methods: :meth:`adapters.outbound.storage.protocol.StorageProvider.put`, :meth:`adapters.outbound.storage.protocol.StorageProvider.get`, and :meth:`adapters.outbound.storage.protocol.StorageProvider.iter_objects`. ``provider_object_id`` is the backend-native identifier (a filesystem path for the local provider, a Drive ``fileId`` for the Google Drive provider). The coordinator threads it through subsequent get/delete/patch calls without re-resolving by name. """model_config=STRICT_FROZEN_CONFIGnamespace:str=Field(min_length=1)object_key_hmac:str=Field(min_length=1)provider_object_id:str=Field(min_length=1)byte_length:NonNegativeIntcontent_hash:str=Field(min_length=1)written_at:datetime
[docs]classProviderProbeReport(BaseModel):"""Health-check result returned by storage-provider probes. Returned by :meth:`adapters.outbound.storage.protocol.StorageProvider.probe`. ``reachable`` is True iff the backend endpoint responds at all. ``writable`` is True iff a sentinel payload write/delete succeeded; inherently False when the probe runs in read-only mode. ``root_folder_present`` is non-None only when the backend supports the notion of an operator-configured root folder (currently Google Drive). """model_config=STRICT_FROZEN_CONFIGprovider_kind:ProviderKindreachable:boolwritable:boolread_only:boolroot_folder_present:bool|None=Nonedetail:str=""
[docs]classRemoteMirrorObjectManifest(BaseModel):"""One ciphertext object entry recorded in a :class:`RemoteMirrorNamespaceManifest`."""model_config=STRICT_FROZEN_CONFIGnamespace:str=Field(min_length=1)object_key_hmac:_ObjectKeyHmacclassification:str=Field(min_length=1)schema_version:int=Field(ge=1)byte_length:NonNegativeIntciphertext_hash:_CiphertextHashstorage_revision_id:_StorageRevisionId|None=Noneprevious_storage_revision_id:_StorageRevisionId|None=Nonerevision_ancestor_ids:tuple[_StorageRevisionId,...]=()row_written_at:datetimerevision_written_at:datetime|None=None
[docs]classRemoteMirrorNamespaceManifest(BaseModel):"""Manifest persisted beside remote ciphertext objects for one namespace. Built by :func:`adapters.outbound.storage.mirror_manifest.build_remote_mirror_namespace_manifest` and persisted by :func:`adapters.outbound.storage.mirror_manifest.put_remote_mirror_namespace_manifest`. """model_config=STRICT_FROZEN_CONFIGmanifest_schema_version:int=Field(ge=1)namespace:str=Field(min_length=1)object_count:NonNegativeIntlatest_revision_id:_StorageRevisionId|None=Nonelatest_revision_written_at:datetime|None=Noneobjects:tuple[RemoteMirrorObjectManifest,...]@model_validator(mode="after")@pydantic_validation_boundarydef_enforce_object_key_uniqueness_and_count(self)->RemoteMirrorNamespaceManifest:"""Refuse foreign-namespace children, duplicate object keys, and a disagreeing ``object_count``. Comparison in :mod:`adapters.outbound.storage.mirror_manifest` keys both manifests by ``object_key_hmac``, so a repeated key would silently discard every earlier row and hide the revision conflict it carried. The key is one object's identity within a namespace, so a repeat is manifest corruption rather than a representable state; refusing it here makes a tampered or torn remote manifest fail closed through the :class:`OutboundStorageIntegrityError` the loader already raises. The namespace binding closes the same class of hole one level up. A manifest describes exactly one namespace, but each child carried its own ``namespace`` field that nothing checked against the parent, and the download path fetches by the *child's* value. A manifest named ``target`` holding a child named ``foreign`` therefore inspected clean and pulled another namespace's ciphertext under the target's identity. Binding the two here means the confusion cannot be represented at all, rather than being caught separately by each consumer. """foreign=sorted({entry.namespaceforentryinself.objectsifentry.namespace!=self.namespace})ifforeign:raiseValueError(f"remote mirror manifest for namespace {self.namespace!r} "f"records objects from foreign namespaces: {foreign}",)keys=[entry.object_key_hmacforentryinself.objects]duplicates=sorted({keyforkeyinkeysifkeys.count(key)>1})ifduplicates:raiseValueError(f"remote mirror manifest repeats object_key_hmac entries: {duplicates}",)ifself.object_count!=len(self.objects):raiseValueError(f"remote mirror manifest object_count {self.object_count} "f"disagrees with {len(self.objects)} recorded objects",)returnself
[docs]classRemoteMirrorIssue(BaseModel):"""One detected remote mirror degradation in a :class:`RemoteMirrorInspection`."""model_config=STRICT_FROZEN_CONFIGkind:RemoteMirrorIssueKindnamespace:str=Field(min_length=1)object_key_hmac:_ObjectKeyHmac|None=Nonedetail:str=Field(min_length=1)
[docs]classRemoteMirrorInspection(BaseModel):"""Typed result returned by remote mirror comparison and inspection helpers."""model_config=STRICT_FROZEN_CONFIGnamespace:str=Field(min_length=1)issues:tuple[RemoteMirrorIssue,...]=()@propertydefok(self)->bool:"""Return whether the inspection recorded no issues."""returnnotself.issues