"""Typed portal-drift record and read-only divergence detection.A :class:`PortalDriftEvent` captures a single detected divergence between a*registered* portal assumption (the canonical URL / host declared on thefrozen :class:`~domain.portals.PortalMetadata` entry) and an *observed*live state. Detection is advisory and read-only: :func:`evaluate_portal_drift`compares an already-observed value the caller supplies against the registryentry and, when they diverge, materialises the typed record. This module nevercontacts AEAT — the observation itself is produced elsewhere, under thelive-read access gate, and handed in as a plain value. The catalogue package asa whole performs no live AEAT access (see the package docstring); this moduleonly *describes* a divergence someone else observed.The record carries the entry's :class:`~domain.portals.UrlStability` tierso downstream health reporting (the ``portal-registry:health`` doctor row in:mod:`application.preflight`) can grade a drift by how stable the URL waspromised to be: a drift on a BOE-referenced ``STABLE_PROTOCOL_GRADE`` URL is areal integrity concern, whereas a drift on a ``VOLATILE_APP_PATH`` shell URL isan expected rotation."""from__future__importannotationsfromdatetimeimportdatetimefromenumimportStrEnumfromurllib.parseimporturlsplitfrompydanticimportAwareDatetime,BaseModel,Field,model_validatorfrom...coreimportSTRICT_FROZEN_CONFIGfrom...core.timeimportnowas_nowfrom._categoriesimportUrlStabilityfrom._codesimportPortalfrom._errorsimportPortalValidationErrorfrom._metadataimportPortalMetadata__all__=["PortalDriftEvent","PortalDriftField","evaluate_portal_drift",]
[docs]classPortalDriftField(StrEnum):"""The registered portal assumption a :class:`PortalDriftEvent` diverges on. ``URL`` — the observed canonical URL differs from the registered URL (same host, different path or query). ``SUBDOMAIN`` — the observed URL is hosted on a different AEAT-family host than the registered :class:`~domain.portals.PortalHost`. ``AVAILABILITY`` — the portal did not answer as reachable when the registered assumption is that it is a live surface. """URL="url"SUBDOMAIN="subdomain"AVAILABILITY="availability"
[docs]classPortalDriftEvent(BaseModel):"""One detected divergence between a registered portal assumption and live state. A strict, frozen record. It asserts an *actual* divergence: ``expected`` (the registered assumption) and ``observed`` (the live state) must differ, so a :class:`PortalDriftEvent` can only exist for a genuine drift. Attributes: portal: The :class:`~domain.portals.Portal` whose assumption drifted. field: The :class:`PortalDriftField` that diverged. expected: The registered assumption value (the registry's truth). observed: The observed live-state value the caller supplied. url_stability: The registered :class:`~domain.portals.UrlStability` tier of the entry, carried so health reporting can grade the drift. detected_at: UTC-aware instant the divergence was observed. note: Optional free-text operator note describing the observation. """model_config=STRICT_FROZEN_CONFIGportal:Portalfield:PortalDriftFieldexpected:str=Field(min_length=1)observed:strurl_stability:UrlStabilitydetected_at:AwareDatetimenote:str=""@model_validator(mode="after")def_validate_divergence(self)->PortalDriftEvent:"""Reject a record whose observed value equals its expected value."""ifself.expected==self.observed:raisePortalValidationError(f"portal {self.portal.value} drift on {self.field.value} carries no divergence: "f"expected == observed == {self.expected!r}",)returnself
[docs]defevaluate_portal_drift(metadata:PortalMetadata,*,observed_url:str,detected_at:datetime|None=None,)->PortalDriftEvent|None:"""Compare a registered portal entry against an observed live URL. Read-only and pure: performs no network access. The ``observed_url`` is a value the caller obtained under the live-read access gate (or from a recorded observation) and passes in for comparison against the registry truth. When the observed URL matches the registered URL exactly, there is no drift and ``None`` is returned. Otherwise a typed :class:`PortalDriftEvent` is materialised: a differing host yields a :attr:`PortalDriftField.SUBDOMAIN` record; any other difference yields a :attr:`PortalDriftField.URL` record. Args: metadata: The registered :class:`~domain.portals.PortalMetadata`. observed_url: The URL observed for the portal in live state. detected_at: The UTC-aware observation instant. Defaults to the clock-seam :func:`core.time.now` reading when omitted. Returns: A :class:`PortalDriftEvent` when the observed URL diverges from the registered URL, or ``None`` when they match. """registered_url=str(metadata.url)ifobserved_url==registered_url:returnNonestamped_at=detected_atifdetected_atisnotNoneelse_now()expected_host=metadata.url.hostobserved_host=urlsplit(observed_url).hostnameifobserved_hostisnotNoneandexpected_hostisnotNoneandobserved_host!=expected_host:returnPortalDriftEvent(portal=metadata.portal,field=PortalDriftField.SUBDOMAIN,expected=expected_host,observed=observed_host,url_stability=metadata.url_stability,detected_at=stamped_at,)returnPortalDriftEvent(portal=metadata.portal,field=PortalDriftField.URL,expected=registered_url,observed=observed_url,url_stability=metadata.url_stability,detected_at=stamped_at,)