Source code for cadrumo.application.user_profile.login_handover
"""Application contract for a durable profile-login handover witness."""from__future__importannotationsfromdatetimeimportdatetimefromenumimportStrEnumfromtypingimportLiteralfrompydanticimportBaseModel,model_validatorfrom...core.bucket_pointerimportBucketPointerfrom...core.errors.hierarchyimportpydantic_validation_boundaryfrom...core.hashingimportbounded_canonical_json_bytesfrom...core.identity.bucketimportBucketIdfrom...core.modelsimportSTRICT_FROZEN_CONFIGfrom...core.time.utcimportvalidate_utc_awareHANDOVER_JOURNAL_MAX_BYTES=4*1024
[docs]classHandoverPhase(StrEnum):"""Durable boundaries of one password-authenticated profile handover."""PREPARED="prepared"POINTER_PUBLISHED="pointer_published"B_BOUND="b_bound"ACCELERATED="accelerated"ACTIVATED="activated"A_RETIRED="a_retired"
[docs]classProfileLoginHandoverJournal(BaseModel):"""Non-secret recovery witness for the short A-to-B handover window."""model_config=STRICT_FROZEN_CONFIGschema_version:Literal[2]=2phase:HandoverPhaseprofile_a:BucketId|Noneprofile_b:BucketIdpointer_before:BucketPointerpointer_after:BucketPointeractivation_at:datetime@model_validator(mode="after")@pydantic_validation_boundarydef_validate_journal(self)->ProfileLoginHandoverJournal:validate_utc_aware(self.activation_at)ifself.pointer_after.bucket_id!=self.profile_b:raiseValueError("handover pointer-after selection must name profile B")ifself.pointer_after!=self.pointer_beforeand(self.pointer_after.transition_revision!=self.pointer_before.transition_revision+1):raiseValueError("handover pointer transition revision must advance exactly once when selection changes")returnself
[docs]@classmethoddefprepare(cls,*,profile_a:str|None,profile_b:str,pointer_before:BucketPointer,pointer_after:BucketPointer,activation_at:datetime,)->ProfileLoginHandoverJournal:"""Capture the transition before pointer publication."""returncls(phase=HandoverPhase.PREPARED,profile_a=profile_a,profile_b=profile_b,pointer_before=pointer_before,pointer_after=pointer_after,activation_at=activation_at,)
[docs]defat_phase(self,phase:HandoverPhase)->ProfileLoginHandoverJournal:"""Return this handover witnessed at its next durable phase."""returnself.model_copy(update={"phase":phase})
[docs]defat_least_phase(self,phase:HandoverPhase)->ProfileLoginHandoverJournal:"""Advance without regressing the durable phase."""if_HANDOVER_PHASE_INDEX[self.phase]>=_HANDOVER_PHASE_INDEX[phase]:returnselfreturnself.at_phase(phase)
[docs]defcanonical_json_bytes(self)->bytes:"""Return the journal's bounded, byte-exact persistence form."""returnbounded_canonical_json_bytes(self.model_dump(mode="json"),maximum_bytes=HANDOVER_JOURNAL_MAX_BYTES,subject="profile login handover journal",)