cadrumo.adapters.persistence.storage.blob_store.blob_store module¶
Encrypted, classification-aware blob store.
Blobs are persisted under a content-addressed layout rooted at
Settings.cadrumo_blob_store_dir. Each blob carries a sidecar
BlobManifest Envelope that pins the sensitivity
classification, the wire-format SHA-256 (plaintext for CORPUS; ciphertext for
every other class), the optional wrapped data-encryption key, the AEAD nonce,
the original size, and the content type.
Two layouts are supported:
Plaintext (
SensitivityClass.CORPUSonly): blob bytes are written verbatim underblobs/<hex[:2]>/<hex>where<hex>is the plaintext SHA-256. The manifest recordsencryption=None.Ciphertext (every other class): the substrate mints a 32-byte data-encryption key (DEK), encrypts the blob with AES-256-GCM keyed by the DEK, wraps the DEK using the active bucket session’s key (also AES-256-GCM), and writes the ciphertext under the plaintext digest path
blobs/<hex[:2]>/<hex>.enc. The manifest records the ciphertext SHA-256, the wrapped DEK, and the AEAD nonces; the master key never touches disk.
The repository’s public read API returns plaintext bytes; the
sensitivity class drives whether decryption is performed under the
hood. Manifests are Envelope[BlobManifest] instances stored
under <hex>.manifest.json.
Attempting to write a blob whose declared classification is incompatible
with its layout (e.g. tagging an arbitrary blob as CORPUS to skip
encryption) raises ClassificationError. Missing blobs raise
BlobNotFoundError. SHA-256 disagreement between the
on-disk file and the manifest raises BlobIntegrityError.
- class BlobManifest(**data)[source]¶
Bases:
BaseModelFrozen manifest record for one blob in the encrypted blob store.
- Variables:
sha256_plaintext_hex – Lowercase hex digest of the original plaintext bytes. Always present.
sha256_ciphertext_hex – Lowercase hex digest of the ciphertext on disk.
Nonefor plaintext (CORPUS) blobs.size_plaintext – Size of the original plaintext in bytes.
content_type – Free-form MIME type or descriptive label (e.g.
application/pdf,application/json,application/octet-stream). The substrate does not parse or validate the value; it is recorded for forensic clarity only. Consumers that need a stricter classification should choose the appropriateSensitivityClassrather than relying on the content-type label.classification – The
SensitivityClassof the payload.wrapped_dek – Per-blob data-encryption key, AES-256-GCM-wrapped with the master key, expressed as JSON-friendly
EncryptionMetadata.Nonefor plaintext blobs.payload_metadata – AEAD metadata for the payload itself (nonce + algorithm). The ciphertext lives on disk; this field carries only the surrounding metadata.
Nonefor plaintext blobs.
- Parameters:
data (Any)
- sha256_plaintext_hex: ContentDigest¶
- sha256_ciphertext_hex: ContentDigest | None¶
- size_plaintext: NonNegativeInt¶
- content_type: str¶
- classification: SensitivityClass¶
- wrapped_dek: EncryptionMetadata | None¶
- payload_metadata: EncryptionMetadata | None¶
- class BlobReference(**data)[source]¶
Bases:
BaseModelFrozen public handle for one blob.
- Variables:
sha256_plaintext_hex – Lowercase hex digest of the plaintext. This is the natural key for retrieval; the consumer computes it from the plaintext at write time.
classification – The classification used at write time. Required because the on-disk layout differs by class (plaintext vs ciphertext); the get path uses this to find the file.
- Parameters:
data (Any)
- sha256_plaintext_hex: ContentDigest¶
- classification: SensitivityClass¶
- class EncryptedBlobStore(*, root_dir)[source]¶
Bases:
objectRepository for the at-rest, classification-aware blob store.
- Parameters:
root_dir (Path)
- put(plaintext, *, classification, content_type='application/octet-stream')[source]¶
Persist
plaintextand return a reference for retrieval.The on-disk layout is dictated by
classification: CORPUS-class blobs are written verbatim; every other class is encrypted with a fresh per-blob DEK that is then wrapped with the master key.- Parameters:
plaintext (
bytes) – Bytes to persist.classification (
SensitivityClass) –SensitivityClasscontrolling the at-rest treatment (plaintext for CORPUS, ciphertext for all other classes).content_type (
str) – Stable MIME-type-style label stored in the manifest.
- Return type:
- Returns:
A
BlobReferencekeyed by the plaintext SHA-256 and the classification.- Raises:
ClassificationError – If a non-CORPUS class is requested but the policy table forbids ciphertext for it (defensive check; the default table never trips this).
- get(reference)[source]¶
Return the plaintext bytes for
reference.- Parameters:
reference (
BlobReference) – The blob reference identifying the stored object.- Return type:
- Returns:
Decrypted plaintext bytes for the referenced blob.
- Raises:
BlobNotFoundError – When the manifest or payload file is missing.
BlobIntegrityError – When the manifest is unusable, including when its envelope and payload classifications disagree – the layout is routed from the payload value, so an unchecked disagreement would send the read to the wrong layout.
- delete(reference)[source]¶
Remove the blob and its manifest.
Order: payload bytes (plaintext or ciphertext) are unlinked first, then the manifest. If the payload unlink fails, the manifest is left in place so a subsequent
getsurfaces aBlobIntegrityErrorrather than a silentBlobNotFoundError(sec-M-2).- Return type:
- Parameters:
reference (BlobReference)
- iter_manifests()[source]¶
Yield the
BlobManifestof every blob currently persisted.The walk is shallow: only the canonical
blobs/<hex[:2]>/<hex>.manifest.jsonfiles are visited. Each manifest is loaded through a single-read + inline version-gate path; corrupted or unparseable manifests fail closed so corruption does not disappear from audit flows.- Return type: