Skip to main content

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 of 10-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.


← Decision log index