ADR-0131: Sanctions FP-suppression never-suppress hardening — force-invalidation on (re)designation, determination bypass, expiry/re-confirmation
Date: 2026-07-24 Status: Accepted Deciders: Adrian (Soft4U BV), Claude Opus 4.8 (implementation + safety analysis)
Decision context:
- Latency: negligible. The force-invalidation adds one indexed
sanctioned_record_id IN (delta)UPDATE per sanctions refresh (bounded by the day's delta) and one audit write per invalidated rule; it runs only inside the already-dark-launched immediate-designation re-screen path (immediate_designation_rescreen_enabled).check_active_rulegains oneinvalidated_at IS NULLpredicate (same index). Not measured because it rides an existing daily-refresh background pass, not the request path. - Dependency surface: zero new packages. Reuses
AuditService(ADR-0064/0109 hash chain),get_admin_session(ADR-0050), and the existing suppression service/model. - Debuggability: an invalidated rule is queryable (
list_rules(status="invalidated")), each invalidation writes an immutablesanctions_suppression_redesignation_invalidatedaudit event carrying the record id + prior rationale, and a LOUDlogger.warning("(re)designated while suppressed: …")fires. Failure mode of the audit write is logged at ERROR but never re-suppresses the rule. - Reversibility: schema is Alembic 096 (apply/downgrade verified). The determination-bypass is a builder addition + a read-site preference (
sanctions_matches_rawwith fallback). Force-invalidation is coupled to the existingimmediate_designation_rescreen_enabledflag. No standalone new flag. - Blast radius: additive.
check_active_ruleonly ever WITHHOLDS suppression (fail-closed), never adds it. The officer-facingsanctions_matches/ EBA#513band is byte-unchanged; only a new parallelsanctions_matches_rawfeeds the deterministic determinations. With the re-screen flag off,invalidated_atis never set, so behaviour is identical to pre-#587. - Alternative considered: blanket-remove Tier-2 suppression (rejected — destroys the legitimate FP-suppression use the officers rely on, and the constraint is to close the STALE/re-designated/bypass cases, not suppression itself).
Context
The tenant-editable sanctions false-positive suppression subsystem (ADR-0045, Tier 2 —
officer-originated learned rules persisted to sanctions_suppression_rules) lets a
compliance officer mark a sanctions match as a false positive so it stops firing. A
whole-app design review (AMLR readiness epic #528, Mini-Wave 0, issue #587) found this is
the single most dangerous "hide a signal" mechanism in the codebase, violating the
never-suppress doctrine (ADR-0067) in three ways:
-
Stale rule silences a fresh Art. 26(4) hit. The immediate-on-designation re-screen (ADR-0128) re-screens active customers when the OpenSanctions refresh (re)adds a designation. But the re-screen's global path (
evaluate_new_designation_match) inspected only therequires_reviewbucket — a customer with a Tier-2 suppression rule had the fresh hit routed intosuppressed_by_rule, where it was not counted. A stale FP rule could therefore silence the very hit the re-designation re-screen exists to catch. -
Suppressed FP hides a sanctioned owner from the ADR-0127 determination. The deterministic sanctioned-ownership/control test (AMLR Art. 20(1)(d), EU 50%-rule) consumes
investigation_result["sanctions_matches"], whichsanctions_match_builderbuilt from the post-suppressionrequires_reviewbucket only. A Tier-2 rule moved a hit intosuppressed_by_rule, so a sanctioned owner an officer had suppressed was invisible to the ownership graph test — it could never floor the subject to CRITICAL. -
No expiry-driven re-confirmation / invalidation dimension. A suppression rule carried a 12-month
expires_atbut nothing invalidated a rule when the underlying record was re-designated, and there was no re-confirmation timestamp — the rule silently rode its original expiry regardless of list changes.
Additionally, sanctions_suppression_rules carried only a tenant_isolation RLS policy
(migrations 054/060) and no admin_bypass — the #561 single-policy gap — so a
cross-tenant admin-session sweep would see zero rows.
Decision
Close the hole with three fail-closed guarantees, keeping the legitimate FP-suppression use intact (a genuine, still-valid FP on an UNCHANGED list stays suppressed).
1. Force-invalidation on (re)designation (the core fix). On the OpenSanctions refresh,
the ADR-0128 delta (compute_designation_delta) drives a new cross-tenant pass,
invalidate_redesignated_suppressions: for every active FP-suppression rule whose
sanctioned_record_id is in the fresh delta, stamp invalidated_at + invalidation_reason
(SanctionsSuppressionService.force_invalidate_for_designations). An invalidated rule
FAILS CLOSED in check_active_rule (it does not suppress) until an officer re-confirms —
"a re-designation always wins". Each invalidation writes an immutable, per-tenant hash-chained
sanctions_suppression_redesignation_invalidated audit event (ADR-0064/0109) and logs a LOUD
"(re)designated while suppressed" warning; the subsequent screen sweep, now un-suppressed,
raises the normal per-case Art. 26(4) monitoring alert into the officer's EDD queue. The pass
runs under get_admin_session (the delta is global) and is coupled to the existing
immediate_designation_rescreen_enabled flag.
2. Deterministic determinations BYPASS suppression. A new
sanctions_match_builder.build_raw_sanctions_matches builds a parallel
investigation_result["sanctions_matches_raw"] from requires_review plus
suppressed_by_rule (Tier-2 officer-editable suppression bypassed; each such record stamped
fp_suppression_bypassed=True to preserve the officer's prior-FP context). The ADR-0127
ownership test reads sanctions_matches_raw (fallback to sanctions_matches for older
investigations); the ADR-0128 evaluate_new_designation_match now also scans
suppressed_by_rule. Tier-1 evidence-based auto_dismissed (≥2 formal discriminators
contradict — a DIFFERENT identity) stays EXCLUDED: it is regulator-defensible evidence, not
the tenant-editable stale-able hole, and re-surfacing it would violate presence ≠ evidence.
The officer-facing sanctions_matches / EBA #513 band is unchanged.
3. Governance hardening. Alembic 096 adds last_reconfirmed_at, invalidated_at,
invalidation_reason to sanctions_suppression_rules, the missing admin_bypass RLS policy
(closing the #561 gap), and a partial index on sanctioned_record_id. record_dismissal
becomes the re-confirmation path: a re-dismissal clears invalidated_at/invalidation_reason
and stamps last_reconfirmed_at (an audited officer re-review WITH the re-designation known).
list_rules excludes invalidated rules from active and adds an invalidated bucket for the
dashboard. The officer-facing config panel is a separate follow-up (#583); this ADR is
backend + audit + the alert only.
Consequences
Positive
- A stale FP-suppression rule can never silence a fresh Art. 26(4) designation hit nor hide a sanctioned owner from the ADR-0127 CRITICAL floor — the named never-suppress hole is closed.
- Fail-closed by construction: an invalidated / un-reconfirmed / expired rule does not suppress; the deterministic determinations bypass suppression entirely (defense in depth with the force-invalidation).
- Every invalidation is immutably audited and loud; a re-designation is surfaced to the officer.
- The
admin_bypassgap onsanctions_suppression_rules(a latent cross-tenant defect) is fixed.
Negative
- A legitimately-suppressed Tier-2 name-only FP now also surfaces in the ownership test as a
labelled
sanctioned_name_candidate(medium, never a floor). This is added scrutiny/noise on a previously-quiet surface; mitigated by thefp_suppression_bypassedprovenance marker and the two-lane design (name-only never floors). Acceptable under never-suppress (surface, don't drop, don't inflate). - Force-invalidation is coupled to
immediate_designation_rescreen_enabled(dark-launched OFF); until that flag flips, a re-designation does not auto-invalidate a rule. The always-on parts (check_active_rule fail-closed, raw-view bypass) still protect the determinations regardless. - One more Alembic migration on the hot suppression table.
Neutral
sanctions_matches_rawis a new investigation-result key carried alongsidesanctions_matches; consumers that readsanctions_matchesare unaffected.
Alternatives Considered
Alternative 1: Blanket-remove Tier-2 suppression
- Delete officer-originated suppression entirely so nothing can ever hide a hit.
- Why rejected: destroys the legitimate, regulator-recognised FP-suppression use (a verified name-collision on an unchanged list). The mandate is to close the stale/re-designated/bypass cases, not suppression itself.
Alternative 2: Include Tier-1 auto-dismissed hits in the raw determination view too
- Feed every screened hit (including evidence-based Tier-1 auto-dismissals) to the deterministic determinations for maximal "raw".
- Why rejected: a Tier-1 auto-dismissal is ≥2 unambiguous formal-discriminator contradictions — regulator-defensible EVIDENCE the hit is a different identity, recomputed per scan, non-editable. Re-surfacing it as an ownership candidate would fabricate a signal against its own evidence (presence ≠ evidence). Only the tenant-editable, stale-able Tier-2 layer is the hole.
Alternative 3: Emit a standalone rule-level MonitoringAlert instead of relying on the re-screen
- Raise a
MonitoringAlertper invalidated rule. - Why rejected: a suppression rule carries no
case_id, andMonitoringAlert/route_detectionare per-case. The immutable audit event is the correct rule-level record; the officer is told loudly via the normal per-case Art. 26(4) alert the (now un-suppressed) re-screen raises.