D95 — First non-Conduit primary provider grant: Sumsub Onboarding go-live (GB/EU) (RESOLVED)
Architecture decision record. Status, thematic clusters, and how to record a new ADR: the decision log index. Program context + the onboarding build plan live in
docs/tracking/multi-provider/(§4 of10-onboarding-identity.md).
The multi-provider substrate now carries its first non-Conduit primary. D94 shipped the
ProviderRouter substrate additive — one live provider (conduit), every capability resolving to it.
Increment 2.1d-2b makes Sumsub the Onboarding PRIMARY for GB + EU, so those two jurisdictions'
onboarding traffic routes to Sumsub while conduit is retained as a permitted (non-primary)
fallback. The decision: this is a jurisdiction-scoped corridor grant, not a global flip — the
router now proves what it was built for, two providers serving ONE capability group
(Onboarding) routed by jurisdiction.
The mechanism is one declaration. In FeaturesServiceProvider::registerFeatureDeclarations(),
after registerConduitBaseline(['ZA','NG','KE','GB','EU','US']) (which routes every
CapabilityGroup to conduit), a single later line overrides only the (GB|EU, Onboarding) tuple:
$providers->assignIn(['GB','EU'], CapabilityGroup::Onboarding, ['sumsub','conduit'], 'sumsub').
features:sync realizes it into jurisdiction_provider_grants. ZA/NG/KE/US stay conduit-primary and
every non-Onboarding group in GB/EU stays conduit-primary — the flip is Onboarding-only. Listing
conduit as a permitted key keeps the per-tenant tenant_provider_overrides escape hatch back to
conduit a config change, not a code change. This grant IS the reusable pattern for committing every
other registered-but-dormant provider (one assignIn line + features:sync).
data_region shipped null — residency is a production gate, not a code guarantee. The grant
carries no data_region/dpa_ref, so ProviderRouter::assertResidency imposes no in-region
constraint on GB/EU KYB/KYC PII routed to Sumsub. This matches Conduit's incumbent null-residency
posture and is acceptable pre-product (no real data), but it is a deliberate deferral: binding a
real data_region is a documented pre-production go-live gate (Sumsub's EU/UK LDP hostname is still
unverified; the DPA must be recorded). A structural limitation informs this: the EU bloc
jurisdiction has region = null (ISO region data covers member countries, not supranational blocs),
and assertResidency refuses when the jurisdiction's region does not equal the grant's
data_region — so a non-null data_region='Europe' on the EU-bloc grant would residency_conflict
and refuse EU routing outright. EU-level residency cannot be expressed by the current mechanism;
when residency is wired it must be evaluated against the resolved member country's region (or
blocs must be given a region).
Ship-state: code-live (pre-product), production-gated. The grant is real, gate-green, and routes
GB/EU onboarding to Sumsub in dev/test — including the local UBO-gate approval path and the
web_sdk_token embed discriminator. It is not serving real EU/GB production traffic: three gates
must clear first — the frontend embed_mode branch (separate repo, D12), the residency/DPA binding
above, and the now-live compliance gaps (a beneficial-owner reconciler + operator runbooks for
missed-webhook / stuck-pending). These are tracked as explicit go-live gates in
10-onboarding-identity.md.
This decision builds on the router substrate (D94) and the onboarding-provider seam (D40/D52/D55/D59 — the mirror/reconcile backbone, the webhook-registry inversion, the tenant↔customer map); it does not restate them.