"""Vendor-agnostic "when did this person last actually use it?" signals. Why this exists ─────────────── ``software_seat.idle_reclaim`` recovers paid seats nobody uses. Until now it could only do that for **Atlassian**, because Atlassian was the only vendor whose activity Helix knew how to read (:mod:`app.integrations.atlassian_activity`). Every other paid seat — Google Workspace, Zoom — was reclaimable only by a human noticing. This module is the seam: one **persisted** store of ``(person × provider × product) → last active``, written by per-vendor collectors and read by the reclaim job, so adding a vendor is adding a collector rather than rewriting the state machine. The four answers, and why ``unknown`` is not ``idle`` ──────────────────────────────────────────────────── * ``active`` — we hold a real last-active timestamp. * ``never`` — the vendor **affirmatively** says the account has never signed in. Reclaimable, but the idle clock then runs from the SEAT GRANT, not from a login that never happened: a seat handed out yesterday and not yet used is not idle, it is new. * ``unknown`` — the vendor answered, but gave nothing usable (blank field, unparseable value, account not found in the roster). * ``unsupported`` — this vendor **cannot** answer at all with the credentials/APIs Helix has. Declared per provider in :data:`NO_USAGE_SIGNAL` with the reason, never inferred. ``unknown`` and ``unsupported`` must never collapse into "idle". The 2026-07-17 identity-sync assurance run produced 36 false findings out of 48 by treating "the snapshot cannot testify" as "the account is open"; the pre-2026-06-11 Atlassian attempt flagged daily users as two-years- idle by trusting a sandbox's frozen timestamp. Same failure, twice, in two domains — so here the state is carried explicitly all the way to the UI instead of being flattened into a number. Freshness is part of the answer ─────────────────────────────── Every row carries ``collected_at``. A row older than :data:`STALE_AFTER_HOURS` cannot testify about today, so :func:`resolve_signal` downgrades it to ``unknown`` — which means a broken collector makes the reclaim job stop reclaiming rather than start reclaiming on stale evidence. Failing closed is the only acceptable direction for a job that revokes access. """ from __future__ import annotations import logging import re from collections.abc import Awaitable, Callable from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta from typing import Any from sqlalchemy.ext.asyncio import AsyncSession logger = logging.getLogger(__name__) SIGNAL_ACTIVE = "active" SIGNAL_NEVER = "never" SIGNAL_UNKNOWN = "unknown" SIGNAL_UNSUPPORTED = "unsupported" #: A collected row older than this cannot testify about current usage. #: 36h, so one missed daily run still resolves but two do not. STALE_AFTER_HOURS = 36 #: Timestamps at or before this are vendor sentinels for "never", not #: real logins. Google Directory returns ``1970-01-01T00:00:00.000Z`` #: for an account that has never signed in; parsed naively that reads as #: fifty-odd years idle — the single most expensive misreading available #: here, since it would reclaim the seat of every brand-new hire who has #: not logged in yet. Anything before 2000 is a sentinel, not a login. _SENTINEL_BEFORE = datetime(2000, 1, 1, tzinfo=UTC) #: ``[T