# OpenSpec Change: enterprise-currency - **Change ID**: enterprise-currency - **Package**: `@suaveplan/currency` - **Category**: finance - **Target path**: `finance/currency/currency` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/currency.md` --- ## Summary Deliver `@suaveplan/currency` on the genesis branch from scratch at enterprise quality. The package is a currency conversion engine with pluggable `CurrencyProvider` interface, TTL-based `RateCache`, automatic BFS cross-rate triangulation (shortest-path over the cached-rate graph), inverse-rate calculation, per-provider circuit breaker, `AbortSignal` cancellation, batch convert, and a `setVirtualRate` live-update API. Returns typed `ConversionResult` with provenance (`providerName`, `triangulatedThrough`). Ships two reference providers: `InMemoryProvider` and `createStaticProvider`. --- ## Current-state audit (genesis) Does not exist on genesis — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Typed `CURRENCY.*` error codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike` | | `@suaveplan/events` (`workspace:^`) | Virtual-rate change events | | `@suaveplan/retry` (`workspace:^`) | Circuit-breaker + backoff | | `@suaveplan/logger` (`workspace:^`) | Structured logs | | `zod` (`catalog:forms`) | Options validation | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters | | `tslib` (`catalog:build`) | Runtime helpers | Dev dep: `@suaveplan/money workspace:^` (for `MoneyLike` type in tests). --- ## Public API surface (from main branch enterprise spec) ```ts class CurrencyConverter { constructor(config: ConverterConfig) convert(from: string, to: string, amount: MoneyLike | number, options?: ConvertOptions): Promise convertAmount(from: string, to: string, amount: number, options?: ConvertOptions): Promise convertBatch(requests: Array<{from: string; to: string; amount: number}>): Promise setVirtualRate(symbol: string, rate: number): void invalidateCache(from?: string, to?: string): void } class RateCache { get(from: string, to: string): ExchangeRate | undefined set(from: string, to: string, rate: ExchangeRate): void prune(): void invalidate(from?: string, to?: string): void } function createStaticProvider(rates: Record>): CurrencyProvider class InMemoryProvider implements CurrencyProvider { setRates(rates: ExchangeRate[]): void } interface ConversionResult { amount: number rate: number inverseRate: number rateAgeMs: number providerName: string triangulatedThrough: string[] | null } interface ConverterConfig { providers: CurrencyProvider[] defaultBase?: string cacheTtlMs?: number maxRetries?: number logger?: LoggerLike } interface CurrencyProvider { name: string supportsFiat: boolean supportsCrypto: boolean fetchRates(options: RateFetchOptions): Promise isHealthy?(): boolean } ``` ### Error codes ```ts type CurrencyErrorCode = | 'CURRENCY_PROVIDER_UNAVAILABLE' | 'CURRENCY_UNSUPPORTED_PAIR' | 'CURRENCY_TRIANGULATION_IMPOSSIBLE' | 'CURRENCY_CONVERT_ABORTED' | 'CURRENCY_INVALID_CONFIG' function registerCurrencyErrorCodes(): void ``` --- ## New additions for genesis - **BFS triangulation** in `src/engine/triangulate.ts` - **Circuit breaker** per provider in `src/engine/circuit-breaker.ts` using `@suaveplan/retry` - **`convertBatch`** — coalesces required rate fetches - **`setVirtualRate` + change events** via `@suaveplan/events` - **`AbortSignal` on `convert`** - **Zod schemas** for `ConverterConfig`, `ConvertOptions` - **OTel** spans per convert, cache-hit counter, triangulation-path histogram --- ## File layout ```text finance/currency/currency/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/ currency.types.ts currency.types.md engine/ converter/ converter.ts converter.md converter.types.ts converter.test.ts index.ts rate-cache/ rate-cache.ts rate-cache.md rate-cache.test.ts index.ts triangulate/ triangulate.ts triangulate.md triangulate.test.ts index.ts circuit-breaker/ circuit-breaker.ts circuit-breaker.md circuit-breaker.test.ts index.ts provider/ in-memory-provider/ in-memory-provider.ts in-memory-provider.md in-memory-provider.test.ts index.ts static-provider/ static-provider.ts static-provider.md static-provider.test.ts index.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact Downstream: `@suaveplan/currency-providers` (implements `CurrencyProvider`), `@suaveplan/money` (integration for `MoneyLike`). **Prerequisites on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/retry`, `@suaveplan/logger`.