Skip to main content

D91 — Per-tenant base currency: a nullable tenants.base_currency preference with a ?base= → tenant → fleet-default resolution chain (RESOLVED, extends D49)

Architecture decision record, relocated verbatim from the retired single-file docs/tenancy/ decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.

The master-balance base currency was effectively global (one fleet-wide treasury.base_currency + the per-request ?base= override — the deferral recorded in the treasury deferrals doc). It is now a per-tenant setting: a nullable base_currency column on the central tenants table — the established simple-tenant-preference pattern (the level/jurisdiction precedent: a natural UPPERCASE code, a real custom column registered in Tenant::getCustomColumns()), not a new settings mechanism. A DB CHECK built from SupportedAsset::values() (the D84 NOT VALID + VALIDATE idiom) backstops raw writes.

The resolution chain (in GetMasterBalanceHandler::resolveBaseCode()): the explicit ?base= param (allow-list-validated at the controller) → the tenant's stored preference → the treasury.base_currency fleet default. A STORED preference outside the currently-supported base set (SupportedBaseCurrencies, now config-driven via treasury.supported_base_currencies, V1 ['USD']) is NOT honoured — honouring it would produce a structurally all-unconverted balance — so the rollup falls back to the fleet default and logs treasury.master_balance.unsupported_tenant_base_currency: the read stays correct, the misconfiguration observable. treasury:warm-rates warms every supported base (∪ the fleet default), not just the default, so a tenant-preferred base's read path is primed too. No operator/API surface sets the column yet (ops-write only, like level/jurisdiction) — a backoffice setter is a tracked follow-up.


← Decision log index