cadrumo.core.storage_taxonomy module

The typed authority for every application-chosen on-disk location.

Where a byte lands used to be decided by four mutually unaware authorities: an untyped dict[str, str] in the settings module, bare string constants in the persistence namespace registry, module-local constants in application and entrypoint modules, and unpinned inline copies of the registry’s own names. A name declared in the adapter layer could not be imported by core without inverting the hexagonal direction, so the duplicates were unfixable where they sat. This module is the single declaration all of them collapse onto: every application-chosen name lives here, in core, and the adapter layer consumes it downward.

Six axes ride on each member because a bare name cannot carry them:

  • the literal POSIX relative subpath, verbatim as it exists on disk;

  • StorageNodeKind, so file-versus-directory is a type-level fact rather than a guess made from a field-name suffix – a guess cannot reach the per-bucket file names no suffix convention governs, and would put a directory exactly where a document must be written;

  • StorageScope, because blobs and audit each legitimately name both a top-level category and a per-bucket subdirectory at a different depth, so members are identified by scope and name together;

  • StorageOverridePolicy, so the guarantee that an operator cannot relocate a keystore out from under the bucket it unlocks is declared data rather than an accident of which module a constant sat in;

  • StorageLifecycle and StorageGrouping, the conceptual classifications that answer what may be reclaimed and what belongs together;

  • FingerprintParticipation, an independent third axis that no union of the other two reproduces, governing what the drift fingerprint covers.

The conceptual grouping has no effect on the resolved path. cache/ is the sole on-disk category prefix and remains so: the encrypted-state substrate, the diagnostic logs, and the durable outputs keep bare, self-describing leaf names.

See also

Settings

Central settings aggregate whose path fields these members name.

storage_path()

Resolver for a root-scoped member.

bucket_scoped_storage_path()

Resolver for a bucket- or keystore-scoped member.

class StorageNodeKind(*values)[source]

Bases: StrEnum

Whether a member names a directory or a single file.

Declared rather than inferred. A name-suffix heuristic cannot reach the per-bucket file names – manifest.toml, .lock, the keystore sidecars – that follow no suffix convention, and inferring wrongly creates a directory where a document belongs, which fails much later at the write.

A second enum names the same axis one layer out: the storage adapter’s StoragePathKind answers a wider question, adding a member for a row living in the encrypted database rather than on disk and one for content-addressed blob content. The two are deliberately not merged – core must not import an adapter type, and a StrEnum carrying members cannot later be extended to subclass another – so the relationship is a declared parity rather than a shared definition: DIRECTORY and FILE must spell their values identically on both sides. Both are StrEnum, so code compares them by value across the boundary and a divergent spelling would return False rather than raising. A gate pins that overlap, and pins only the overlap, so the adapter stays free to grow members core has no use for.

DIRECTORY
FILE
class StorageScope(*values)[source]

Bases: StrEnum

The anchor a member’s subpath is relative to.

Scope is a required part of a member’s identity, not a decoration: the top-level blobs and audit categories share their names with per-bucket subdirectories at a different depth, and both are correct. Keying on name alone would conflate them.

The keystore anchor is a second, independent bucket-id-parameterized root, not a nested subdirectory of the bucket it unlocks. Production states this as the load-bearing invariant it is (validate_keystore_separation()): the keystore lives at <root>/keystore/<bucket-id>/, sibling to buckets/, and a configuration that resolves it under buckets/ is refused so a later unlock cannot silently violate the separation. KEYSTORE_ROOT names that per-bucket keystore directory itself – parameterized by a bucket id like BUCKET_RELATIVE, but anchored at <root>/keystore/ rather than <root>/buckets/ – and KEYSTORE_RELATIVE names what nests beneath it.

ROOT
BUCKET_RELATIVE
KEYSTORE_ROOT
KEYSTORE_RELATIVE
class StorageOverridePolicy(*values)[source]

Bases: StrEnum

Whether an operator may relocate a member.

The bucket and keystore layout is deliberately fixed. An operator must not be able to move a keystore out from under the bucket it unlocks, and that guarantee is expressed here as data so it can be read and enforced, rather than resting on which module a constant happened to sit in.

OPERATOR_OVERRIDABLE
FIXED
class StorageLifecycle(*values)[source]

Bases: StrEnum

How a member’s contents are bounded over time.

Governs what a reclaim operation may delete. UNBOUNDED_BY_DESIGN covers the substrate a filing is defended with: growth is the point, and pruning it would destroy evidence.

UNBOUNDED_BY_DESIGN
RETENTION
ROTATION
TTL
class StorageGrouping(*values)[source]

Bases: StrEnum

The conceptual family a member belongs to.

Purely a classification for operator-facing presentation and reclaim grouping. It has no effect on the resolved path, so a member grouped as a cache need not sit under the cache/ prefix and several do not.

STATE
LOGS
CACHE
EXPORTS
class StorageArea(*values)[source]

Bases: StrEnum

Stable operator vocabulary for the four aggregate storage families.

STATE
LOGS
CACHE
EXPORTS
class StorageCustodyProfile(*values)[source]

Bases: StrEnum

Portable-profile custody depth selected by application policy.

FULL
STRUCTURED
class FingerprintParticipation(*values)[source]

Bases: StrEnum

Whether writes beneath a member move the data-root drift digest.

An independent axis, never derived. Measured against every candidate, no expression over lifecycle or grouping reproduces the shipped set, and three candidates differ in both directions at once – omitting members that are excluded while adding members that are not. Getting it wrong is invisible in both directions: excluding too much walks the digest toward the empty-tree constant that once defeated drift detection outright, and excluding too little churns it on every cache write until an operator learns to distrust the refusal.

PARTICIPATING
EXCLUDED
STORAGE_ROOT_SETTINGS_FIELD: Final[str]

The settings field naming the anchor every root-scoped member resolves against.

Neither a member nor an escape, and given its own name so it cannot be mistaken for either. It is not a member because the taxonomy declares locations relative to it – a member for the root would be a member whose subpath is itself. It is not an escape because the application both chooses it and writes beneath it. It is therefore the anchor rather than a member.

class StorageCategory(*values)[source]

Bases: StrEnum

Every application-chosen on-disk location, identified by scope and name.

Root-scoped members carry their bare category name. Bucket- and keystore-scoped members carry a scope-qualifying prefix, which is what keeps root-scoped names distinct from per-bucket subdirectories.

TOKENS
SECRETS
BLOBS
LIVE_STATE
LIVE_STATE_IVA_WALLET
LIVE_STATE_IVA_REMOTE_STATE
LIVE_STATE_IVA_REMOTE_STATE_FILED_HISTORY
LIVE_STATE_IVA_REMOTE_STATE_WALLET
LOGS
LOG_FILE
LLM_USAGE
LLM_RUN_TELEMETRY
RUNS
LLM_CACHE
CORPUS_SEARCH_CACHE
CORPUS_SEARCH_INDEX
SUBMISSIONS
SUBMISSIONS_AMENDMENT_RESULTS
SUBMISSIONS_AMENDMENTS
WORKFLOW_RUNS
DRAFTS
JUSTIFICANTES
FILING_HISTORY
FILED_DECLARATIONS
IVA_COMPENSATION_HISTORY
IVA_READ_EVIDENCE
FINANCIAL_TRANSACTIONS
INVOICES
ATTACHMENTS
ATTACHMENTS_MANIFESTS
USAGE_RATIOS
BUCKETS
ACTIVE_PROFILE_POINTER
ROOT_FALLBACK_DATABASE
CONFIG_RESET_JOURNAL
OPERATION_JOURNAL
PROFILE_CUSTODY_TRANSACTION_JOURNAL
PROFILE_CUSTODY_RECEIPT
PROFILE_CUSTODY_HOLD_EVIDENCE
PROFILE_CUSTODY_HOLD_FILING_OWNER
PROFILE_CUSTODY_HOLD_DERIVED_EVIDENCE
PROFILE_CUSTODY_LABEL_HEAD
LOCAL_READER_FITNESS_VERDICTS
BUCKET_DATABASE
BUCKET_DATABASE_FILE
BUCKET_BLOBS
BUCKET_MANIFEST
BUCKET_LOCK
BUCKET_OUTPUT_LANGUAGE_HINT
BUCKET_KEYSTORE
PROFILE_CAPSULE_CUSTODY
PROFILE_CAPSULE_PASSWORD_ENVELOPE
PROFILE_CAPSULE_RECOVERY_ENVELOPE
PROFILE_CAPSULE_DATA
PROFILE_CAPSULE_COMMIT
KEYSTORE_PROFILE_SESSION
KEYSTORE_PROFILE_SESSION_RETIREMENT
KEYSTORE_LOGIN_THROTTLE
class StorageLocation(**data)[source]

Bases: BaseModel

One declared location and every axis a bare name cannot carry.

Frozen and strict: the taxonomy is a declaration, and a mutable one would let a consumer rewrite the authority it is reading.

Parameters:
category: StorageCategory
subpath: str

POSIX-style path relative to this member’s scope anchor, verbatim.

node_kind: StorageNodeKind
scope: StorageScope
override_policy: StorageOverridePolicy
lifecycle: StorageLifecycle
grouping: StorageGrouping
fingerprint_participation: FingerprintParticipation
settings_field: str | None

Flat Settings attribute holding the resolved path.

Members name their settings field rather than absorbing it. Path fields stay flat, introspectable attributes on the settings model because the structural gates discover their subject by walking Settings.model_fields; a model that hid them behind a nested object or a property would leave those gates discovering nothing, which passes silently and covers nothing.

derives_settings_default: bool

Whether settings validation computes this field’s default from the root.

False for a member whose field is a deliberate opt-in override. The registry disk cache is the worked case: its name is governed here, but its field must stay absent by default because its resolver selects a shared temporary location under pytest precisely by observing that the field is unset. Governing the name and deriving the field are separate decisions.

consumer_module: str | None

Production module that consumes this location, relative to src/cadrumo.

Names the module, and a structural gate verifies the claim: that module must contain a real reference to this member – an attribute load of the bound settings field, an attribute load of the category member, or the field name as a non-docstring string constant, which is how the live-evidence roots are reached. A name appearing in prose satisfies nothing.

The claim is deliberately consumption rather than write-reachability. Tracing statically from an attribute to a filesystem write produces a false negative on every indirection, and a gate that blocks legitimate changes is a gate somebody switches off. Consumption is weaker in theory and far stronger in practice, because a location no module touches cannot be written to – which is the condition worth catching.

Exactly one of this and dormant_reason is set.

dormant_reason: str | None

Why this member has no consuming module, when it has none.

A declared location nothing reads or writes is a decision waiting to be taken – wire it or delete it – and the point of requiring the reason is that the decision becomes visible in the declaration instead of waiting to be rediscovered by an audit. Exactly one of this and consumer_module is set.

relative_path()[source]

Return subpath as a path relative to this member’s anchor.

Return type:

Path