Bulk Good-Standing Certificate Polling and Status Diffing
This guide is part of the Good-Standing Certificate Automation area within the Secretary of State Portal & API Ingestion framework. It builds the concrete engine at the centre of that pipeline: the scheduled sweep that reads good-standing status for an entire portfolio in one pass and computes a precise, typed diff of each read against the last-known state, so a good → delinquent → not-in-good-standing transition surfaces as a first-class event rather than a value buried in a table.
Scope of This Page
This page covers two tightly coupled mechanics: the bulk poll — fanning a status read across every entity under a concurrency bound without overrunning any single registry — and the status diff — joining that fresh read against a persisted snapshot to produce a typed StatusChange set classified as regression, recovery, or lateral move. It deliberately excludes what happens to those changes next: routing, deduplication, and severity ranking belong to Alerting on Good-Standing Status Changes Across a Portfolio, and the per-registry pacing that keeps the sweep polite is owned by Async Polling & Rate Limiting. Here we assume a paced client and a canonical status enum already exist, and focus only on sweeping the portfolio and computing the delta correctly.
The Constraint That Forces a Persisted Snapshot
A diff needs two operands, and only one of them — today’s read — is available at run time. The other, the last-known status, must be persisted between runs, and its integrity is a compliance concern rather than a caching convenience. Good standing is a statutory status: a California entity that lapses under Cal. Corp. Code § 2205 loses corporate powers on a specific date, and a Delaware corporation that misses its March 1 obligation under 8 Del. C. § 502 ceases to be in good standing on a specific date. The whole point of diffing is to pin when that transition happened relative to your observation window, which is only possible if the prior state was durably recorded with its own timestamp. A snapshot that is lost, stale, or silently reset does not merely lose data — it manufactures a false transition on the next run, either fabricating a regression that was already known or masking one that is real. The engine therefore treats the snapshot store as an append-only status time series, never a mutable single row, so every observation is retained and the diff is always against a dated prior.
Prerequisites
- Python 3.10+ — for
matchon the canonical status,X | Yunions, andasyncio.TaskGroup-style structured concurrency (orgatheron 3.10). httpx0.27+ — async client with pooling, injected already-paced from the parent area so this engine never sets its own rate policy.- A persistence layer — any store that supports an append-only status series keyed by
(entity_id, jurisdiction); the example uses a narrow protocol so SQLAlchemy, Postgres, or a document store all satisfy it. - A canonical
GoodStandingStatus— the ordered enum defined by the parent area, with three real states plus anUNKNOWNfail-safe. - Standard library —
asyncio,dataclasses,enum,json,logging,datetimefor the diff, the change set, and structured logging.
Implementation: A Bulk Poller with Snapshot Diffing
The engine loads the last-known snapshot for the portfolio, fans a status poll across every entity under a semaphore so no registry is hammered, then joins the fresh reads against the snapshot to emit a typed StatusChange set. An UNKNOWN read never overwrites a known baseline, so a flaky portal produces a retry signal rather than a phantom regression; only entities whose status genuinely moved appear in the change set, and the fresh reads are persisted as the next run’s baseline. Comments mark the compliance-critical lines.
from __future__ import annotations
import asyncio
import enum
import json
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Awaitable, Callable, Protocol, Sequence
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("good_standing.bulk_diff")
class GoodStandingStatus(enum.IntEnum):
NOT_IN_GOOD_STANDING = 0 # ordered worst-to-best: a lower value is a worse status
DELINQUENT = 1
GOOD_STANDING = 2
UNKNOWN = 3 # read failed/ambiguous — special-cased, never a comparison
class ChangeKind(str, enum.Enum):
REGRESSION = "regression" # current worse than previous — the compliance signal
RECOVERY = "recovery" # current better than previous — cured / reinstated
LATERAL = "lateral" # same severity rank, different label (rare)
@dataclass(frozen=True)
class Observation:
entity_id: str
jurisdiction: str
status: GoodStandingStatus
observed_at: datetime
@dataclass(frozen=True)
class StatusChange:
entity_id: str
jurisdiction: str
previous: GoodStandingStatus
current: GoodStandingStatus
kind: ChangeKind
observed_at: datetime
class SnapshotStore(Protocol):
"""Append-only status series. load() returns the latest per entity; persist() appends."""
def load(self, entity_ids: Sequence[str]) -> dict[str, GoodStandingStatus]: ...
def persist(self, observations: Sequence[Observation]) -> None: ...
# A poll function is injected already-paced by Async Polling & Rate Limiting.
PollFn = Callable[[str, str], Awaitable[GoodStandingStatus]]
class BulkGoodStandingDiffer:
"""Sweep a portfolio's good-standing status and diff it against a persisted snapshot."""
def __init__(self, store: SnapshotStore, poll: PollFn, max_concurrency: int = 8) -> None:
self._store = store
self._poll = poll
self._sem = asyncio.Semaphore(max_concurrency) # bound so no registry is flooded
async def _poll_one(self, entity_id: str, jurisdiction: str) -> Observation:
async with self._sem: # concurrency gate keeps the sweep within pacing budgets
try:
status = await self._poll(entity_id, jurisdiction)
except Exception as exc: # a transport failure is UNKNOWN, not a status change
logger.warning(json.dumps({
"event": "poll_failed", "entity_id": entity_id,
"jurisdiction": jurisdiction, "error": type(exc).__name__,
}))
status = GoodStandingStatus.UNKNOWN
return Observation(entity_id, jurisdiction, status, datetime.now(timezone.utc))
async def poll_all(self, entities: Sequence[tuple[str, str]]) -> list[Observation]:
results = await asyncio.gather(
*(self._poll_one(eid, juris) for eid, juris in entities)
)
logger.info(json.dumps({"event": "sweep_complete", "polled": len(results)}))
return list(results)
def diff(
self, previous: dict[str, GoodStandingStatus], fresh: Sequence[Observation]
) -> list[StatusChange]:
changes: list[StatusChange] = []
for obs in fresh:
# A failed read must never clobber a known baseline — hold and retry next run.
if obs.status is GoodStandingStatus.UNKNOWN:
continue
prior = previous.get(obs.entity_id)
if prior is None or prior is GoodStandingStatus.UNKNOWN:
continue # first real observation establishes a baseline, emits nothing
if prior == obs.status:
continue # unchanged — dropped, the common case on a healthy portfolio
kind = (ChangeKind.REGRESSION if obs.status < prior
else ChangeKind.RECOVERY)
change = StatusChange(obs.entity_id, obs.jurisdiction, prior,
obs.status, kind, obs.observed_at)
changes.append(change)
logger.info(json.dumps({
"event": "status_change", "entity_id": obs.entity_id,
"jurisdiction": obs.jurisdiction, "previous": prior.name,
"current": obs.status.name, "kind": kind.value,
}))
return changes
async def run(self, entities: Sequence[tuple[str, str]]) -> list[StatusChange]:
"""One scheduled sweep: load baseline, poll, diff, persist real reads, return changes."""
previous = self._store.load([eid for eid, _ in entities])
fresh = await self.poll_all(entities)
changes = self.diff(previous, fresh)
# Persist only successful reads so an UNKNOWN cannot overwrite a good baseline.
real = [o for o in fresh if o.status is not GoodStandingStatus.UNKNOWN]
self._store.persist(real)
logger.info(json.dumps({
"event": "run_complete", "entities": len(entities),
"changes": len(changes),
"regressions": sum(c.kind is ChangeKind.REGRESSION for c in changes),
}))
return changes
The differ is deliberately a pure transformation over its two operands: given a baseline and a set of observations, its output is fully determined, which makes it replayable during an audit and trivially unit-testable. The only stateful actor is the snapshot store, and the engine touches it exactly twice per run — a load at the start and a persist of successful reads at the end — so the window in which the baseline can drift is bounded to a single sweep.
Configuration Reference
These parameters govern the sweep and diff behaviour and are configuration data, not branches in the loop, because they are dictated by portfolio size and registry tolerance rather than by code.
| Parameter | Suggested value | Justification |
|---|---|---|
max_concurrency |
8 | Bounds simultaneous registry hits; higher risks a 429 from a single portal, lower slows the sweep. Tune per portal, not globally. |
| Snapshot model | append-only series | A mutable single row loses transition history and cannot date a regression; the diff needs a dated prior. |
UNKNOWN persistence |
never persisted | A failed read persisted as baseline would mask the real prior status and fabricate a transition on the next successful read. |
| First-observation policy | emit nothing | With no dated prior there is no transition to report; the first real read only establishes the baseline. |
| Poll injection | paced client from parent area | The differ must not set its own rate policy; pacing belongs to Async Polling & Rate Limiting so limits stay per-registry. |
| Diff purity | no I/O in diff() |
Keeps the delta replayable from a persisted baseline and observation set for audit reproduction. |
Failure Modes and Fallback Routing
Each fault maps onto the four-tier scheme in the parent framework’s error categorization & retry logic — transient, statutory, data-validation, and system — and the engine responds to each differently.
- A registry times out mid-sweep (transient).
_poll_onecatches the exception, records the read asUNKNOWN, and the differ skips it, so the entity’s known baseline is preserved rather than dropped. The failure is logged for the retry layer to re-poll on the next window; it never enters the change set as a false regression. - The snapshot store returns an empty or stale baseline (system). With no prior,
diffemits nothing for that entity — correct, because there is no transition to report — but a portfolio-wide empty baseline is itself the alarm: ifpreviousis empty for entities you know were observed, the store lost data and the sweep must halt rather than re-baseline silently on possibly-degraded reads. - A registry changes its vocabulary so reads normalize to
UNKNOWNen masse (data-validation). BecauseUNKNOWNis never persisted and never diffed, a vocabulary break produces a spike of held reads and poll-failure logs, not a wave of phantom regressions. The spike is the signal to fix the upstream label map before the baseline goes stale. - Concurrency set too high trips a single portal’s limiter (transient/statutory-adjacent). A flooded registry starts returning
429; the paced client should absorb this, but ifmax_concurrencyoutpaces the per-registry budget the sweep degrades toUNKNOWNreads. Lower concurrency for that jurisdiction rather than globally, and route the throttled calls back through the parent area’s async polling & rate limiting budget.
Frequently Asked Questions
Why persist a full status series instead of just overwriting the current status per entity?
Because the diff has to date the transition, and a single mutable row cannot. Good standing is lost on a statutory date — a California suspension under Corp. Code § 2205, a Delaware default under § 502 — and the value of the diff is pinning that change to an observation window. An append-only series retains every dated prior, so a regression can be reported with a defensible “last seen good on” timestamp; overwriting throws that away and leaves you unable to prove when the entity lapsed.
What stops an intermittent portal failure from firing a false regression?
A failed or ambiguous read normalizes to UNKNOWN, and the differ both skips UNKNOWN when comparing and refuses to persist it as a baseline. So a portal that flaps produces a poll-failure log and a held baseline, never a StatusChange. Only a successful read that genuinely differs from the last successful read becomes a regression or recovery, which keeps transient transport noise out of the change set entirely.
How is the sweep kept from overrunning a single state's rate limit?
Two mechanisms. The max_concurrency semaphore bounds how many reads are in flight at once, and the poll function itself is injected already-paced by Async Polling & Rate Limiting, which applies a per-registry token budget. The differ never sets its own rate policy; it only bounds fan-out. If one portal is more fragile, lower concurrency for that jurisdiction rather than throttling the whole sweep.
Why does the first observation of an entity emit no change?
Because a diff needs two operands and the first read supplies only one. With no dated prior in the snapshot there is no transition to report, so the first successful read simply establishes the baseline and emits nothing. This is deliberate: treating an initial observation as a change would flood the change set every time a new entity is onboarded, drowning the real regressions the engine exists to surface.