cadrumo.adapters.persistence.storage.bucket.directory_layout module¶
Filesystem provisioning and path resolution for per-bucket directories.
The per-bucket on-disk model lives at <cadrumo-root>/buckets/<bucket-id>/
and carries exactly two subdirectories:
db/relational state (SQLite database files).blobs/opaque artefact storage (sealed ciphertext blobs).
This module RESOLVES that layout; it does not create or destroy it. A
bucket root comes into existence exactly once, by capsule publication’s atomic
no-replace rename, and a second creator here would target the very directory
that rename must claim – measured, bucket_paths(...).bucket_dir and the
capsule commit marker’s parent are the same path. The test-only provisioner
that used to live here now sits in the wheel-excluded test package.
The typed BucketPaths record carries each resolved subpath so callers
never compose the layout themselves.
- class BucketPaths(**data)[source]¶
Bases:
BaseModelTyped record carrying the resolved paths for one bucket directory.
- Parameters:
data (Any)
- bucket_id: BucketId¶
- root: Path¶
- bucket_dir: Path¶
- db_dir: Path¶
- blobs_dir: Path¶
- database_file: Path¶
The bucket’s database file.
Resolved here rather than composed by callers. Seventeen test modules were hand-building it as
storage_root / "buckets" / id / "db" / "cadrumo.db"precisely because this record carried every sibling directory but not the one file inside them.The composition is easy to get wrong in a way that fails late:
BUCKET_DATABASE_FILE’s subpath isdb/cadrumo.db– bucket-relative, already carrying its owndb/segment – so it joins ontobucket_dir. Joining it ontodb_diryields<bucket>/db/db/cadrumo.db, a path nothing creates, surfacing as a missing file rather than a wrong join. That is theblobs/blobsshape: the wrong anchor, not a wrong constant.
- validate_path_component(value, *, subject)[source]¶
Refuse
valueunless it is one path component that stays where it is joined.Every storage path built from a caller-supplied string needs this same answer, and the rule was previously written out at each join instead. Three copies drifted apart:
bucket_pathsandkeystore_path()each enumerated the empty and separator cases, and the keystore sidecar join validated itsbucket_idwhile never looking at itsfilenameat all.Four ways a value fails to be a component, each measured against a real join rather than reasoned about:
Empty, which joins to the parent itself.
Carrying a separator, the only case all three copies caught.
A dot segment.
".."carries no separator, so a separator check passes it, and the join then resolves ABOVE the directory being addressed.Drive-qualified.
"D:x"resolves onto another drive entirely, and"C:x"– when the root is already onC:– silently becomes the component"x", so the directory name no longer equals the identifier that named it and two distinct ids can land on one directory.
- Raises:
BucketValidationError – When
valueis not a single containable component;subjectnames the offending parameter.- Return type:
- Parameters:
- bucket_paths(root, bucket_id)[source]¶
Resolve the typed paths for
<root>/buckets/<bucket_id>/without IO.The id is checked by
validate_path_component()before it is joined, so it must be one containable component. Nothing reaches this with a hostile id today – a restored archive’sbucket_idmust equal the custody envelope’sprofile_id, which is a UUID, and the system-scoped ids aresystem,unsecuredanddiagnostic-probe. But that containment lives upstream in an identity cross-check written for another purpose, whileBucketIditself is a 1-128 character string that admits".."happily. Refusing it HERE puts the guarantee at the boundary that owns the join.- Parameters:
root (
Path) – Cadrumo storage root (the parent ofbuckets/).bucket_id (
str) – The bucket identifier; must be one path component.
- Return type:
- Returns:
A
BucketPathsrecord carrying every resolved subpath.- Raises:
BucketValidationError – When
bucket_idis not a single containable path component; seevalidate_path_component().