Skip to main content

ADR-0128: Immediate-on-designation sanctions re-screen

Date: 2026-07-24 Status: Accepted Deciders: Adrian (Soft4U BV), Claude Opus 4.8

Context

AMLR Art. 26(4) requires credit and financial institutions to re-verify the Art. 20(1)(d) sanctions conditions immediately upon any new targeted-financial-sanctions (TFS) designation, as a cycle independent of the general ongoing-monitoring cadence.

Before this ADR, TrustRelay's OpenSanctions bulk refresh (opensanctions_bulk.refresh_dataset) DELETE+INSERTed the local dataset (~893K entities, sanctions refreshed daily) and stamped last_refreshed_at, but never diffed the newly-added designations. A new listing was therefore only caught at the next risk-paced re-screen tick (RESCREEN_CADENCE_DAYS_BY_TIER = 90/180/365 days) — up to a year late for a lower-risk customer. That is the specific gap the maturity assessment flagged as the sharpest FR-MON-03 exposure ("prove you re-verified Customer X within hours of the EU adding its UBO to the TFS list").

The event-trigger machinery to act on a new designation already exists — MonitoringTriggerType.sanctions_list_update (a mandatory-min-response trigger, ADR-0083) routed through trigger_router_service.route_detection (typed MonitoringAlert + auto-opened EDD review). What was missing was the delta detector that turns a refresh into that trigger.

Decision

Add an immediate-on-designation re-screen, dark-launched behind immediate_designation_rescreen_enabled (default False):

  1. Designation delta on refresh — in refresh_dataset, for the sanctions dataset only and only when the flag is on, snapshot the designation id-set before the destructive reload (snapshot_designation_ids, guard-and-swallow → empty set on failure so it degrades to "no delta", never crashes the refresh) and diff against the id-set after (compute_designation_delta → newly-added ids only).
  2. Affected-customer evaluation (evaluate_new_designation_match) — a customer is AFFECTED if any of its screened entities produces a NEW hit against the delta (CRITICAL), or if any entity screen is errored/indeterminate (WARNING — fail-toward-scrutiny: we could not rule out a match, so we re-screen). A hit only against a pre-existing designation dedups (not affected). A clean, complete screen is not affected.
  3. Immediate enqueue — for each affected customer, route an immediate re-screen via route_detection / MonitoringTriggerType.sanctions_list_update, bypassing the is_rescreen_due cadence gate. One bad case never stops the sweep.

The re-screen re-evaluates the Art. 20(1)(d) conditions (and, where ADR-0127's sanctioned-ownership/control test is available, the >50%/control determination).

Consequences

Positive

  • Closes the Art. 26(4) "immediately upon new designation" obligation — a new TFS listing re-screens affected customers within the refresh window (hours), not at the next 90/180/365-day tick.
  • Reuses the proven trigger/alert/EDD machinery (ADR-0083); the delta detector is the only new logic.
  • Fail-closed by construction: a new designation matching a customer is never silently missed; an indeterminate screen re-screens rather than passing.

Negative

  • The delta+match sweep runs on every sanctions refresh (daily) and, at scale, must match the delta against the customer-entity index efficiently — a naive O(customers × designations) scan would be prohibitive; the first cut matches per-customer against the delta and defers a fully-indexed cross-customer scan (noted in the issue).
  • Enabling the flag drives real re-screens + alerting (cost + officer load), hence the dark-launch + Calibration-Review gate before flipping it on.

Neutral

  • With the flag off, behaviour is byte-identical (no snapshot, no delta, no route) — the cadence path is unchanged.

Alternatives Considered

Alternative 1: Tighten the existing cadence

Shorten RESCREEN_CADENCE_DAYS_BY_TIER. Rejected: "immediately" is not "more often" — Art. 26(4) requires an event-driven cycle independent of the cadence; a shorter cadence still misses a designation for days and multiplies cost for every customer regardless of any new listing.

Alternative 2: Full designation-vs-all-customers matcher on every refresh

A complete cross-customer scan each refresh. Rejected for the first cut on efficiency (daily over ~893K entities); the delta-only approach bounds the work to the newly-added ids, and the fully-indexed cross-customer scan is a tracked follow-up.

Decision context

  • Latency: adds a bounded delta query + per-affected-customer route on each sanctions refresh (daily, off-request); guard-and-swallow so it never adds latency to the refresh itself.
  • Dependency surface: no new dependency — reuses opensanctions_bulk, trigger_router_service, the existing matcher.
  • Debuggability: typed MonitoringAlert + immutable audit via route_detection; a snapshot/delta failure logs and degrades to no-delta.
  • Reversibility: single flag flip (immediate_designation_rescreen_enabled=False) restores prior behaviour exactly.
  • Blast radius: additive; flag-off is a no-op; on, it only ADDS re-screens/alerts (fail-toward-scrutiny), never removes a signal.
  • Alternative considered: tightening the cadence (rejected — event-driven ≠ more frequent).