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):
- Designation delta on refresh — in
refresh_dataset, for thesanctionsdataset 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). - 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. - Immediate enqueue — for each affected customer, route an immediate re-screen via
route_detection/MonitoringTriggerType.sanctions_list_update, bypassing theis_rescreen_duecadence 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 viaroute_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).