# High-Level Architecture

This page is the one-screen mental model of Oshun V1: how a request travels from
a client surface, through the shared shell and the single BFF, into the
customer-facing domains, down through the platform substrates and the shared
foundations, and finally to the data and runtime stores. It serves engineers and
platform leads who need to know _which box a thing lives in_ before opening any
single subsystem page, and it sits directly above the per-subsystem pages —
[Product Surfaces](./product-surfaces.md),
[Customer-Facing Domains](./customer-domains.md), the substrate pages, and
[Foundations](./foundations.md) — that zoom into each layer. It is part of the
V1 architecture set hubbed at [../ARCHITECTURE.md](../ARCHITECTURE.md).

## The V1 Stack at a Glance

V1 is a strict subset of the wider Oshun monorepo: **six customer-facing
domains** (Tara, Veritas, Nyx, Arete, Nisaba, Metis), **six platform
substrates** (the five product substrates Sophia, Iris, Psyche, Lilith, Isis,
plus the Aje payment substrate), **six cross-domain support subsystems** (Aja,
Yemaya, Themis, plus the Studio-only Bellona, Hathor, Neith), a set of operator
and distribution surfaces, and the foundations they all share. Everything below
is wired so that exactly one path exists from any client to any domain: there is
a single shell, a single BFF, a single contracts package, and one typed adapter
per substrate.

```mermaid
flowchart TB
    subgraph CLIENTS["Client Surfaces"]
        direction LR
        web["Customer Web"]
        mobile["Customer Mobile"]
        adminWeb["Admin Web"]
        adminMobile["Admin Mobile"]
        web ~~~ mobile ~~~ adminWeb ~~~ adminMobile
    end

    shell["<b>Shared Consumer Shell</b><br/><sub>@oshun/shell-* · navigation · offline · ui · design-tokens</sub>"]

    bff["<b>Oshun BFF</b> <sub>· apps/oshun/bff</sub><br/><sub>Fastify · OpenAPI 3.1 · Idempotency · Tenant · Residency</sub><br/><sub>Routes /api/oshun/domains/{tara, veritas, nyx, arete, nisaba, metis}</sub>"]

    subgraph DOMAINS["Customer-Facing Domains · libs/oshun/domain-*"]
        direction LR
        tara[Tara]
        veritas[Veritas]
        nyx[Nyx]
        arete[Arete]
        nisaba[Nisaba]
        metis["Metis<br/><sub>apps/metis/*</sub>"]
        tara ~~~ veritas ~~~ nyx ~~~ arete ~~~ nisaba ~~~ metis
    end

    subgraph SUBSTRATES["Platform Substrates"]
        direction LR
        sophia["<b>Sophia</b><br/><sub>evidence-sophia</sub>"]
        iris["<b>Iris</b><br/><sub>memory-iris</sub>"]
        psyche["<b>Psyche</b><br/><sub>embodiment-psyche</sub>"]
        lilith["<b>Lilith</b><br/><sub>persona-policy-lilith</sub>"]
        isis["<b>Isis</b><br/><sub>generation-control-isis</sub>"]
        aje["<b>Aje</b><br/><sub>libs/aje · payments-bridge</sub>"]
        sophia ~~~ iris ~~~ psyche ~~~ lilith ~~~ isis ~~~ aje
    end

    subgraph FOUNDATIONS["Foundations · Shared Infrastructure"]
        direction LR
        contracts["@oshun/<br/>contracts"]
        persistence["@oshun/<br/>persistence"]
        eventbus["@oshun/<br/>event-bus"]
        queue["@oshun/<br/>queue"]
        integrations["@oshun/inbound-<br/>integrations"]
        residency["@oshun/<br/>data-residency"]
        identity["@oshun/<br/>identity"]
        audit["@oshun/<br/>audit-platform"]
        contracts ~~~ persistence ~~~ eventbus ~~~ queue
        queue ~~~ integrations ~~~ residency ~~~ identity ~~~ audit
    end

    subgraph DATA["Data &amp; Runtime"]
        direction LR
        pg[("PostgreSQL<br/>+ pgvector")]
        redis[("Redis<br/>Streams · cache")]
        minio[("MinIO / S3")]
        es[("Elasticsearch")]
        qdrant[("Qdrant")]
        neo4j[("Neo4j")]
        kafka[("Kafka")]
        otel{{"OTLP · Jaeger<br/>Prometheus · Grafana"}}
        pg ~~~ redis ~~~ minio ~~~ es
        es ~~~ qdrant ~~~ neo4j ~~~ kafka ~~~ otel
    end

    CLIENTS --> shell
    shell -->|HTTPS + WebSocket| bff
    bff --> DOMAINS
    DOMAINS --> SUBSTRATES
    SUBSTRATES --> FOUNDATIONS
    FOUNDATIONS --> DATA

    classDef tier fill:#fef3c7,stroke:#b45309,color:#1f2937
    classDef substrate fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
    classDef found fill:#ecfeff,stroke:#0e7490,color:#083344
    classDef store fill:#f3e8ff,stroke:#6d28d9,color:#3b0764
    class tara,veritas,nyx,arete,nisaba,metis tier
    class sophia,iris,psyche,lilith,isis,aje substrate
    class contracts,persistence,eventbus,queue,integrations,residency,identity,audit found
    class pg,redis,minio,es,qdrant,neo4j,kafka,otel store
```

The diagram is layered top-to-bottom, and the layering is load-bearing: each
layer talks only to the one directly beneath it. Client surfaces never call the
BFF directly — they call it _through_ the shared shell. Domains never reach into
data stores directly — they go through substrates and foundations. This is what
keeps the six domains swappable and the substrate runtimes hidden behind stable
shapes.

## The Five Architectural Commitments

V1 leans on five decisions that the rest of the document elaborates. They are
the reason the stack reduces to a single readable diagram instead of a mesh.

### 1. A single BFF fronts every Oshun-side domain

There is exactly one Backend-for-Frontend at `apps/oshun/bff` (package
`@oshun/bff`), built on Fastify with OpenAPI 3.1, idempotency keys, tenant
scoping, and data-residency enforcement. Every customer-facing domain is reached
through one BFF prefix, `/api/oshun/domains/<domain>`. These prefixes are not
folklore — they are declared as the single source of truth in
`libs/oshun/domain-registry/src/registry.ts`, where each domain entry carries a
`bff-base-path`:

| Domain  | BFF base path                |
| ------- | ---------------------------- |
| Tara    | `/api/oshun/domains/tara`    |
| Veritas | `/api/oshun/domains/veritas` |
| Nyx     | `/api/oshun/domains/nyx`     |
| Arete   | `/api/oshun/domains/arete`   |
| Nisaba  | `/api/oshun/domains/nisaba`  |
| Metis   | `/api/oshun/domains/metis`   |

The registry order — `tara, veritas, nyx, arete, nisaba, metis` — is the
canonical `OSHUN_DOMAIN_IDS` ordering exported from the same file, and it is the
ordering the BFF, the shell, and the navigation layer all honor. Because the BFF
is singular, cross-cutting concerns (auth, idempotency, tenant isolation,
residency routing, rate limits, audit) are implemented and tested once rather
than per surface. See [Communication Patterns](./communication-patterns.md) for
the request envelope and
[Data Architecture and Tenancy](./data-architecture-tenancy.md) for residency
and isolation.

### 2. A single contracts package drives every spec

`@oshun/contracts` at `libs/contracts/` holds the Zod schemas that define every
request and response shape. Those schemas are not duplicated into hand-written
OpenAPI — they _generate_ the OpenAPI specs under `libs/openapi/src/specs/`. The
practical consequence is that the BFF, the typed domain adapters, the admin
surfaces, and the assistant all share one definition of truth: change a schema
in one place and the spec, the types, and the contract tests move with it. Each
domain keeps its own contracts under `libs/contracts/src/<domain>/` (Zod schemas
plus spec tests), so the single package is internally partitioned by domain.

### 3. Per-domain adapters present typed read APIs to the shell

Under `libs/oshun/domain-*`, each domain ships an adapter that exposes a typed
read API to the shared shell, the admin surfaces, and the assistant. The shell
never imports a domain's internal runtime — it imports the adapter's stable
shape. This is the boundary that lets a domain rework its internals without
forcing a shell change, and it is why the shell can render all six domains with
one composition strategy.

### 4. Substrate adapters hide the substrate runtimes behind one shape

Each of the platform substrates is consumed through a thin Oshun-side adapter
whose only job is to present one stable shape and hide the underlying runtime:

| Substrate | Adapter package                   | What it hides                         |
| --------- | --------------------------------- | ------------------------------------- |
| Sophia    | `evidence-sophia`                 | grounding / evidence runtime          |
| Iris      | `memory-iris`                     | assistant memory + continuation state |
| Psyche    | `embodiment-psyche`               | real-time runtime                     |
| Lilith    | `persona-policy-lilith`           | contemplative persona policy          |
| Isis      | `generation-control-isis`         | generation control                    |
| Aje       | `libs/aje/` via `payments-bridge` | non-custodial settlement              |

The Aje payment substrate (`libs/aje/`) is consumed specifically through the
Oshun-side bridge `libs/oshun/payments-bridge/` so that settlement stays
non-custodial and the rest of the platform sees only an entitlement-shaped
interface. Each substrate has its own deep-dive:
[Sophia](./substrate-sophia.md), [Iris](./substrate-iris.md),
[Psyche](./substrate-psyche.md), [Lilith](./substrate-lilith.md),
[Isis](./substrate-isis.md), and [Aje](./substrate-aje.md).

### 5. Metis runs as its own stack, integrated through the shell

Metis is the launch-blocking education product and the **only** V1 domain with
its own dedicated apps and microservice stack beneath `apps/metis/`. The Metis
runtime is `apps/metis/{web,admin,api-gateway,worker}` and is integrated into
the Oshun shell through `libs/metis/api-client` (renamed under the Oshun adapter
convention per `V1/TODOS.md` § 1.3). Every other customer-facing domain renders
through `apps/oshun/web` and `apps/oshun/mobile` via the shared shell and the
BFF; Metis is the exception that brings its own backend.

> **A nuance the surface table flattens:** Metis is _not_ solely rendered
> through `apps/oshun/mobile`. There is also a separate Metis mobile app at
> `apps/metis/mobile`. So while every _other_ domain is mobile-rendered only
> through the Oshun mobile shell, Metis has both an Oshun-shell presence and its
> own dedicated Metis mobile surface. The `apps/metis/` tree therefore contains
> `web`, `admin`, `api-gateway`, `worker`, **and** `mobile`.

## How a Request Actually Flows

The arrows in the diagram describe a concrete journey. Walk through a customer
opening a Veritas claim from the web:

1. **Client → Shell.** The customer is in `apps/oshun/web`. They click a claim
   card. The click is handled inside the shared shell, not by a bespoke page —
   the shell owns navigation, current-domain persistence, and command-surface
   registration via `@oshun/navigation`.
2. **Shell → BFF.** The shell issues an HTTPS request (and, for live work, a
   WebSocket) to the single BFF. It hits the Veritas prefix
   `/api/oshun/domains/veritas`, carrying the tenant, residency, and idempotency
   context the BFF enforces.
3. **BFF → Domain.** The BFF dispatches to the Veritas domain adapter under
   `libs/oshun/domain-*`, validated against the Zod contracts in
   `libs/contracts/src/veritas/`.
4. **Domain → Substrates.** The Veritas domain pulls grounding through the
   Sophia adapter (`evidence-sophia`), assistant context through Iris
   (`memory-iris`), and so on — each substrate behind its single stable shape.
5. **Substrates → Foundations → Data.** The substrates persist and read through
   the foundations (`@oshun/persistence`, `@oshun/event-bus`, `@oshun/queue`,
   `@oshun/data-residency`, `@oshun/identity`, `@oshun/audit-platform`,
   `@oshun/inbound-integrations`), which finally touch PostgreSQL+pgvector,
   Redis, MinIO/S3, Elasticsearch, Qdrant, Neo4j, and Kafka, with
   OTLP/Jaeger/Prometheus/Grafana observing the whole path.

The same five-hop shape holds for every domain; only the prefix and the adapter
change. That uniformity is the payoff of the single-BFF, single-contracts
design.

## Client Surfaces Feeding the Top Layer

The `CLIENTS` box in the diagram contains four boxes, but the actual surface
inventory is larger and worth naming precisely, because two real surfaces are
easy to miss.

### The named app surfaces (all real, all packaged)

Every one of these is a real workspace package with its own `package.json` and a
substantial `src/`:

| Surface           | Path                           | Package                   |
| ----------------- | ------------------------------ | ------------------------- |
| Customer Web      | `apps/oshun/web/`              | `@oshun/web`              |
| Customer Mobile   | `apps/oshun/mobile/`           | `@oshun/mobile`           |
| Oshun BFF         | `apps/oshun/bff/`              | `@oshun/bff`              |
| Admin Web         | `apps/oshun/admin/`            | `@oshun/admin`            |
| Admin Mobile      | `apps/oshun/admin-mobile/`     | `@oshun/admin-mobile`     |
| Tenant Console    | `apps/oshun/tenant-admin/`     | `@oshun/tenant-admin`     |
| Telegram Bot      | `apps/oshun/telegram-bot/`     | `@oshun/telegram-bot`     |
| Telegram Mini App | `apps/oshun/telegram-miniapp/` | `@oshun/telegram-miniapp` |

### Two surfaces the prose has historically under-described

- **Content service** — `apps/oshun/content-service/` is a real app, package
  `@oshun/content-service-app`. The [Product Surfaces](./product-surfaces.md)
  table has historically omitted this surface; it belongs in the inventory.
- **Legal** — `apps/oshun/legal/` is **not** an app. It is a markdown-only
  surface containing `privacy-policy.md` and `terms-of-service.md` with **no**
  `package.json`. The architecture's references to "the legal surface at
  `apps/oshun/legal/`" are correct, but it is a docs folder, not a buildable
  app, and should be read that way. See
  [Security, Privacy, and Compliance](./security-privacy-compliance.md) for how
  these documents are consumed.

For the web app, the PWA layer is real and test-covered, not aspirational. The
service worker lives at `apps/oshun/web/public/sw.js`, the manifest at
`apps/oshun/web/public/manifest.json` (`name: "OSHUN"`,
`start_url: "/?surface=pwa"`, `display: "standalone"`,
`theme_color: "#f1ebdd"`), and the install/update/caching logic at
`apps/oshun/web/src/lib/pwa-service-worker.ts` with its policy test at
`apps/oshun/web/src/lib/pwa-service-worker.test.ts`.

The mobile app is a real Expo + Expo Router project: `app.json` sets
`expo.scheme: "oshun"`, the iOS `bundleIdentifier` is `com.oshun.mobile`, and
its `associatedDomains` include `applinks:oshun.app` (plus `www.oshun.app`,
`app.oshun.com`, and `activitycontinuation:`/`webcredentials:` entries). Its tab
IA lives under `apps/oshun/mobile/app/(tabs)/` as `index`, `explore`,
`activity`, `library`, and `profile`. Admin Mobile (`@oshun/admin-mobile`) is
likewise a full Expo Router app with an `app/(operator)/` group plus `login.tsx`
and `step-up.tsx`, and a deep `src/` (`auth`, `incidents`, `offline`, `review`,
`urgent-queue`, and more). See [Product Surfaces](./product-surfaces.md) for the
full surface walkthrough.

## The Shared Shell Is Wider Than the Diagram Label

The shell subtitle in the diagram reads
`@oshun/shell-* · navigation · offline · ui · design-tokens`, and the
`@oshun/shell-*` glob is doing more work than the commonly cited composition
list implies. The shell is genuinely composed from these real libraries:

| Shell library                       | Role                                                                                              |
| ----------------------------------- | ------------------------------------------------------------------------------------------------- |
| `@oshun/shell-core`                 | home / explore / activity / library / notifications / profile / settings / assistant entry points |
| `@oshun/shell-assistant`            | collapsible assistant dock + context handoff                                                      |
| `@oshun/shell-routines`             | cross-domain routine and continuation cards                                                       |
| `@oshun/shell-wearable`             | watch / widget / Live-Activity-style surface                                                      |
| `@oshun/shell-desktop`              | desktop (Electron-class) surface — see below                                                      |
| `@oshun/shell-achievements`         | achievements, challenges, social accountability — see below                                       |
| `@oshun/navigation`                 | current-domain persistence, deep links, route analytics, command-surface registration             |
| `@oshun/offline`                    | offline/PWA/mobile sync substrate — see below                                                     |
| `@oshun/design-tokens`, `@oshun/ui` | design system + component library                                                                 |

Three of these are real shell libraries that the older composition prose did not
name, and they materially change the picture:

- **`@oshun/shell-desktop`** is a whole desktop surface class — an
  Electron-class companion. Its `src/` carries `desktop-engine.ts`,
  `window-manager.ts`, `tray-companion.ts`, `protocol-handler.ts`,
  `update-manager.ts`, `widget-engine.ts`, `notification-bridge.ts`, and
  `shortcut-manager.ts`. This is a surface category beyond web and mobile; it is
  real code, and it should be understood as part of the shell composition.
- **`@oshun/shell-achievements`** ships `achievement-engine.ts`,
  `achievement-definitions.ts`, `challenge-templates.ts`, and
  `social-accountability.ts` — the engagement/accountability layer that rides on
  top of the cross-domain shell.
- **`@oshun/offline`** is the real PWA/mobile offline substrate that the shell
  composition list also tends to drop. Its `src/` exports an `OfflineSyncQueue`
  class (`queue.ts`), an `OfflineCache` class (`cache.ts`), and the
  `SyncQueueItem`, `RetryPolicy`, and `ConnectivityState` types (`types.ts`),
  plus `storage.ts`, `retry.ts`, and `connectivity.ts`. It is the substrate
  behind the web PWA's offline shell and the mobile app's sync behavior.

See the
[Shared Consumer Shell section of Product Surfaces](./product-surfaces.md) and
the [Subsystem Glossary](./glossary.md) for the per-library detail.

## The `/domains/*` Namespace Covers All Six Domains

Customer web ships two coordinated namespaces per domain: a presentational
**consumer hub** at `/<domain>` and a **power-user deep-tools** namespace at
`/domains/<domain>/*`. An earlier reading of the docs claimed Nisaba and Metis
ship _no_ parallel `/domains/*` namespace and instead bolt their deep tools onto
the consumer hub. The code disagrees, and the code is authoritative.

Under `apps/oshun/web/src/app/domains/` there are four **static** domain
directories — `arete`, `nyx`, `tara`, `veritas` — plus a `[domainId]` **dynamic
catch-all** and a `layout.tsx`. The catch-all is what extends the namespace to
the remaining two domains:

- `apps/oshun/web/src/app/domains/[domainId]/page.tsx` carries a `DOMAIN_META`
  record with entries for **both `nisaba` and `metis`**, and gates rendering on
  `isWebNavigableDomainId(domainId)` — if a path is not a navigable domain it
  calls `notFound()`.
- `apps/oshun/web/src/navigation/routes.ts:52-53` defines
  `WEB_DOMAIN_IDS = ['tara', 'veritas', 'nyx', 'arete', 'nisaba']` and
  `WEB_NAVIGABLE_DOMAIN_IDS = [...WEB_DOMAIN_IDS, 'metis']`. So `nisaba` is in
  the static set and `metis` is added to the navigable set — both pass the
  `isWebNavigableDomainId` gate.
- The shared `@oshun/navigation` route map at
  `libs/oshun/navigation/src/routes.ts` (lines ~98–130) maps **all six** domains
  to `webPath: '/domains/<domain>'` with `deepLinkBase: 'oshun://<domain>'`,
  including explicit `nisaba` (`/domains/nisaba`, `oshun://nisaba`) and `metis`
  (`/domains/metis`, `oshun://metis`) entries. Its tests assert
  `getDomainRouteDefinition('metis').webPath === '/domains/metis'`.

The practical truth: `/domains/nisaba` and `/domains/metis` **are** reachable
web routes — served by the `[domainId]` catch-all rather than by static
directories, but reachable all the same. The dual-namespace pattern applies to
every domain, not four-plus-exceptions. The two namespaces are intentional
audience splits, not deprecation candidates for each other; see
[Customer-Facing Domains](./customer-domains.md) and the companion
[Product Surfaces](./product-surfaces.md) for the per-domain room and deep-tool
inventory.

> **Why a catch-all instead of two more static dirs?** The static directories
> (`arete`, `nyx`, `tara`, `veritas`) host heavier bespoke client workspaces;
> Nisaba and Metis route through the data-driven `[domainId]` page, which reads
> from `DOMAIN_META` and the navigation route map. That keeps newly navigable
> domains additive — a domain becomes reachable the moment it is added to
> `WEB_NAVIGABLE_DOMAIN_IDS` and `DOMAIN_META`, without scaffolding a new page
> tree. The consumer hubs themselves are also real and rich:
> `apps/oshun/web/src/app/nisaba/` carries subdirs like `compare`, `daily`,
> `graph`, `lexicon`, `manuscript`, `notebook`, `scholar`, and
> `apps/oshun/web/src/app/metis/` carries `assessment`, `byom`, `courses`,
> `ingest`, `lesson`, `session`, `tutor`, `upload`.

### One honest caveat on the live shell count

The web experimentation layer carries a `shellDomainCount` (and a
`shellNavigationDomainCount`) in its `FlagEvaluationContext`
(`apps/oshun/web/src/experimentation/feature-flags.ts`). Per the 2026-06-23
triage, the live shell's `shellDomainCount` can drop from five to four
post-hydration when Nisaba evaluates as _disconnected_ and Metis as _planned_.
So while the `/domains/*` **routes** for all six domains exist and resolve, the
**runtime-active** domain count surfaced to the shell can be smaller than the
route map suggests at a given moment. The routing capability is real; the live
activation is flag- and connection-gated. This is the architecture being honest
about the difference between "reachable route" and "active domain," not a
contradiction.

## Reading Order from Here

This page is deliberately the shallow map. The next layer of detail lives in:

- **Surfaces** — every client and operator surface in full:
  [Product Surfaces](./product-surfaces.md).
- **Domains** — the six customer-facing domains, their adapters, contracts, and
  registry: [Customer-Facing Domains](./customer-domains.md).
- **Substrates** — the platform runtimes behind the stable adapters: the
  [Sophia](./substrate-sophia.md), [Iris](./substrate-iris.md),
  [Psyche](./substrate-psyche.md), [Lilith](./substrate-lilith.md),
  [Isis](./substrate-isis.md), and [Aje](./substrate-aje.md) pages.
- **Foundations** — the shared infrastructure libraries:
  [Foundations](./foundations.md).
- **Cross-cutting flows** —
  [Communication Patterns](./communication-patterns.md) and
  [Data Architecture and Tenancy](./data-architecture-tenancy.md).

## Related

- [Product Surfaces](./product-surfaces.md)
- [Customer-Facing Domains](./customer-domains.md)
- [Foundations](./foundations.md)
- [Communication Patterns](./communication-patterns.md)
- [Data Architecture and Tenancy](./data-architecture-tenancy.md)
- [Subsystem Glossary](./glossary.md)
- [Product Surfaces (features companion)](./product-surfaces.md)
- [Hub: V1 Architecture](../ARCHITECTURE.md)
