"""Shared predicate for "may this person receive a NEW access grant?". Background (roger.zhou02 incident, provisioning direction, 2026-07-12): the ``hris.absent_person_check`` cron stamps ``hris_record_state='purged'`` on a person who vanished from the HRIS roster but **deliberately leaves ``employment_status`` untouched** (usually ``active``). ``auth.service. is_effectively_departed`` correctly treats ``purged`` as departed for the LOGIN/session surface — but every "grant new access on approval-callback / admin action" gate hand-rolled its own leaver check as ``employment_status in ('left','leaver_pending')`` and never consulted ``hris_record_state``. A purged-but-still-``active`` person therefore slipped through all of them, so a pending API-key / seat / SSO / permission request approved AFTER the purge fired a real downstream grant against a tombstoned identity (an orphaned vendor-side credential the person can't even use, since they can no longer log in). This module centralises the predicate so the gates stop drifting. It is intentionally BROADER than ``is_effectively_departed``: a confirmed leaver whose termination date is still in the future (``leaver_pending`` before their last day) must not receive fresh access either — the pre-existing behaviour of the hand-rolled checks, preserved here. Kept dependency-free (no domain imports, no DB) so any domain module can import it without violating the cross-domain import rule. """ from __future__ import annotations from typing import Any # ``employment_status`` values that block a new grant outright — a # departed or departing person is not a valid grant target regardless of # their local termination date (unlike the login gate, which lets a # pre-termination leaver keep working until their last day, a NEW grant # to a confirmed leaver is never appropriate). _BLOCKED_EMPLOYMENT_STATUSES = ("left", "leaver_pending") def is_blocked_from_new_grant(person: Any) -> bool: """True when ``person`` must NOT receive a new access grant / provision. Blocks when the person is: * soft-deleted (``deleted_at`` set), * a departed / departing employee (``employment_status`` in ``left`` / ``leaver_pending``), or * HRIS-purged (``hris_record_state == 'purged'`` — the tombstone the hand-rolled ``employment_status``-only gates were missing). A ``None`` person is treated as blocked (fail-closed): a grant target that can't be resolved is never eligible. """ if person is None: return True if getattr(person, "deleted_at", None) is not None: return True if getattr(person, "employment_status", None) in _BLOCKED_EMPLOYMENT_STATUSES: return True if getattr(person, "hris_record_state", None) == "purged": return True return False