cadrumo.application.user_profile.capsule_record module

Authenticated current-profile records in the capsule-local secure-object DB.

The profile record is one encrypted USER_PROFILE_VALUE_NAMESPACE row in the capsule’s normal SQLite substrate. It is never a sidecar JSON document. Every create or replacement co-writes the row and the canonical bucket-event history through one SecureObjectRepository.apply_batch transaction.

exception ProfileRecordConflictError(message=None, *, context=None, translated_message=None)[source]

Bases: UserProfileError

The authenticated record changed before its CAS command committed.

Joins the UserProfileError family so the refusal binds to the error registry and one clause still catches the whole user-profile surface. ValueError is retained deliberately: it is load-bearing ancestry here, not decoration – see ProfileRecordIntegrityError.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
exception ProfileRecordIntegrityError(message=None, *, context=None, translated_message=None)[source]

Bases: UserProfileError

A current-record row or its event witness is malformed or mis-bound.

Joins the UserProfileError family so the refusal binds to the error registry. ValueError is retained because the lifecycle restore path converts a failed authenticated validation into its own refusal through a ValueError arm; dropping the builtin ancestry would let the inner refusal escape that conversion and reach the operator naming a stage it never reached.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
class ProfileRecordCommandEvent(**data)[source]

Bases: BaseModel

Non-secret command witness co-written into canonical bucket history.

Parameters:
event_type: BucketEventType
occurred_at: str
payload: Mapping[str, str]
class LoadedProfileRecord(record, row_revision_id, previous_row_revision_id, event_id)[source]

Bases: object

One verified current record together with its secure-object CAS token.

Parameters:
record: UserProfileRecord
row_revision_id: str
previous_row_revision_id: str | None
event_id: str
class ProfileRecordSession(profile_id, envelope_digest, password_generation, dek_epoch, _dek, profile_decode_context)[source]

Bases: object

Short-lived authority bound to one unlocked custody envelope and DEK.

Parameters:
profile_id: UUID
envelope_digest: str
password_generation: int
dek_epoch: str
profile_decode_context: ProfileDecodeContext
classmethod from_envelope(*, envelope, dek, profile_decode_context)[source]

Create a record authority from an unlocked envelope and its DEK.

Return type:

ProfileRecordSession

Parameters:
close()[source]

Zeroise the DEK in place, retiring this authority without discarding it.

Return type:

None

property closed: bool

Whether the DEK has been zeroised, so this authority can no longer decrypt.

Retirement is destructive but not observable from the outside: close zeroises the key in place and leaves the object, its UUID and its envelope binding intact, so a holder cannot tell a retired authority from a live one by looking at it. Anything deciding whether to REUSE a session has to ask, and before this predicate existed the only way to find out was to ask for the key and catch the refusal – an integrity error raised at the point of use, far from the reuse decision that caused it.

Read as the absence of key material rather than as a separate flag, so there is one definition of retired rather than a boolean a future close variant could forget to set.

encryption_key()[source]

Return the DEK only to the local secure-object session bridge.

Return type:

bytes

create_context()[source]

Reuse this session’s pinned generation for a replacement write.

Return type:

ProfileCreateContext

assert_initial_record(record)[source]

Validate that a record is the first authenticated revision for this session.

Core types: UserProfileRecord.

Return type:

None

Parameters:

record (UserProfileRecord)

assert_replacement(current, replacement)[source]

Validate that a replacement advances the authenticated current record.

Core types: UserProfileRecord.

Return type:

None

Parameters:
write_provenance(record)[source]

Return the strict row header cross-bound to the encrypted payload.

Core types: UserProfileRecord.

Return type:

str

Parameters:

record (UserProfileRecord)

assert_row_binding(raw, record)[source]

Authenticate the row header against the unlocked envelope and record.

Core types: UserProfileRecord.

Return type:

None

Parameters:
profile_record_object_key(profile_id)[source]

Return the registered natural key for the one current profile record.

Return type:

str

Parameters:

profile_id (UUID)

class ProfileRecordStore(*, session, root=None)[source]

Bases: object

Strict current-record storage over the capsule’s canonical secure DB.

Parameters:
load()[source]

Return the one authenticated current row and its secure CAS lineage.

Return type:

LoadedProfileRecord

history()[source]

Return the append-only authenticated history for this profile record.

Return type:

tuple[BucketEvent, ...]

create_initial(record, *, stage_path)[source]

Write revision one and its creation event before the stage commit marker.

Core types: UserProfileRecord.

Return type:

None

Parameters:
validate_staged_database(*, stage_path)[source]

Refuse a restore stage unless its exact current record is authenticated.

Return type:

LoadedProfileRecord

Parameters:

stage_path (Path)

replace(*, replacement, event, expected_revision, expected_content_digest)[source]

CAS-replace the sole current row and append its event in one transaction.

Core types: UserProfileRecord.

Return type:

UserProfileRecord

Parameters:
rehead_under_rotated_envelope(*, rotated, event)[source]

Re-publish the current row’s witness under a re-wrapped envelope.

A passphrase rotation re-mints the password envelope over the SAME data key. The row’s write_provenance folds the envelope digest and the password generation, so after the swap the stored witness names a wrapper that no longer exists and every read refuses. Nothing about the RECORD changed, but its custody witness must.

Read and write use different sessions on purpose, and that asymmetry is the whole method: the stored row can only be authenticated by the session that wrote it, while the replacement can only be authenticated afterwards by the rotated one. Both address the same profile and hold the same key – rotation does not re-key – so the secure-object repository is indifferent to which one opens it.

The revision advances rather than being rewritten in place. A row rewritten under its own revision would leave a revision-one record carrying a predecessor row, which the initial-lineage assertion refuses outright; and the rotation is a real lifecycle fact that ought to appear in the profile’s history rather than mutate its past silently. Content is preserved exactly, so the advance records the custody change and nothing else.

Return type:

UserProfileRecord

Parameters:
stage_initial_profile_record_database(*, stage_path, root, session, record)[source]

Materialise the canonical encrypted DB before the custody commit marker exists.

Core types: UserProfileRecord.

Return type:

None

Parameters:
validate_staged_profile_record_database(*, stage_path, root, session)[source]

Authenticate the exact staged record before restore publication can continue.

Return type:

LoadedProfileRecord

Parameters: