D98 — Utila custody go-live — Custody→utila (all six), no Conduit fallback, accepted travel-rule originator drop (RESOLVED)
Architecture decision record. Status, thematic clusters, and how to record a new ADR: the decision log index. Program context + the custody build plan (the increment ladder, the go-live runbook, the unblock criteria) live in
docs/tracking/multi-provider/20-custody.md§5a.
Custody flips to Utila across the whole corridor. After the entire provider path shipped
registered-not-granted (2.2a–2.2f-3 "d-prep" — the adapter, the mirror, the webhooks, the Stage-0
approval quorum + the real forward, the asset map, the signer/quorum retirement), the go-live grant
makes Utila the Custody PRIMARY for all six conduit-baseline jurisdictions (ZA/NG/KE/GB/EU/US).
This is the second worked instance of the D95 one-liner (Sumsub Onboarding), and — unlike D95's
two-jurisdiction corridor — it is a fleet-wide flip of a single capability group: every seeded
jurisdiction's custody traffic now routes to Utila, conduit everything else.
The mechanism is one declaration. In FeaturesServiceProvider::registerFeatureDeclarations(),
after registerConduitBaseline([...]) (which routes every CapabilityGroup to conduit) and the
D95 Onboarding line, one later line overrides only the (*, Custody) tuples:
$providers->assignIn(['ZA','NG','KE','GB','EU','US'], CapabilityGroup::Custody, ['utila','conduit'], 'utila').
features:sync realizes it into jurisdiction_provider_grants (central; no tenant-scoped migration —
the flip is config, not schema). Every other capability group in those jurisdictions stays
conduit-primary — the flip is Custody-only. Deploy, not code, is the live gate: the grant is a
one-line switch; the runbook (20-custody.md §5a) enumerates the deploy steps that make it behave
— vault provisioning + per-vault SA secrets, the config/utila.php assets contract-address publish,
features:sync, and the sandbox HARD/soft gates.
No Conduit fallback — conduit is an override-only permitted key. conduit is listed as a
PERMITTED (non-primary) key so a per-tenant tenant_provider_overrides back to Conduit needs no code
change (the escape hatch for legacy Conduit-modeled signer/quorum/wallet flows). It is NOT an
automatic custody-transaction fallback (the locked 2026-07-11 decision) — and it could not be one
anyway: the Custody group is co-dependent (all five capabilities resolve to ONE provider) and
Conduit does not implement ManagesCustodyTransactions, so a custody-transaction under a Conduit
override fails loud rather than silently mis-routing money. This is why the PRIMARY must be a
custody-tx-capable provider: only utila implements ManagesCustodyTransactions, so a non-tx primary
would fail-loud AdapterMissingCapability (500) at the per-capability bind (20-custody.md §5a note
(a)). Utila as primary satisfies that invariant.
data_region shipped null — residency is a production gate, not a code guarantee. Utila's data
residency is EU per the vendor, but the grant carries no data_region/dpa_ref, exactly as D95
shipped. This is forced by the same structural limitation: the EU bloc jurisdiction has
region = null (ISO region data covers member countries, not supranational blocs), and
ProviderRouter::assertResidency refuses when the jurisdiction's region ≠ 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. Binding a real residency region (against the resolved member
country's region, or by giving blocs a region — the deferred D95 fix) plus recording the DPA is a
documented pre-production go-live gate, not a blocker for the flip.
Accepted regulatory risk — the travel-rule field drop (option C). Utila's address-book create
(registerAddress) accepts only address/displayName/network, so three neutral travel-rule inputs
are not transmitted to Utila's screening, in two kinds: selfCustodyAttestation + originatorDetails
are DROPPED ENTIRELY (never transmitted, not retained), while custodyType is RETAINED LOCALLY
(carried into our records via mapRegisterResult, so not lost from our side) but is still NOT
transmitted — the self-hosted/VASP custody classification is itself a travel-rule determinant. The
drop is UNCONDITIONAL across all six granted jurisdictions (ZA/NG/KE/GB/EU/US) — not
conditioned on jurisdiction: EU/GB/US are the acute registration-mandate cases, while ZA
(FSCA)/NG/KE have emerging FATF travel-rule regimes and are not unaffected. This is a live,
knowingly ACCEPTED exposure per the 2026-07-11 owner decision — recorded honestly, not silently
omitted, and made visible at the drop point (UtilaRailsProvider::registerAddress) and in the
rails/payments READMEs. It is not a resolved gap: the remediation — local originator capture
at registration + a fail-closed registration-mandate guard that refuses a Utila-routed registration
for a mandate jurisdiction until captured — is DEFERRED and TRACKED as a go-live carry-over
(20-custody.md §5a criterion (6)). Remediation owner: TBD — a named compliance approver +
remediation owner is recorded before serving real EU/GB/US custody traffic.
Verify-in-place, pre-product. The flip is verified in place (no data migration; pre-product — the
freeze is at the v1 guard): the grant is real, gate-green, and routes a corridor tenant's Custody to
Utila in dev/test, driving the full money path — InitiateCustodyTransactionCommand → Stage-0 quorum
→ the SYSTEM forward (asset mapped via UtilaAssetMap, initiate + APPROVE co-sign on the real Utila
endpoints, ref stamped, approved → forwarded). It is not serving real production custody
movement until the deploy-time write-path steps land (20-custody.md §5a). One deploy-time subtlety
recorded for the flip: appending the transaction tenant resolver in d-prep un-darkened INBOUND Utila
transaction resolution at DEPLOY, not at the flip — safe only because no Utila wallet is mapped and no
Utila transaction webhook arrives until go-live.
This decision builds on the router substrate (D94), the first non-Conduit grant (D95), the ceremony-less custody contract (D96), and the custody approval-quorum engine (D97); it does not restate them.