Skip to main content

ADR-0055: UBO Senior Managing Official (SMO) fallback

Status: Accepted Date: 2026-06-15 Supersedes: none Superseded by: none Deciders: Adrian (Soft4U), Claude Opus 4.8

Decision context:

  • Latency: one extra Neo4j read (active directors) in fetch_ownership_graph, plus an O(directors) post-pass in the engine. Negligible; only the post-pass list-build runs, and only when no qualified UBO exists.
  • Dependency surface: no new packages. Adds SmoCandidate to trustrelay-models, audit_note to BeneficialOwnerResult, smo_candidates to OwnershipGraph, a post-pass to the engine, one query to the adapter.
  • Debuggability: each SMO result carries reason_code="smo_fallback" and an audit_note stating the ownership/control bases were exhausted — self-explaining for EU AI Act Art. 12.
  • Reversibility: branch revert. All model changes are additive (no field-type changes), so no consumer breaks.
  • Blast radius: additive — the post-pass only appends when no qualified UBO exists; the ownership (ADR-0053) and control (ADR-0054) traversals are untouched. One behavioural change: get_amlr_coverage §2(c) ubo_identified now becomes satisfied via the SMO fallback for entities with directors but no owner/controller (intended AMLR behaviour).
  • Alternative considered: elect only the single most-senior official (rejected — title seniority is not reliably orderable across jurisdictions; see Alternatives).

Context

ADR-0053 (ownership) and ADR-0054 (control) determine UBOs from the two AMLR bases. But AMLR requires a UBO of last resort: when no natural person qualifies via ownership or control, the senior managing official(s) are designated as beneficial owner. The engine previously returned no qualified result in that case, leaving the entity with "0 UBOs" — non-compliant.

Directors / senior management are already in the graph as HAS_DIRECTOR edges (Company→Person, role, active = invalid_at IS NULL), populated by the registry ETL. They are the SMO candidate pool. Only citation strings for the fallback existed (decision_memorandum_service.py:741, dashboard_agent.py:953) — no logic.

Article citation note (honesty). The issue (#32) references AMLR Art. 53; the existing codebase citation for the SMO fallback is Art. 42(1) (decision_memorandum_service.py:741). We cite Art. 42(1) as primary (consistent with existing code) and record the issue's Art. 53 reference here. The behaviour — SMO as UBO of last resort — is unambiguous; only the exact article number is uncertain, and this ADR does not assert false certainty on it.

Decision

Add an SMO fallback post-pass to the pure engine, fired only when ownership + control yield zero qualified UBOs:

  1. Models (trustrelay-models/ubo.py): SmoCandidate(person_id, person_name, role); OwnershipGraph.smo_candidates; BeneficialOwnerResult.audit_note.
  2. Engine (ubo_engine.py): after the ownership+control per-person loop, if not any(r.qualified for r in results) and graph.smo_candidates, append one BeneficialOwnerResult per candidate with qualified=True, qualified_via=["smo_fallback"], reason_code="smo_fallback", aggregated_pct=0.0, and an audit_note recording that the ownership (Art. 51) and control (Art. 52) bases were exhausted. The guard keys on qualified — a below-threshold near-miss (emitted unqualified) does not suppress the fallback; a control-only or ownership UBO does.
  3. Elect all active directors (plural) — not a single "most senior."
  4. Adapter (graph_service.fetch_ownership_graph): read active directors (HAS_DIRECTOR, invalid_at IS NULL, tenant-scoped) into smo_candidates, keyed person:<name>.

Consequences

Positive

  • Closes the AMLR SMO gap; an entity with no qualifying owner/controller no longer reports "0 UBOs."
  • audit_note records why the fallback fired (explainability).
  • Reuses existing HAS_DIRECTOR data — no new ETL.
  • Additive models — no consumer breaks.

Negative

  • Elects all active directors rather than a single "most senior" — may over-list for large boards (acceptable; narrowing is a follow-up).
  • ubo_identified (§2(c)) broadens to count the SMO fallback — intended, but a behavioural change to coverage that reason_code="smo_fallback" keeps auditable.

Neutral

  • SMO results persist in ubo_computations like any other (model_dump round-trips).

Alternatives Considered

Alternative 1: Elect only the single most-senior official

Rank directors by role/title and elect one.

  • Why rejected: title seniority is not reliably orderable across jurisdictions and languages (Gérant, Managing Director, Geschäftsführer, Bestuurder, …). AMLR uses the plural; electing all active directors is non-arbitrary and defensible. A single-election refinement can follow if a customer needs it.

Alternative 2: Put the fallback in UBOComputationService rather than the pure engine

  • Why rejected: SMO election is regulated determination logic and belongs with the ownership/control arithmetic in the pure, unit-testable engine (same rationale as ADR-0053/0054). The service stays orchestration + persistence.

Alternative 3: Mark the SMO result qualified=False (informational only)

  • Why rejected: AMLR treats the SMO as the beneficial owner when none is found via ownership/control — it is a real determination, not a note. qualified=True with reason_code="smo_fallback" is the faithful model; downstream CDD must treat the SMO as the identified beneficial owner.