cadrumo.adapters.persistence.storage.schema_lineage module¶
Schema identity checks for persisted secure-object payloads.
A persisted secure object passes two version gates on the way to a consumer, and they hold deliberately different contracts.
Layer one guards the outer SQL row. ensure_schema_version_readable()
requires the exact version the consumer declares. A version above it is
refused as written by a newer application; a version below it has no reader in
this build and is refused before decryption.
Layer two guards the Envelope inside the decrypted payload, and its
contract is strict EQUALITY — inner_envelope_version_is_current().
Equality is right there precisely because layer one has already established
the current row version, so an inner deviation is drift or corruption. The two
layers have distinct defining modules so a layer-two caller cannot reach for
the wrong gate.
Layer two carries a second equality alongside the version one:
inner_envelope_classification_is_expected() re-checks the inner
envelope’s SensitivityClass against
what the caller’s own namespace declares, as defense-in-depth against a row
whose embedded payload metadata has drifted from the outer columns layer one
already gated. Callers use this predicate so the equality rule remains owned
by the schema-lineage boundary regardless of their domain-specific refusal
type or message.
See also
secure_object_record_from_row()Row decode path that applies this policy before returning a record.
- ensure_schema_version_readable(*, namespace, schema_version, current_version)[source]¶
Refuse a stored version the current application cannot read.
A version above
current_versionwas written by a newer application. A version below it has no reader in this build. Both refuse before decrypting.- Raises:
EnvelopeVersionError – When
schema_versionexceedscurrent_versionor falls below it.- Return type:
- Parameters:
- inner_envelope_version_is_current(stored_version, current_version)[source]¶
Return whether a decrypted payload’s inner envelope sits at the current version.
This is the layer two contract described in the module docstring, and it is an equality rather than a ceiling. By the time a consumer validates the inner
Envelope, layer one has already refused a future outer row or chain-upgraded an older one tocurrent_version, so the only remaining ways an inner stamp can differ are drift and corruption — neither of which a ceiling detects on the below-current side.The below-current side is what gives this predicate its teeth. The row codec re-stamps the OUTER record to the current version unconditionally, while the inner stamp lives in the payload bytes and moves only if a registered upgrader rewrites that field. An upgrader that transforms payload shape but forgets the inner version therefore yields exactly a below-current inner stamp on a row layer one has already declared current, and this equality is the ONLY place that is detectable at read time.
The predicate does not raise, by contract. Callers own their refusal: each read path raises its own exception class carrying its own translated message key and the per-object diagnostics (object key, bucket id, amendment id, observation and snapshot labels) that identify which row is unreadable. At least one caller is additionally ordering-sensitive — its raise sits inside a
trywhoseexceptclause names the exception types it expects — so a raising helper would silently re-route that path even though the comparison itself is unchanged.- Parameters:
stored_version (
int) – Theschema_versionread from the inner envelope.current_version (
int) – The namespace’s declared currentschema_version, which every caller derives from itsSecureObjectNamespaceDefinitionrather than restating as a literal.
- Return type:
- Returns:
Truewhen the stored version is exactly the current version.
- inner_envelope_classification_is_expected(stored, expected)[source]¶
Return whether a decrypted payload’s inner envelope carries the expected class.
The classification sibling of
inner_envelope_version_is_current(): the same layer two defense-in-depth re-check, on the same equality contract, for the same reason. By the time a consumer validates the innerEnvelope, the outer SQL row’sexpected_classargument has already gated the row once; this predicate is the second, independent look at the payload’s own embedded statement, so a row whose payload bytes drifted from its own columns is still caught.The predicate does not raise, by contract — the same contract as its version sibling, and for the same reason. Callers own their refusal: each read path raises its own exception class (
ClassificationErrorat most sites, but six catalogue repositories raise their own domain-specific*PersistenceErrorinstead) carrying its own translated message key and per-object diagnostics. A raising helper would silently re-route every one of those differently-typed refusals even though the comparison itself is identical everywhere.- Parameters:
stored (
SensitivityClass) – The classification read from the inner envelope.expected (
SensitivityClass) – The classification the caller’s namespace declares, which every caller derives from its ownSensitivityClassconstant orSecureObjectNamespaceDefinitionrather than restating as a literal.
- Return type:
- Returns:
Truewhen the stored classification is exactly the expected one.