"""Canonical persisted authentication contracts."""from__future__importannotationsfromdatetimeimportdatetimefromenumimportStrEnumfromtypingimportAnnotatedfrompydanticimportBaseModel,Field,StringConstraints,model_validatorfrom...core.errors.hierarchyimportpydantic_validation_boundaryfrom...core.heximportHex64Strfrom...core.identity.bucketimportBucketIdfrom...core.modelsimportSTRICT_FROZEN_CONFIGfrom...core.time.utcimportvalidate_utc_awaretype_AuthOperationId=Hex64Str"""Identity of one durable auth operation.Producers derive it as ``hashlib.sha256(...).hexdigest()``, so the value isalways lowercase hex. The previous length-only bound admitted uppercase andnon-hex strings that no producer can emit, which a resume path would thenfail to match against the operation it was meant to continue."""typeCertificateSourceName=Annotated[str,StringConstraints(strip_whitespace=True,min_length=1,max_length=160),]"""Canonical spelling of a named certificate source.The name is the natural key three separate surfaces derive an identity from:the ``certificate_sources`` registry dict, the active-source selector, and thesecret-store key. Each of those used to strip independently, so a recordpersisted as ``" personal "`` kept its padding in durable state while thesecret backend filed its passphrase under ``"personal"`` — one nominal sourceaddressed under two spellings, with exact-dict selection unable to resolve thepadded record from the canonical selector. Stripping at the boundary makes thepersisted spelling the canonical one, and ``min_length`` applies *after* thestrip, so a blank-after-strip name is refused rather than stored."""def_require_utc(*values:datetime|None)->None:"""Reject any populated timestamp that is not UTC-aware. Timestamps on these records are persisted as JSON, which preserves the offset, so the canonical contract is enforceable at the model boundary. """forvalueinvalues:ifvalueisnotNone:validate_utc_aware(value)
[docs]classCertificateSourceRecord(BaseModel):"""One named, registered PKCS#12 certificate source."""model_config=STRICT_FROZEN_CONFIGname:CertificateSourceNamecertificate_path:str=Field(min_length=1)friendly_name:str|None=Noneregistered_at:datetime@model_validator(mode="after")@pydantic_validation_boundarydef_timestamps_are_utc(self)->CertificateSourceRecord:"""Reject a registration instant that is naive or not UTC."""_require_utc(self.registered_at)returnself
[docs]classAuthCleanupCertificateSource(BaseModel):"""Version witness for one certificate source captured by auth cleanup."""model_config=STRICT_FROZEN_CONFIGname:CertificateSourceNameregistered_at:datetime@model_validator(mode="after")@pydantic_validation_boundarydef_timestamps_are_utc(self)->AuthCleanupCertificateSource:"""Reject a witness instant that is naive or not UTC."""_require_utc(self.registered_at)returnself
[docs]classAuthCleanupIntent(BaseModel):"""Secret-free durable plan for one resumable operator auth cleanup."""model_config=STRICT_FROZEN_CONFIGoperation_id:_AuthOperationIdoperation_kind:AuthCleanupOperationKindbucket_id:BucketIdprovider_ids:tuple[str,...]all_providers:boolstarted_at:datetimeprovider_at_start:str|None=Noneconfigured_at_at_start:datetime|None=Noneauthenticated_at_at_start:datetime|None=Nonehad_session_state:bool=Falsecertificate_path_at_start:str|None=Noneactive_certificate_source_at_start:CertificateSourceName|None=Nonecertificate_sources:tuple[AuthCleanupCertificateSource,...]=()provider_configuration_ids:tuple[str,...]=()session_provider_ids:tuple[str,...]=()lock_provider_ids:tuple[str,...]=()secret_source_names:tuple[CertificateSourceName,...]=()@model_validator(mode="after")@pydantic_validation_boundarydef_timestamps_are_utc(self)->AuthCleanupIntent:"""Reject any populated intent instant that is naive or not UTC."""_require_utc(self.started_at,self.configured_at_at_start,self.authenticated_at_at_start)returnself
[docs]classCertificateSecretMutationEventKind(StrEnum):"""Stable event classifications for certificate-secret mutations."""SET="set"ROTATED="rotated"REMOVED="removed"
[docs]classCertificateSecretMutationIntent(BaseModel):"""Secret-free durable plan for one resumable certificate-secret mutation."""model_config=STRICT_FROZEN_CONFIGoperation_id:_AuthOperationIdbucket_id:BucketIdsource_name:CertificateSourceNameevent_kind:CertificateSecretMutationEventKindstarted_at:datetimeprior_present:boolrequest_witness:Hex64Str|None=Nonecompletion_witness:str|None=None@model_validator(mode="after")@pydantic_validation_boundarydef_timestamps_are_utc(self)->CertificateSecretMutationIntent:"""Reject a mutation start instant that is naive or not UTC."""_require_utc(self.started_at)returnself
[docs]classAuthState(BaseModel):"""Persisted local AEAT access readiness embedded in workflow state."""model_config=STRICT_FROZEN_CONFIGprovider:str|None=Nonecertificate_path:str|None=Noneconfigured_at:datetime|None=Noneauthenticated_at:datetime|None=Nonesubject:str|None=Nonecertificate_sources:dict[CertificateSourceName,CertificateSourceRecord]=Field(default_factory=dict)active_certificate_source:CertificateSourceName|None=Nonecleanup_intent:AuthCleanupIntent|None=Nonecertificate_secret_mutation_intent:CertificateSecretMutationIntent|None=None@model_validator(mode="after")@pydantic_validation_boundarydef_timestamps_are_utc(self)->AuthState:"""Reject any populated auth-state instant that is naive or not UTC."""_require_utc(self.configured_at,self.authenticated_at)returnself