cadrumo.adapters.persistence.storage.sql._secure_object_row_codec module¶
Row-level codec helpers for SQL secure-object records.
This module keeps the encrypted row decode path close to the SQL secure-object adapter without leaving the algorithm embedded in the repository class. It validates row classification and exact schema identity before decrypting, and refuses rows whose revision hashes no longer match their stored metadata. Revision lineage is derived and stamped by the repository’s write funnel inside the same statement that persists the ciphertext.
See also
SecureObjectRepositoryRepository that owns the write funnel and delegates row decoding here.
derive_revision_id()Deterministic revision-id primitive the write funnel stamps rows with.
verify_revision_self_consistency()Integrity check applied before decrypting an existing row.
build_revision_ancestor_ids()Revision-lineage helper used to persist ancestor chains.
SecureObjectRecordPlaintext record returned after classification, schema, lineage, and AEAD checks pass.
secure_object_payload_aad()Associated-data builder that binds ciphertext to row identity.
SensitivityClassExpected row classification validated before a row is decoded.
- decode_secure_object_row(*, namespace, row_id, object_key, classification_str, schema_version, written_at, payload_wire, revision_id, previous_revision_id, payload_hash, ciphertext_hash, previous_payload_hash, expected_class, max_supported_version, namespace_definition, enforce_registered_row_schema)[source]¶
Validate and decrypt one secure-object row, raising on every failure.
The single decode pipeline behind both read surfaces. It always RAISES; choosing between propagating that failure and converting it to an unreadable row is the caller’s policy, so the two surfaces cannot drift in WHAT they check or in WHICH ORDER, only in how they report.
Order is part of the contract, and is deliberately integrity-before-crypto:
classification is a known value, and is the one this namespace expects
the stored schema version is readable, and registered for the namespace
revision lineage is self-consistent
the AEAD opens
the current-version plaintext is returned
Step 3 runs before step 4 on purpose: a row whose stored lineage metadata already contradicts itself is refused without spending an AEAD open on it. That metadata is UNAUTHENTICATED until step 4 verifies the tag, so the pre-decrypt check is strictly refuse-only – it can reject a row, and it can do nothing else. It never marks a row readable, never skips a later step, and never selects a decode branch, so hostile header metadata cannot buy an attacker anything beyond a refusal they could have caused anyway.
- Parameters:
namespace (
str) – The secure-object namespace the row was stored under; it selects the registered row schema and scopes the diagnostics.row_id (
int) – The row’s primary key, carried only so a refusal can name the offending row.object_key (
bytes) – The row’s opaque object key, used as AEAD associated data so a payload cannot be replayed under a different key.classification_str (
str) – The classification exactly as stored. Parsed in step 1 and matched againstexpected_class.schema_version (
int) – The payload schema version as stored. Checked for exact readability and namespace registration in step 2.written_at (
datetime) – The row’s stored write timestamp, carried onto the decoded record.payload_wire (
bytes) – The stored wire bytes – the sealed envelope the AEAD opens in step 4.revision_id (
str|None) – The row’s own revision identifier, orNonewhen the row carries no revision metadata.previous_revision_id (
str|None) – The revision this row supersedes, orNonefor a chain head.payload_hash (
str|None) – Digest of this row’s plaintext payload, orNone.ciphertext_hash (
str|None) – Digest of this row’s wire bytes, orNone.previous_payload_hash (
str|None) – Digest of the superseded row’s payload, orNone. Together with the two revision ids this forms the lineage triple checked in step 3.expected_class (
SensitivityClass) – TheSensitivityClassthis namespace declares; the stored row’s own classification must match it exactly.max_supported_version (
int) – The highest schema version this reader can handle. A row on either side is refused rather than guessed at.namespace_definition (
SecureObjectNamespaceDefinition|None) – The namespace’s registered definition, orNonewhen the namespace is unregistered.enforce_registered_row_schema (
Callable[...,None]) – The namespace-registration check invoked in step 2. Injected so the codec does not reach back into the registry, and so tests can drive registration failures directly.
- Raises:
ClassificationError – Unknown classification, or one that does not match the namespace’s expected class.
EnvelopeVersionError – Schema version unreadable or unregistered for the namespace.
SecureObjectUnreadableError – Revision lineage self-consistency failed, which includes a row carrying no revision metadata at all.
DecryptionError – The AEAD did not open.
- Return type:
- secure_object_record_from_row(row, *, expected_class, max_supported_version, namespace_definition=None, enforce_registered_row_schema)[source]¶
Decode one ORM row, propagating the typed failure.
Fail-closed policy: a caller loading a specific row wants the typed error, not a placeholder. Normalisation of the ORM row is the only thing this wrapper adds; every check lives in
decode_secure_object_row().- Parameters:
row (
SecureObjectRow) – The ORM row to decode. Its columns are coerced to the concrete types the codec requires (intid,bytesobject key and payload) and forwarded; nothing is otherwise interpreted here.expected_class (
SensitivityClass) – TheSensitivityClassthe row’s own classification must match, forwarded verbatim todecode_secure_object_row().max_supported_version (
int) – The highest schema version this reader can handle, forwarded verbatim.namespace_definition (
SecureObjectNamespaceDefinition|None) – The namespace’s registered definition, orNonewhen unregistered, forwarded verbatim.enforce_registered_row_schema (
Callable[...,None]) – The namespace-registration check, forwarded verbatim.
- Return type:
- secure_object_list_item_from_raw_row(raw, *, namespace, expected_class, max_supported_version, namespace_definition, enforce_registered_row_schema)[source]¶
Decode one raw
iter_records_with_failuresrow into a typed outcome.expected_classis theSensitivityClassrequired for the namespace.Fault-isolated: every failure mode (unknown classification, classification mismatch, unreadable schema version, revision-lineage inconsistency, decrypt failure, upgrade failure) returns a
SecureObjectUnreadablecarrying the reason instead of raising, so a caller iterating many rows can attribute a failure to its own row and keep inspecting the rest.Fault isolation is the ONLY thing this wrapper adds to
decode_secure_object_row(); the checks and their order live there, so the batch surface cannot validate a row differently from the single-row surface. NoteSecureObjectUnreadableErrorsubclassesDecryptionErrorand so must be caught first.- Return type:
- Parameters:
raw (object)
namespace (str)
expected_class (SensitivityClass)
max_supported_version (int)
namespace_definition (SecureObjectNamespaceDefinition | None)
enforce_registered_row_schema (Callable[[...], None])