cadrumo.adapters.persistence.storage.sql.secure_objects module¶
Encrypted SQL byte-object repository for sensitive application payloads.
- class SecureObjectMigrationTarget(namespace, object_key, expected_class, current_version)[source]¶
Bases:
NamedTupleOne explicitly governed row participating in an atomic schema cutover.
- Parameters:
namespace (str)
object_key (str)
expected_class (SensitivityClass)
current_version (int)
-
expected_class:
SensitivityClass¶ Alias for field number 2
- class SecureObjectRepository(*, engine=None, namespace_registry=None, active_session_bucket_id=None, require_secure_active_session=False)[source]¶
Bases:
SecureObjectWriteOperationsRepository over encrypted byte objects stored in the primary database.
- Parameters:
engine (Engine | None)
namespace_registry (StorageHierarchyRegistry | None)
active_session_bucket_id (str | None)
require_secure_active_session (bool)
- static object_key_digest(object_key)[source]¶
Derive the stored lookup digest for one natural object key.
- property namespace_registry: StorageHierarchyRegistry | None¶
Return the bound hierarchy registry, if any.
The registry is a
StorageHierarchyRegistry.
- property engine: Engine¶
Return the bound SQLAlchemy
Engine.Exposed so a sibling plaintext ORM table (e.g. a derived, non-sensitive routing index) can be written in the SAME database file and, where the driver supports it, the same transaction as this repository’s encrypted rows – without duplicating the bucket-to-engine routing this repository already resolved at construction.
- guarded_session_scope()[source]¶
Yield a session over this repository’s engine, session-checked first.
The counterpart to
enginefor a sibling plaintext table. Taking the engine directly is the one route into this database that skips_check_session_freshness(), which every operation on this repository otherwise applies – so a caller reading a routing index through a raw scope reads after a session seal, an idle expiry, or a move to another bucket, with nothing to stop it.The existing callers are safe only because each happens to perform a guarded load first. That is protection by call ORDER: it holds until someone adds an entry point that reaches the raw read first, and nothing states the ordering or would notice it changing. This method makes the check structural rather than incidental.
No namespace is passed: a sibling table is not a secure-object namespace, so only the session half of the check applies.
- exists_many(namespace, object_keys)[source]¶
Return the subset of
object_keyspresent innamespace.The set-based companion of
exists(), and the implementation behind it: one indexedIN (...)read per key slice answers the whole membership question, where per-keyexists()calls cost one session and one statement each — the dominant cost of a bulk content-addressed ingest that deduplicates against what is already stored. Natural keys are HMAC-digested exactly as theobject_keycolumn stores them, so membership is decided on the digest and reported back as the caller’s natural keys. Nothing is decrypted.
- exists_by_raw_key(namespace, hashed_object_key)[source]¶
Return whether
namespacecarries a row with this raw HMAC digest.Used by the archive restore pipeline when the natural key was not present in the source bundle. Same master-key constraint as
save_with_raw_key().
- iter_all_records_raw(*, namespace=None, batch_size=256)[source]¶
Yield every stored row as a
SecureObjectRawRowwithout decryption.Walks every row in secure_objects ordered by (namespace, object_key) without attempting to decrypt the payload. The query bypasses the encrypted-column type decorators so rows sealed under a rotated master key still surface verbatim — this is what the outbound sync coordinator’s ciphertext-layer mirror consumes, mirroring on-wire ciphertext to a remote storage provider without ever decrypting domain data.
- Parameters:
namespace (
str|None) – Optional exact namespace filter applied by SQL before iteration.Nonepreserves the archive-wide traversal.batch_size (
int) – SQLAlchemy yield_per chunk size. The default keeps memory bounded for very large substrates while still amortising session overhead across multiple rows.
- Yields:
One SecureObjectRawRow per persisted row. The order is (namespace ASC, object_key ASC) so consumers can checkpoint progress deterministically.
- Return type:
- list_namespaces()[source]¶
Return the distinct namespaces present in
secure_objectssorted.Used by the integrity diagnostic so consumers do not have to hardcode the namespace list (which drifts as new domain repositories register their own namespaces).
- quarantine_unreadable_rows()[source]¶
Move every undecryptable row into
secure_objects_quarantine.Iterates every populated namespace, probes each row’s payload through the row-identity-bound secure-object decryptor, and for rows that fail tag verification copies the original (encrypted) payload plus all metadata into the quarantine table, then deletes the row from
secure_objects. The quarantine table mirrorssecure_objectswith the addition of aquarantined_attimestamp so the archive is auditable.Decryptable rows are NOT touched; the quarantine table is created on first use; nothing is auto-deleted from the user’s data even after quarantine. The operator can recover the quarantined rows manually from the table if a missing master key is later recovered (for example, restored from a recovery key backup).
- Return type:
- Returns:
A tuple of
SecureObjectNamespaceIntegrityrecords describing how many rows were quarantined per namespace.
- probe_namespace_integrity(namespace)[source]¶
Count decryptable and undecryptable rows in
namespace.Returns a
SecureObjectNamespaceIntegrityfor the namespace.This method answers a strictly crypto-layer question – can the
payloadciphertext be unwrapped under the current master key – and intentionally bypasses the classification, schema-version, and namespace-registration contracts that consumer reads enforce. It probes over whatever namespaces are physically present in the table (as surfaced bylist_namespaces()), including orphan, legacy, and unregistered ones – those are precisely the rows a repair diagnostic exists to find – so it runs the session / route freshness check but not the namespace-registration check. Used byaeat config repairto surface namespaces holding rows from a prior keychain master-key generation.- Return type:
- Parameters:
namespace (str)
- iter_namespace_decryptability(namespace)[source]¶
Yield
SecureObjectDecryptabilityRowmetadata for one namespace.This is the row-level companion to
probe_namespace_integrity(). It decrypts only to validate the AEAD tag, never returns plaintext, and exposes the HMAC lookup digest plus storage metadata needed by repair diagnostics. Like its namespace-level companion it is a crypto-layer probe over whatever namespaces are physically present, so it runs the session / route freshness check but not the namespace-registration check.- Return type:
- Parameters:
namespace (str)
- list_keys(namespace)[source]¶
Return stored lookup digests under
namespaceas hex strings.Natural object keys are HMAC digested before storage and cannot be recovered from the index. Domain repositories that need natural IDs should iterate
list_records()and read IDs from decrypted payloads.
- list_records(namespace, *, expected_class, max_supported_version)[source]¶
Yield secure-object rows under
namespaceor fail on unreadable rows.The default listing path is fail-closed: it walks the namespace through
iter_records_with_failures()and raisesSecureObjectUnreadableErrorbefore yielding a partial readable subset. Useiter_records_with_failuresfor explicit mixed readable/unreadable diagnostics.- Parameters:
namespace (
str) – The storage namespace whose rows are listed.expected_class (
SensitivityClass) – TheSensitivityClassall rows in this namespace must carry.max_supported_version (
int) – The consumer’s currentschema_versionceiling; a row above it, or below it without a complete registered upgrade chain, is treated as unreadable.
- Return type:
- load_many(namespace, object_keys, *, expected_class, max_supported_version)[source]¶
Yield requested secure-object rows or fail closed on unreadable rows.
This is the targeted equivalent of
list_records(): it performs a singleWHERE namespace = ? AND object_key IN (...)read for the requested natural keys, decrypts matching rows, and raisesSecureObjectUnreadableErrorbefore yielding a partial readable subset if any matching row is unreadable. Missing keys are omitted, mirroring repeatedload()calls that returnNonefor absent rows.expected_classis theSensitivityClassevery returned row must be classified under; a mismatch fails closed.- Return type:
- Parameters:
namespace (str)
expected_class (SensitivityClass)
max_supported_version (int)
- load_many_current(namespace, object_keys, *, expected_class, current_version, refuse_legacy)[source]¶
Load exact current-version rows in one addressed snapshot.
Every selected outer row is checked before any payload is decrypted or yielded. A legacy row therefore reaches the owning repository’s explicit-cutover refusal without an implicit upgrade or partial read.
Core types:
SensitivityClass.- Return type:
- Parameters:
- migrate_many_atomically(namespace, object_keys, *, expected_class, current_version, validate_upgraded_payloads, write_provenance)[source]¶
Validate every upgraded payload, then persist all replacements atomically.
Older rows are decrypted and chain-upgraded through the normal read policy. The caller receives the complete natural-keyed payload set in
validate_upgraded_payloadsbefore any replacement is written. Only after that callback succeeds are all older rows replaced in one compare-and-swap batch, so a malformed sibling or concurrent write leaves every original row intact.Core types:
SensitivityClass.
- migrate_targets_atomically(targets, *, validate_upgraded_payloads, write_provenance)[source]¶
Validate and CAS-replace an explicit cross-namespace target set once.
- iter_many_with_failures(namespace, object_keys, *, expected_class, max_supported_version)[source]¶
Yield readable/unreadable outcomes for requested natural object keys.
Rows are selected by raw HMAC digests derived from
object_keysand returned in stored digest order. Missing keys produce no item, matchingload()returningNone. Present rows use the same classification, schema-version, AEAD, and revision-lineage checks as namespace scans.expected_classis theSensitivityClassevery yielded row must be classified under; a mismatch fails closed.- Return type:
- Parameters:
namespace (str)
expected_class (SensitivityClass)
max_supported_version (int)
- iter_records_with_failures(namespace, *, expected_class, max_supported_version, batch_size=256)[source]¶
Yield a typed outcome per stored row under
namespace.Each row is represented by either a
SecureObjectRecord(the row decrypts cleanly and matches the consumer’s classification and schema-version contract) or aSecureObjectUnreadable(the on-wire ciphertext exists but cannot be decrypted under the current master key, or its metadata fails the consumer’s contract).The iterator is fault-isolated: a failure on row
Ndoes not prevent rows> Nfrom being inspected. Consumers count the failures and decide how to report them; nothing is auto-deleted.- Parameters:
namespace (
str) – The storage namespace whose rows are scanned.expected_class (
SensitivityClass) – TheSensitivityClassall rows in this namespace must carry; rows with a differing classification are yielded asSecureObjectUnreadable.max_supported_version (
int) – The consumer’s currentschema_versionceiling. Rows above it, or below it without a complete registered upgrade chain, are yielded asSecureObjectUnreadable.batch_size (
int) – SQLAlchemyyield_perchunk size for the raw row scan. The default keeps memory bounded for large namespaces while preserving deterministic(object_key ASC)order.
- Yields:
One
SecureObjectListItemper stored row — either aSecureObjectRecordor aSecureObjectUnreadable.- Raises:
StorageValidationError – When
batch_sizeis less than 1.- Return type:
- load(namespace, object_key, *, expected_class, max_supported_version)[source]¶
Load and decrypt one secure-object row, returning
Nonewhen absent.Returns a
SecureObjectRecordwhen the row is present and decrypts under the expected class/version.- Parameters:
namespace (
str) – The storage namespace to look in.object_key (
str) – The natural string key identifying the record.expected_class (
SensitivityClass) – TheSensitivityClassthe consumer expects.max_supported_version (
int) – Highestschema_versionthe consumer supports.
- Return type:
- peek_metadata(namespace, object_key)[source]¶
Return
SecureObjectMetadatafor one object without decrypting it.Returns
Nonewhen no row matches. Never decrypts the payload column; callers use this to fingerprint an envelope they intend to discard (e.g. the workflow-state reset recovery path).- Return type:
- Parameters:
- peek_many_schema_versions(namespace, object_keys)[source]¶
Return schema versions for exact natural keys without loading ciphertext.