cadrumo.adapters.outbound.storage.local module¶
Local-filesystem adapters.outbound.storage.protocol.StorageProvider implementation.
Stores objects under a configurable root directory. Each namespace is
a subdirectory; each object is a single file named
<hmac_prefix_8>--<label>.<ext>. Metadata (content_hash,
byte_length, written_at, full HMAC, and label) lives in a sibling JSON
sidecar so the listing API can return ProviderObjectMetadata without
re-hashing the payload.
Bytes-in / bytes-out: encryption + classification stay above this
layer. The provider treats every payload as opaque bytes and uses
adapters.outbound.storage._integrity.verify_content_hash() to
enforce the stored digest on read.
- class LocalFileSystemProvider(root)[source]¶
Bases:
objectBytes-in / bytes-out provider backed by a
pathlib.Pathtree.- Parameters:
root (Path)
- property root: Path¶
Provider storage root as a
pathlib.Path.
- put(namespace, object_key_hmac, payload, *, content_hash, label, batch=None)[source]¶
Atomically write the object and its sidecar, returning
ProviderObjectMetadata.batchopts this write into aDurableWriteBatch, deferring its fsyncs to the batch commit. Sound here specifically because the store is content-addressed:content_hashnames the payload, so a crash mid-batch yields a file that fails its own digest check on read and whose source document is still available to re-import. A bulk evidence ingest passes one batch for the whole run; a singleputpasses nothing and keeps per-file durability.Atomicity guarantee: the payload file is written through
atomic_write_hardened_bytes()(the master-keyO_EXCL+ mode0o600+ fsync pattern) rather than the standard tier. The payload here is already-encrypted ciphertext (encryption and classification stay above this provider layer), but the 2026-05-30 security-paths audit flagged the sidecar’s plaintextcontent_hash/byte_lengthmetadata as a size-channel inference aid against the encrypted blob set, so the object file is hardened defence-in-depth even though its own content is opaque. The sidecar is written afterwards throughatomic_write_text()(standard tier), closing the crash window where a torn sidecar write could leave a committed object with missing or corrupted metadata; on sidecar-write failure the payload is removed so no orphaned object lingers without metadata.- Return type:
- Parameters:
- get(namespace, object_key_hmac)[source]¶
Read the object payload from disk and return verified metadata.
Locates the
.binfile by HMAC prefix, loads the sibling.meta.jsonsidecar, reads the raw bytes, and compares thecore.hashing.sha256_hex()digest against the sidecar’scontent_hashfield throughverify_content_hash(). Bothsha256-<hex>-prefixed strings and bare hex digests are accepted.- Parameters:
- Return type:
- Returns:
A two-tuple containing payload bytes and
ProviderObjectMetadata.- Raises:
OutboundStorageNotFoundError – When the object file is absent.
OutboundStorageIntegrityError – When the sidecar is missing, unreadable, or contains non-JSON content; or when the payload digest does not match the stored hash.
OutboundStoragePermissionError – When the object file cannot be read due to OS permissions.
StorageCorruptionError – When the sidecar
byte_lengthfield has an unexpected type, or itswritten_atis absent, unparseable, or tz-naive.OutboundStorageValidationError – When
namespaceorobject_key_hmacfail format checks.
- delete(namespace, object_key_hmac)[source]¶
Remove the object file and its sidecar from disk, or neither.
Returns
Falseimmediately when the object is absent; deleting a non-existent object is idempotent. The sidecar is removed withmissing_ok=Trueso a pre-existing orphaned payload without a sidecar is still cleanly deleted.A failure leaves the PAIR, never half of it. The payload used to be unlinked first and the sidecar second, so a sidecar cleanup that failed after the payload was already gone raised while leaving nothing to retry:
iter_objectsno longer reported the object,getcould not read it, and the orphaned sidecar path then blocked a re-putunder the same key. The object was un-deletable, un-readable and un-writable at once.Two changes make the failure recoverable. The sidecar goes first, so a refusal there removes nothing at all. And its bytes are read before that removal, so a payload unlink that then fails can put the sidecar back — the sidecar is metadata this method already holds, whereas the payload is not something it could restore. Either way the retry that follows sees the same state the caller started from.
Both unlinks are guarded on
OSErrorrather thanPermissionErroralone: the ways a filesystem refuses to remove a path are not one errno, and on some platforms an unlink of a directory-shaped sidecar raisesIsADirectoryError, which is not aPermissionError. A narrower catch let exactly the corruption shape above escape untranslated.- Parameters:
- Return type:
- Returns:
Truewhen the object was found and deleted;Falsewhen it was already absent.- Raises:
OutboundStoragePermissionError – When the OS refuses to read or remove either half of the pair.
OutboundStorageValidationError – When
namespaceorobject_key_hmacfail format checks.
- iter_namespaces()[source]¶
Yield the name of every namespace subdirectory under
root.Returns immediately (yields nothing) when
rootdoes not yet exist on disk.
- iter_objects(namespace)[source]¶
Yield metadata for every object in
namespace.Only
.binfiles with a companion.meta.jsonsidecar are yielded; files without a sidecar are silently skipped (the coordinator surfaces those as integrity issues via its own diff classifier).- Parameters:
namespace (
str) – Logical bucket name.- Yields:
ProviderObjectMetadatarecords in sorted filename order.- Raises:
OutboundStorageNotFoundError – When the namespace directory is absent.
OutboundStorageIntegrityError – When a sidecar file is unreadable or contains non-JSON content.
StorageCorruptionError – When a sidecar
byte_lengthfield has an unexpected type, or awritten_atis absent, unparseable, or tz-naive.OutboundStorageValidationError – When
namespacefails format checks.
- Return type:
- probe(*, read_only=False)[source]¶
Assess filesystem accessibility and write permissions, returning a
ProviderProbeReport.Attempts to create
rootif absent. Then, unlessread_only=True, performs a sentinel write/delete round-trip in a_probenamespace to confirm write access end-to-end.The method never raises; every failure mode is encoded in the returned
ProviderProbeReport.- Parameters:
read_only (
bool) – WhenTrue, skip the sentinel write round-trip and reportwritable=Falseregardless of actual permissions.- Return type:
- Returns:
A
ProviderProbeReportwithreachable,writable, and a human-readabledetailstring describing the outcome.