Files
2026-05-27 23:29:53 +01:00

5.5 KiB

Tasks: enterprise-currency

Change ID: enterprise-currency Package: @suaveplan/currency Target path: finance/currency/currency Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/retry, @suaveplan/logger


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree per proposal.md.
  • package.json at 0.1.0, "sideEffects": false, exports "." + engine + provider subpaths.
  • Deps: workspace + catalog as listed.
  • DevDeps: vitest catalog:testing, @vitest/coverage-istanbul catalog:testing, fast-check catalog:testing, @suaveplan/testing workspace:^, @suaveplan/money workspace:^.
  • tsconfig.json extending library.json; vitest.config.ts with createTestConfig().
  • Scripts: lint, typecheck, test, test:coverage, build, docs:check.

2. Types and errors

  • Define all interfaces and Zod schemas (ConverterConfigSchema, ConvertOptionsSchema) in src/types/currency.types.ts.
  • Write currency.types.md (≥200 words, both examples, API reference).
  • src/error-codes.ts: five codes; registerCurrencyErrorCodes(); eager call.

3. Rate cache

  • Implement RateCache in src/engine/rate-cache/rate-cache.ts:
    • get(from, to) / set(from, to, rate) / prune() / invalidate(from?, to?).
    • TTL check on get; prune on set when cache exceeds 1000 entries.
    • OTel counter currency.cache_hits_total / currency.cache_misses_total.
  • Write rate-cache.test.ts: TTL expiry, prune, invalidate, hit/miss tracking.
  • Write rate-cache.md (≥200 words).

4. Triangulation

  • Implement BFS triangulation in src/engine/triangulate/triangulate.ts:
    • findPath(cache, from, to): string[] | null — BFS over cached pairs to find shortest path; max 3 hops.
    • triangulateRate(cache, path): number — multiply rates along path.
  • Write triangulate.test.ts: direct found; one-hop; two-hop; no path returns null; cycle avoidance.
  • Write triangulate.md (≥200 words).

5. Circuit breaker

  • Implement per-provider ProviderCircuitBreaker in src/engine/circuit-breaker/circuit-breaker.ts:
    • States: closed → open (after N consecutive failures) → half-open (after cooldown) → closed.
    • wrap(fn): Promise<T> — skips call when open; re-tries in half-open.
    • Uses @suaveplan/retry for backoff calculation.
  • Write circuit-breaker.test.ts: consecutive failures open circuit; cooldown half-open; success resets.
  • Write circuit-breaker.md (≥200 words).

6. Providers

  • Implement InMemoryProvider and createStaticProvider in respective feature dirs.
  • Write unit tests + .md for each.

7. CurrencyConverter

  • Implement CurrencyConverter in src/engine/converter/converter.ts:
    • Constructor validates config via ConverterConfigSchema; calls registerCurrencyErrorCodes() eagerly.
    • convert(from, to, amount, options?):
      1. Check cache; if hit, return without provider call.
      2. For each provider (skip open circuit): call fetchRates({ symbols: [to], base: from }); on success cache result.
      3. If no direct rate found: BFS triangulate from cache.
      4. If still not found: throw CURRENCY_TRIANGULATION_IMPOSSIBLE.
      5. Honour AbortSignal between each provider attempt.
      6. Return ConversionResult with providerName, triangulatedThrough.
      7. OTel span currency.convert; counter currency.converts_total with {direct, triangulated}.
    • convertBatch(requests): group by provider calls, execute, map results.
    • setVirtualRate(symbol, rate): update cache; emit currency:rate:updated event via @suaveplan/events.
    • invalidateCache(from?, to?): delegate to RateCache.
  • Write converter.types.ts.
  • Write converter.test.ts (≤500 lines): direct convert; cache hit; triangulation; AbortSignal; circuit breaker open; virtual rate; batch; all providers fail throws.
  • Write converter.md (≥200 words, simple + advanced, API reference).

8. Integration + property tests

  • tests/integration.test.ts: three-provider fallback chain; circuit breaker in play; virtual rate update.
  • tests/property.test.ts (fast-check): convert(from, to) then convert(to, from) yields approximately inverse; convertBatch results equal individual converts.

9. Docs + verify + publish

  • All .md per layout; root README.md with quick-start, provider guide, BFS triangulation explanation, API table.
  • bunx turbo lint --filter=@suaveplan/currency — zero errors.
  • bunx turbo typecheck --filter=@suaveplan/currency — zero errors.
  • bunx turbo test --filter=@suaveplan/currency — all pass.
  • bunx turbo test:coverage --filter=@suaveplan/currency — 100%.
  • bun scripts/publish.ts --package @suaveplan/currency.
  • Update CLAUDE.md; archive to openspec/archive/2026-04-24-enterprise-currency/.