cadrumo.application.user_profile.profile_record_repository module

Session-bound read authority for the capsule-resident current record.

This is intentionally not a generic persistence repository. It cannot create rows, save arbitrary aggregates, delete, tombstone, or reactivate anything. The physical capsule lifecycle stages and publishes bytes; this narrow owner only authenticates the one exact current fact record for an already-bound session.

Core types: UserProfileRecord.

invocation_profile_record_handoff()[source]

Scope one authenticated profile record to one CLI invocation.

CLI composition reads the selected profile once to snapshot its output language. A calculation command can consume that exact authenticated record as its initial profile load. The ContextVar is entered by the CLI root and reset when Click closes the invocation, so a live profile session cannot carry plaintext record data into a later command.

Return type:

Generator[None]

publish_invocation_profile_record(record)[source]

Offer a UserProfileRecord to its current CLI invocation.

Return type:

None

Parameters:

record (UserProfileRecord)

take_invocation_profile_record(profile_id)[source]

Consume the invocation record when it belongs to profile_id.

Return type:

UserProfileRecord | None

Parameters:

profile_id (str | UUID)

bound_profile_record_session(session)[source]

Bind one authenticated record session for the duration of a command.

Return type:

Generator[None]

Parameters:

session (ProfileRecordSession)

activate_profile_record_session(session)[source]

Install the authenticated record authority for the active process session.

The caller has already authenticated and bound the matching custody DEK. A later login must retire the old record authority before publishing the new one, so a profile switch cannot leave facts decryptable through a prior session.

Return type:

None

Parameters:

session (ProfileRecordSession)

bind_active_profile_record_session(session)[source]

Bind a candidate record authority without retiring the prior session.

Handover owns the two-phase lifetime: it first makes B observable, then retires A only after every required B publication succeeds. Returning A lets that owner restore the exact prior authority if a later publication fails, without a transient plaintext re-authentication or a duplicate record-session constructor.

Return type:

ProfileRecordSession | None

Parameters:

session (ProfileRecordSession)

clear_active_profile_record_session_binding(expected)[source]

Clear only an expected active binding without closing its owner.

Candidate cleanup first removes the context reference, then zeroises the candidate. The identity check prevents a late cleanup from unbinding a replacement session installed by a nested operation.

Return type:

None

Parameters:

expected (ProfileRecordSession)

active_profile_record_session()[source]

Return the record session this process currently holds, whatever installed it.

Return type:

ProfileRecordSession | None

close_active_profile_record_session()[source]

Zeroise and clear the process-local record authority.

Return type:

None

profile_record_session_if_authenticated(profile_id, *, profile_decode_context)[source]

Return the record authority serving this UUID, or None when none is live.

This is the structural answer to “is this profile unlocked?”, and callers that need to tell a LOCKED profile from a broken one ask it here rather than inferring the answer from a refusal message. An absent session is an ordinary, expected state – the operator has simply not logged in – so it is reported as an absence rather than raised; require_profile_record_session() converts it for the callers whose contract is a session or nothing.

The installed authority is process-local, so a second profile becoming the live custody session in the same process would otherwise be read through the first profile’s latched authority – or, because the identity guard refuses that, not be readable at all until the process restarts. An authority that does not serve the requested UUID is therefore re-derived rather than refused outright.

Re-derivation stays as narrow as the first derivation: it succeeds only from the requested profile’s already-open custody session, so an unauthenticated profile remains unreadable, and installing the derived authority retires the one it replaces so a switch cannot leave facts decryptable through a prior session.

A latched authority must be LIVE as well as correctly addressed. Retirement zeroises the key in place and leaves the object latched, so the identity check alone cannot tell a retired authority from a working one: a span that binds a session, has it retired under it, and then restores its own prior binding on exit hands the next caller a UUID-matching authority with no key behind it. That reached the caller as an integrity error from deep inside the decrypt, blaming the read for a decision made when the session was reused.

A retired authority is therefore treated exactly as an absent one – it carries no authority, so it is re-derived where a live custody session exists and reported absent where none does. Nothing is unbound on the declining path: a read that declines should not mutate process state, and a successful later derivation replaces the dead reference anyway.

A SESSION-DERIVED authority must also still be BACKED. Login mints it from a live custody session and latches it, and nothing re-checked that derivation afterwards, so closing the bucket session by any path other than the strong logout left the authority behind and facts readable through it: within one process, “logged out” and “record authority gone” were different states, and a health check after a logout still read facts through the survivor. The custody session is therefore consulted on every resolution of a derived authority – an in-memory check, no capsule read – and one whose session is gone declines exactly as an absent one does.

An EXPLICITLY BOUND authority is untouched by this. A caller that opened a record session itself and installed it owns its lifetime; requiring a live custody session behind it would revoke every such binding the moment no profile happened to be logged in, which is a different behaviour, not a stricter one.

Declining does not zeroise it. The record authority and the bucket session are bound together and rebound together during the login handover’s rollback window, so a reader is not the owner that may destroy either; logout_active_profile(), which is the close owner, wipes it there.

A profile_id that is not a canonical UUID still raises: that is a caller defect rather than a lock state, and returning None for it would let a malformed identity read as a merely locked profile.

Return type:

ProfileRecordSession | None

Parameters:
require_profile_record_session(profile_id, *, profile_decode_context)[source]

Return the record authority that serves this exact UUID, or refuse.

The authority resolution itself lives in profile_record_session_if_authenticated(); this door is for callers whose contract admits no absence.

Raises:

ProfileNotFoundError – When no authenticated session serves this UUID, or when profile_id is not a canonical UUID.

Return type:

ProfileRecordSession

Parameters:
class ProfileRecordRepository(*, session, root=None)[source]

Bases: object

Read exactly one encrypted current record through an authenticated session.

Parameters:
classmethod for_current_session(profile_id, *, root=None, profile_decode_context)[source]

Create a repository using the authenticated session for this profile.

Return type:

ProfileRecordRepository

Parameters:
property profile_id: UUID

Return the UUID served by the authenticated record session.

load(profile_id)[source]

Load and authenticate the current record for the requested UUID.

Return type:

UserProfileRecord

Parameters:

profile_id (str | UUID)

complete_setup(profile_id, *, expected_revision, expected_content_digest, now=None)[source]

CAS-complete setup; the lifecycle owns the physical replacement.

Judged through the same authority every fact-writing door uses, but at the strictest setting the authority offers. A profile born incomplete defers exactly the missing-required-field issues, and this promotion is the moment they come due: COMPLETE is not a label for a record that has stopped being edited, it is the CLAIM that nothing required is missing. Promoting without re-applying them publishes that claim about a record that does not support it, and every downstream surface reading setup state then trusts it – which is why this door judges harder than the doors that merely write a fact, rather than the same amount.

The already-COMPLETE early return above stays ahead of the judgement on purpose: it is an idempotent no-op that publishes nothing, so holding it to a contract a stored record may predate would refuse a caller that changes no state.

Raises:
Return type:

UserProfileRecord

Parameters:
apply_fact_changes(profile_id, *, facts, expected_revision, expected_content_digest, event_type, event_payload, now=None)[source]

CAS-publish an explicit fact replacement and its command event.

facts is the complete next exact fact sequence, rather than an implicit upsert patch. Callers must load and compose that sequence first, which keeps every mutation revision-bound and makes a cleared fact as explicit as an added one. The lifecycle publishes the encrypted replacement and its authenticated event in the same capsule data-file replacement.

event_type is the closed catalogue member, never a free string, and the command witness it is carried on declares the same closed type, so a caller-invented string can no longer travel as far as the capsule writer. Taking the member here rather than a string puts the accepted set in front of the caller at the call site, where the alternative was a refusal deep inside the writer that cost the whole command – every fact it carried included.

Return type:

UserProfileRecord

Parameters: