# Lakshmi Domain — Architecture

> Personal and household finance for the Oshun monorepo: a deep, typed library
> of financial domain logic (account modelling, budgeting, tax, investments,
> retirement, estate, behavioral finance) sitting on a Drizzle/PostgreSQL data
> layer, fronted by a set of service applications. This page describes the real
> code under `libs/lakshmi/*` and `apps/lakshmi/*`, and is honest about which
> parts are fully implemented versus which are runtime skeletons not yet wired
> to the domain logic.

---

## What Lakshmi Is

Lakshmi is the bounded context for **personal and household financial
intelligence**. Its purpose is to give a user a single, consent-driven view of
every account they hold — checking, savings, credit cards, loans, brokerage and
retirement accounts, crypto wallets, real estate, insurance policies, business
equity — and to turn that aggregated picture into planning: budgets, debt
payoff, tax optimization, retirement projection, estate readiness, and
behavioral nudges.

Where Neith is the lowest layer of the stack, Lakshmi is a **product domain near
the top**: it consumes shared infrastructure and adjacent domains' facts (Aje
for on-chain assets, Cybele for property facts, Gaia for climate/energy inputs)
and owns one thing exclusively — _the personal-finance decision_. It is the
source of truth for what a user's net worth is, how their budget is tracking,
and whether their retirement is on course. It is named after the Hindu goddess
of wealth and prosperity.

The design principle that shapes every layer is **consent-first**: no financial
data enters the system without explicit user consent, every connection is
revocable, and every row is scoped to its owner at the database level.

---

## Implementation Status — Read This First

Earlier drafts of this page called Lakshmi a "fully-implemented platform" where
"every feature exists in source." That is half-true and worth stating precisely,
because the split matters for anyone working here:

- **The domain libraries are real and deep.** The 24 packages under
  `libs/lakshmi/*` contain genuine, domain-specific financial logic —
  calculation engines (amortization, time-value-of-money, Monte Carlo, tax
  brackets, risk metrics, Social Security), a 32-variant account model, TF-IDF
  transaction categorization, portfolio performance math (TWR/MWR/XIRR),
  tax-loss harvesting, and more. These are pure input→output modules with their
  own types and tests.
- **The data layer is real.** `@lakshmi/db` declares a full Drizzle schema, RLS
  policy generation, TimescaleDB hypertable plans, a pgvector similarity index,
  connection pooling, a migration registry, and seed data.
- **The service runtime is partly a skeleton.** The six `apps/lakshmi/*`
  services provide real _infrastructure_ — a Hono gateway with JWT/OAuth/RBAC
  middleware and OpenAPI generation, a BullMQ queue/DLQ system, a cron
  scheduler, typed Kafka event schemas. But the **HTTP data plane is not yet
  wired to the domain libraries**: the gateway's route handlers return
  placeholder empty payloads (`{ accounts: [] }`, `taxProfile: null`) with
  comments like _"Domain service call will be implemented in `@lakshmi/accounts`
  library."_ The sync-engine and ai-agents services currently serve only health
  checks; the latter says so explicitly in its source.

So Lakshmi today is **a strong domain core and data layer with a deliberately
fail-honest service shell around it**. The libraries can be imported and used
directly; the end-to-end HTTP/sync/AI pipelines are scaffolding awaiting wiring.
Items genuinely deferred are labelled `(planned)` below.

---

## Workspace Shape

The domain is **24 TypeScript libraries** under `libs/lakshmi/*` and **6 service
applications** under `apps/lakshmi/*`.

```text
apps/lakshmi/
  api-gateway/        Hono HTTP/WS gateway: auth, RBAC, OAuth scopes, OpenAPI, Kafka bus (:4200)
  sync-engine/        health/ready surface; open-banking polling (planned wiring) (:4201)
  ai-agents/          health/ready surface; financial agents (planned wiring) (:4202)
  worker/             BullMQ background worker: 5 queues + DLQs
  scheduler/          BullMQ repeatable-job scheduler (:3804)
  browser-extension/  MV3 extension: shopping-context capture (popup/content/service-worker)

libs/lakshmi/
  core/  db/                                   ← foundation
  accounts/  integrations/                     ← integration
  transactions/ budgeting/ behavioral/         ← intelligence
  investments/ tax/ debt/ credit/ retirement/
  insurance/ estate/ real-estate/ crypto/
  income/ goals/ household/ business/ ai-engine/
  security/                                    ← security
  reporting/ alerts/                           ← experience
```

Note: there is **no** `libs/contracts/src/lakshmi` or
`libs/openapi/src/specs/lakshmi` package in source. Cross-domain contracts under
`@contracts/lakshmi` are **(planned)**; the OpenAPI document is generated at
runtime inside the gateway (`apps/lakshmi/api-gateway/src/public-rest-api.ts`),
not published as a shared spec package.

---

## Layered Architecture

The libraries are organized into five dependency layers. Higher layers depend on
lower ones; the reverse is never allowed, which keeps core primitives stable
while the experience layer evolves.

- **Core** — `@lakshmi/core` (money, account, user/household primitives, the
  calculation engine) and `@lakshmi/db` (the persistence layer). Foundation for
  everything above.
- **Integration** — `@lakshmi/accounts` (aggregation-provider adapters, sync,
  statement import, account management) and `@lakshmi/integrations`
  (open-banking, payroll, credit-bureau, crypto, real-estate, document
  gateways). Normalizes raw provider data into domain objects.
- **Intelligence** — the bulk of the domain: `@lakshmi/transactions`,
  `@lakshmi/budgeting`, `@lakshmi/behavioral`, `@lakshmi/investments`,
  `@lakshmi/tax`, `@lakshmi/debt`, `@lakshmi/credit`, `@lakshmi/retirement`,
  `@lakshmi/insurance`, `@lakshmi/estate`, `@lakshmi/real-estate`,
  `@lakshmi/crypto`, `@lakshmi/income`, `@lakshmi/goals`, `@lakshmi/household`,
  `@lakshmi/business`, and `@lakshmi/ai-engine`. Derives meaning and
  recommendations from ingested data.
- **Security** — `@lakshmi/security` (encryption, key derivation, RBAC, MFA,
  GDPR/CCPA/SOC2 controls, privacy-preserving primitives). Enforces privacy
  across layers, complementing the database's row-level security.
- **Experience** — `@lakshmi/reporting` (statements, exports, shareable links)
  and `@lakshmi/alerts` (threshold notifications), surfaced through the
  `apps/lakshmi/*` services.

---

## The Core Domain Model (`@lakshmi/core`)

`@lakshmi/core` is the foundation every other library imports. Its public
surface (`libs/lakshmi/core/src/index.ts`) re-exports caching, Prometheus
metrics, financial primitives, domain types, and the calculation engine. The
[specifications page](./specifications.md) enumerates the full type system; the
load-bearing pieces are:

- **Branded ID types** (`Brand<T, B>` in `types/account.ts` and `types/user.ts`)
  — `UserId`, `HouseholdId`, `AdvisorId`, `AccountId`, `ConnectionId`,
  `InstitutionId`, `ManualAssetId`. These are compile-time unique so an
  `AccountId` cannot be passed where a `UserId` is expected.
- **`FinancialAccount`** (`libs/lakshmi/core/src/types/account.ts`) — the
  central type: a discriminated union over a `type` field with **32 account
  variants** across eight `AccountCategory` groups (`depository`, `credit`,
  `loan`, `investment`, `crypto`, `real_estate`, `business`, `manual`). Each
  carries an `AccountSign` (`asset`/`liability`) that drives its net-worth
  contribution. Using a discriminated union forces every code path to handle
  every variant.
- **`Money`** (`libs/lakshmi/core/src/primitives/money.ts`) — an immutable value
  object holding integer minor units plus an ISO 4217 currency. Arithmetic
  preserves currency and rejects cross-currency operations; `fromDecimal`,
  `divide`, `allocate`, and `split` use banker's rounding (round-half-to-even)
  and conserve remainder pennies. **Every monetary field in the domain is
  integer cents** to avoid floating-point drift.
- **The calculation engine** (`libs/lakshmi/core/src/calculations/`) — a
  deterministic suite kept separate from the domain types so it can be tested
  against known answers: `amortization` (fixed/ARM/interest-only/balloon,
  per-payment schedules, extra-payment modelling), `tvm` (PV/FV/NPV/IRR/XIRR),
  `monte-carlo` (retirement and goal simulation), `tax-brackets` (federal +
  50-state, 2024), `risk-metrics` (Sharpe/Sortino/VaR/CVaR/beta/alpha), and
  `social-security` (PIA/COLA/claiming adjustments).

Transactions and goals are deliberately **not** core entities — they are owned
by `@lakshmi/transactions` and `@lakshmi/goals` and persisted by `@lakshmi/db`.

---

## Persistence and the Data Layer (`@lakshmi/db`)

`@lakshmi/db` (`libs/lakshmi/db/src/index.ts`) is fully implemented and is the
single owner of Lakshmi's PostgreSQL schema.

- **Namespaces and tables.** `src/schema/namespaces.ts` declares **19 PostgreSQL
  schema namespaces** (`accounts`, `transactions`, `budgets`, `investments`,
  `tax`, `debt`, `credit`, `retirement`, `insurance`, `estate`, `real_estate`,
  `crypto`, `income`, `goals`, `behavioral`, `household`, `business`, `alerts`,
  `audit`), mirrored by `LAKSHMI_SCHEMA_NAMES` in the migration registry. Across
  `src/schema/*.ts` there are roughly **80 Drizzle tables** (82
  `…Schema.table(...)` declarations), mapped to their namespaces by
  `LAKSHMI_TABLE_SCHEMA_BY_NAME`. _(Earlier drafts said "20 namespaces / 78
  tables"; the source has 19 and ~80.)_
- **Row-level security.** `src/migrations.ts` generates real RLS SQL. Helpers
  like `buildUserScopedRlsPolicy`, `buildHouseholdSharedRlsPolicy`,
  `buildHouseholdOwnerRlsPolicy`, and `buildParentScopedRlsPolicy` emit
  `ENABLE ROW LEVEL SECURITY` plus `CREATE POLICY` statements predicated on
  `current_setting('app.current_user_id', true)::uuid`. A query physically
  cannot return another user's rows even if an application check is bypassed —
  the database enforces the boundary.
- **Time-series.** `TIMESCALE_HYPERTABLE_CONFIGS` + `applyTimescaleHypertables`
  convert the time-series tables (balance/net-worth/price/benchmark/credit-score
  history) into TimescaleDB hypertables via `create_hypertable(...)`, and the
  routine **skips gracefully if TimescaleDB is not installed** rather than
  failing the migration.
- **Vector search.** The same migration builds a **pgvector IVFFlat index** on
  transaction embeddings for similarity-based categorization. (The gateway's
  `/v1/transactions/search` endpoint anticipates this but is not yet wired.)
- **Connection pooling.** `src/connection.ts` exposes three purpose-built `pg`
  pools — a primary pool, a high-throughput transaction-ingest pool (short
  statement timeout, larger max), and an analytics pool (120 s statement timeout
  for Monte Carlo / report queries) — each wrapped by Drizzle.
- **Seed and reference data.** `src/seed-data/` ships 2024 federal/state tax
  brackets, IRS contribution limits, RMD uniform lifetime table, Social Security
  benefit tables, market benchmarks, credit-score factor weightings, the Plaid
  transaction-category taxonomy, and insurance product templates.

---

## Intelligence: Where Data Becomes Insight

The intelligence libraries are the heart of the domain. Each owns one reasoning
area, shares `@lakshmi/core` primitives, and is otherwise independent so it can
be tested and evolved alone. A representative example shows the depth and the
honesty caveat that applies to a few modules:

**Transaction categorization**
(`libs/lakshmi/transactions/src/categorization/engine.ts`) is a real algorithm:
text normalization → tokenization → TF-IDF scoring against a per-category
keyword centroid → cosine similarity → confidence tiering (`HIGH ≥ 0.85`,
`MEDIUM 0.70–0.84`, `LOW 0.50–0.69`, `VERY_LOW < 0.50`), with low-confidence
results routed to a `ManualReviewQueue` and a `PersonalizationEngine` that
learns from user corrections. (The module's docstring quotes accuracy
percentages and a future pgvector/Qdrant ANN path; treat those as aspirational —
the shipped logic is the TF-IDF cosine pipeline, and the README's "98%+ accuracy
/ 20,000+ institutions" are marketing claims, not measured metrics.)

The other intelligence modules follow the same pattern of concrete domain math:
`@lakshmi/budgeting` has distinct `ZeroBudgetEngine`/`CategoryBudgetEngine`/
`CashFlowBudgetEngine` plus velocity tracking and forecasting;
`@lakshmi/investments` computes TWR/MWR/XIRR, VaR/CVaR, factor exposure, and
tax-aware rebalancing; `@lakshmi/tax` does wash-sale-aware tax-loss harvesting
and Roth-ladder planning; `@lakshmi/retirement` runs Monte Carlo projections and
Social Security optimization; `@lakshmi/estate` tracks beneficiaries, an
encrypted document vault, and probate-avoidance analysis. The specifications
page lists the engine classes per module.

`@lakshmi/ai-engine` is intended to sit above these modules and provide
reasoning, recommendation, and explanation. The _library_ exists; the _service_
that would orchestrate it (`apps/lakshmi/ai-agents`) is a health-check skeleton
today (see below).

---

## Service Runtime — Honest State

### API Gateway (`apps/lakshmi/api-gateway`, port 4200)

`src/app.ts` builds a real Hono application. **Implemented:** a global
middleware stack (request logger, CORS with an allow-list, secure headers),
`/health` and `/ready` probes (the latter pings Redis and checks Kafka), runtime
OpenAPI generation (`/openapi.json`, `/v1/meta`,
`/.well-known/oauth-authorization-server`), and a v1 router protected by
`authenticate()` (JWT verification via `jose`, claims in
`src/middleware/auth.ts`), `rateLimitMiddleware` (Redis-backed), per-route OAuth
scope enforcement (`requireOAuthScopes`, driven by the endpoint catalog), and
per-route RBAC (`requirePermissions`, driven by
`LAKSHMI_ROUTE_PERMISSION_POLICIES`). The JWT model supports
advisor-acting-on-behalf-of-client and household-role claims.

**Not implemented:** the route _handlers themselves_. `GET /v1/accounts` returns
`{ accounts: [] }`; `/v1/tax/profile` returns `taxProfile: null`; almost every
handler returns an empty array or `null` with a comment that the domain-service
call is still to be added. The gateway is a complete, secured HTTP front door
that does not yet call the domain libraries or the database.

### Sync Engine (`apps/lakshmi/sync-engine`, port 4201)

The docstring describes open-banking polling across
Plaid/Yodlee/MX/Finicity/Tink and Kafka publication, but `src/index.ts`
currently implements only a `node:http` server answering `/health` and `/ready`
(reporting the configured provider list, sync interval, and concurrency from
env). The provider adapters live in `@lakshmi/accounts/providers`; the service
does not yet invoke them. **(Polling loop: planned.)**

### AI Agents (`apps/lakshmi/ai-agents`, port 4202)

`src/index.ts` is explicit and fail-honest: _"The financial agents themselves …
are NOT yet implemented in this service — this process currently serves health
checks only."_ Its health payload reports **only the model providers whose
credentials are actually present in the environment**
(`resolveConfiguredModelProviders` checks
`OPENAI_API_KEY`/`ANTHROPIC_API_KEY`/Google keys) rather than asserting
connections that may not exist. **(Agent orchestration: planned.)**

### Worker and Scheduler

`apps/lakshmi/worker/src/queues.ts` is fully implemented BullMQ infrastructure:
five priority queues (`account-sync`, `transaction-categorize`,
`ai-recommendation`, `report-generation`, `data-export`), per-queue
retry/backoff policies, typed job payloads, enqueue helpers with deduplicated
job IDs, a typed `createWorker` factory with structured logging, and an
`attachDlqForwarder` that moves permanently-failed jobs to per-queue dead-letter
queues. The job _processors_ (the functions that would call domain logic) are
injected by the caller and are not yet supplied.

`apps/lakshmi/scheduler/src/schedules.ts` defines the real recurring tasks
(`buildRecurringTasks`): a 15-minute due-connection sync sweep, a 5-minute
uncategorized-transaction sweep, a daily AI-recommendation cron (`0 3 * * *`), a
daily report-rollup cron, and an hourly expired-export maintenance cron — each
enqueued into the corresponding worker queue with a stable `jobId`.

### Browser Extension (`apps/lakshmi/browser-extension`)

A Manifest V3 extension (service worker, content script, popup) that captures
shopping context (`src/shared/shopping-context.js`) to support behavioral/spend
insight at the point of purchase. It is plain JS with its own build and
validation scripts and Playwright/vitest tests.

### Event Bus

`apps/lakshmi/api-gateway/src/events.ts` defines versioned Zod schemas for all
domain events (account-sync, new/categorized transactions, alerts, rebalance,
tax harvest, goal milestone, AI recommendation) with explicit Kafka topic
routing and a correlation ID propagated from the originating request — the
contract by which the gateway, sync engine, worker, and downstream consumers
would decouple.

---

## Component and Data Flow

The diagram shows the intended runtime topology. Solid arrows are wired today;
dashed arrows are the planned handler/processor wiring that is not yet in
source.

```mermaid
flowchart TD
  subgraph Clients
    UI[Web / Mobile / Advisor portal]
    EXT[Browser extension]
  end

  subgraph Services[apps/lakshmi]
    GW[api-gateway Hono\nJWT + OAuth + RBAC + OpenAPI]
    SYNC[sync-engine\nhealth only today]
    AI[ai-agents\nhealth only today]
    WORK[worker BullMQ\n5 queues + DLQs]
    SCHED[scheduler\nrepeatable crons]
  end

  subgraph Libs[libs/lakshmi domain logic]
    CORE[core: Money, accounts,\ncalculations]
    INTEL[intelligence: transactions,\nbudgeting, investments, tax, ...]
    SEC[security]
    DB[(@lakshmi/db\nDrizzle + RLS + Timescale + pgvector)]
  end

  subgraph Infra
    PG[(PostgreSQL\nrow-level security)]
    REDIS[(Redis\ncache + rate limit + BullMQ)]
    KAFKA[(Kafka\ndomain events)]
    S3[(MinIO S3\nreceipts/docs/exports)]
  end

  UI --> GW
  EXT --> GW
  GW -->|auth, rate-limit, OpenAPI| REDIS
  GW -->|publish events| KAFKA
  GW -.->|planned: call domain services| INTEL
  SCHED -->|enqueue| REDIS
  WORK -->|consume jobs| REDIS
  WORK -.->|planned: invoke| INTEL
  SYNC -.->|planned: poll providers, ingest| INTEL
  AI -.->|planned: orchestrate| INTEL
  INTEL --> CORE
  INTEL --> DB
  CORE --> DB
  SEC --> DB
  DB --> PG
  WORK --> S3
  KAFKA --> WORK
```

---

## Invariants, Failure Modes, and Extension Points

**Invariants the codebase enforces.**

- _Money is integer minor units._ Every monetary value is cents; arithmetic goes
  through `Money` (banker's rounding, penny-conserving allocation,
  currency-safe).
- _IDs are branded._ Cross-entity reference errors are caught at compile time.
- _Rows are user-scoped at the database._ RLS predicated on
  `app.current_user_id` is the last line of defence regardless of application
  bugs; household-shared, household-owner, and parent-scoped policies layer on
  top.
- _Accounts are exhaustive._ The 32-variant discriminated union forces every
  consumer to handle every account type — adding a variant surfaces every place
  that must change.
- _Consent-first and revocable._ An `AccountConnection` only refreshes when
  `active`; revocation moves it to `revoked`.

**Failure modes the code already handles honestly.**

- _Missing credentials fail loud, not fake._ `@lakshmi/db` throws if
  `LAKSHMI_DATABASE_URL` is unset; ai-agents reports only configured model
  providers; readiness returns `503` when Redis or Kafka is down.
- _Degraded infrastructure degrades gracefully._ TimescaleDB hypertable creation
  is skipped (with a warning) when the extension is absent.
- _Background jobs have a dead-letter path._ Jobs that exhaust retries are
  forwarded to a DLQ with the original payload, failure reason, and attempt
  count.

**Extension points.** Add an account type by extending the `FinancialAccount`
union and its helper switches in `core/src/types/account.ts`. Add an aggregation
provider via the `AggregationProvider` enum and a `@lakshmi/accounts/providers`
adapter. Add a background job by defining its payload + queue in
`worker/src/queues.ts` and a sweep in `scheduler/src/schedules.ts`. Add an HTTP
endpoint by extending the gateway route catalog (which auto-derives OAuth-scope
enforcement and the OpenAPI document). The most valuable open work is wiring the
existing gateway handlers and worker processors to the existing domain
libraries.

---

## Cross-Domain Boundaries

Lakshmi's boundaries are drawn around ownership of the _financial decision_, not
raw data. Each adjacent domain owns its own source of truth and exposes it
through a defined contract:

- **Aje** owns blockchain/Web3 infrastructure; `@lakshmi/crypto` consumes
  on-chain asset data from Aje rather than re-implementing chain access, and
  owns how those assets appear in net worth and tax.
- **Cybele** owns property records, construction costs, and AVM data;
  `@lakshmi/real-estate` consumes those facts but owns affordability,
  buy-vs-rent, equity, and rental economics.
- **Maat** owns enterprise/organizational finance; it may consume aggregate,
  consented business intelligence from Lakshmi but shares no database or model.
- **Themis** supplies governance policy and immutable audit records used for
  legal-transfer events (the V2 estate-vault surface).
- **Gaia** (Phase 175) supplies renewable-energy-potential and climate-risk
  products that Lakshmi consumes for household capacity planning, insurance
  context, property-risk, and financial-scenario modelling.

Two V2 adapter surfaces — `@v2/lakshmi-responsible-play-spend-insight`
(composing behavioral/budgeting/transactions for responsible-play store
confirmation) and `@v2/per-account-vault-estate-bridge` (bridging estate data to
`@oshun/identity` and `@themis/transparency`) — live in the **V2 services
tree**, not under `libs/lakshmi`. Both are off-rollback and must never feed
deterministic match simulation or competitive outcomes. See
[features.md](./features.md) for their contracts.

---

## Verification Expectations

Every financial calculation, categorization, aggregation, and alert path is
expected to carry deterministic tests (asserting computed values against
known-correct answers), plus privacy, consent, audit, and contract tests.
Integration tests use sandbox fixtures and **never** require live aggregation,
bureau, or model-provider credentials in CI — which is why the service layer's
external integrations are environment-gated and fail loud when unconfigured
rather than fabricating connections. The honest gap to close is the data-plane
wiring between the services and the already-built domain libraries.
