# Tara — Technical Specifications

> Meditation and mindfulness platform. Named after the Buddhist bodhisattva of
> compassion and liberation.
>
> This specification documents what is **implemented** in the `tara` codebase:
> the `@tara/api` Hono backend, the `@tara/web` Next.js PWA, the `@tara/mobile`
> Expo app, the eight `libs/tara/*` libraries, and the eight `libs/meditation/*`
> platform-agnostic engine libraries that Tara consumes. Every schema, enum,
> endpoint, and field below is traceable to source.

---

This document is the engineering reference for the Tara domain. It is organized
to move from the macro (what exists, what it runs on) to the micro (exact table
fields, enum values, Zod schema constraints). Engineers implementing a feature
should start with §5 (API Surface) for endpoint contracts, then §4 or §7 for the
database schemas behind those endpoints, and §9–§15 for the library-level type
definitions consumed by clients.

A critical structural fact for anyone new to this domain: **two independent
database layers co-exist**. The `@tara/api` Hono server defines and migrates its
own Drizzle schema (§4). A separate `@tara/database` Prisma package (§7) serves
library tooling and provides a richer domain model. They are not the same schema
— §4 and §7 document each independently. Never assume that a table present in
one also exists in the other with the same fields.

---

## 1. Domain Overview

The table below provides a quick reference for the domain's key dimensions.

| Property             | Value                                                                                         |
| -------------------- | --------------------------------------------------------------------------------------------- |
| Domain name          | `tara`                                                                                        |
| Scope                | App-store-safe meditation and mindfulness platform                                            |
| Applications         | 3 — `@tara/api`, `@tara/web`, `@tara/mobile` (`apps/tara/`)                                   |
| Domain libraries     | 8 (`libs/tara/`)                                                                              |
| Meditation engine    | 8 platform-agnostic libraries (`libs/meditation/`)                                            |
| Content / docs trees | `apps/tara/content`, `apps/tara/docs`, top-level `tara/content`, `tara/assets`, `tara/config` |
| API backend          | Hono 4 + Drizzle ORM, OpenAPI 3.1, port 3001                                                  |
| API persistence      | PostgreSQL via Drizzle (`@tara/api` owns its own schema in `src/db/schema.ts`)                |
| Separate ORM library | `@tara/database` ships an independent Prisma schema (`TARA_DATABASE_URL`)                     |
| Cache                | Redis (session cache, used by API)                                                            |
| Object storage       | AWS S3 (`@aws-sdk/client-s3`) for avatar uploads; CloudFront signed URLs for premium content  |

> **Two persistence layers exist.** `@tara/api` defines and migrates its own
> Drizzle schema (`apps/tara/api/src/db/schema.ts`, migrations in
> `apps/tara/api/drizzle/`). `@tara/database` is a **separate** Prisma package
> (`libs/tara/database/prisma/schema.prisma`) with its own table and enum set.
> They are not the same schema; §4 and §7 document each independently.

---

## 2. Service Inventory

### Applications (`apps/tara/`)

The three Tara applications are the deployed, runnable products. Each has its
own build target and runtime.

| Project        | Path               | Type                                                                   |
| -------------- | ------------------ | ---------------------------------------------------------------------- |
| `@tara/api`    | `apps/tara/api`    | Backend REST API — Hono 4, `@hono/zod-openapi`, OpenAPI 3.1, port 3001 |
| `@tara/web`    | `apps/tara/web`    | Next.js 14 PWA (App Router, `[locale]` i18n routing)                   |
| `@tara/mobile` | `apps/tara/mobile` | Expo 51 / React Native (Expo Router 3.5)                               |

`apps/tara/content` (audio/course/teacher/sound asset trees + JSON schemas) and
`apps/tara/docs` (help-center, legal, marketing, app-store checklists) are asset
and documentation directories, not build targets.

### Domain Libraries (`libs/tara/`)

These eight libraries are the reusable, individually-buildable packages that the
Tara applications depend on. Each has its own `package.json` and test suite.

| Project            | Path                   | Responsibility                                                             |
| ------------------ | ---------------------- | -------------------------------------------------------------------------- |
| `@tara/analytics`  | `libs/tara/analytics`  | Typed event tracking, pluggable providers, A/B experiments, feature flags  |
| `@tara/api-client` | `libs/tara/api-client` | Typed HTTP client + generated OpenAPI types for the Tara API               |
| `@tara/config`     | `libs/tara/config`     | Runtime config, environment parsing, ten typed feature flags               |
| `@tara/content`    | `libs/tara/content`    | Content type definitions, API client, cache, filters, search, React hooks  |
| `@tara/database`   | `libs/tara/database`   | Prisma schema, generated client, seed scripts (independent of `@tara/api`) |
| `@tara/features`   | `libs/tara/features`   | Feature-state selectors: progress/streak summaries, rituals, taxonomies    |
| `@tara/monitoring` | `libs/tara/monitoring` | Error tracking, breadcrumbs, performance monitoring, pluggable providers   |
| `@tara/ui`         | `libs/tara/ui`         | Cross-platform UI component library, design tokens, theme provider         |

### Meditation Engine Libraries (`libs/meditation/`, consumed by Tara)

These eight libraries are platform-agnostic — they know nothing about Tara's
API, database, or billing. They own all audio, breathing, timer, session, and
offline logic. Tara declares direct dependencies on only
`@oshun/meditation-player` and `@oshun/meditation-breathing`; the remaining
engine libraries are reached transitively through those two.

| Project                       | Path                        | Responsibility                                                |
| ----------------------------- | --------------------------- | ------------------------------------------------------------- |
| `@oshun/meditation-core`      | `libs/meditation/core`      | Shared primitives, content models, date/duration/format utils |
| `@oshun/meditation-player`    | `libs/meditation/player`    | Audio playback, mixer, queue, streaming, background, cache    |
| `@oshun/meditation-timer`     | `libs/meditation/timer`     | Countdown timer, bells, intervals, ambient sounds, background |
| `@oshun/meditation-session`   | `libs/meditation/session`   | Session lifecycle, persistence, scheduling, analytics         |
| `@oshun/meditation-progress`  | `libs/meditation/progress`  | Streaks, statistics, achievements, milestones, export, sync   |
| `@oshun/meditation-breathing` | `libs/meditation/breathing` | Breathing exercise engine, patterns, guidance, haptics        |
| `@oshun/meditation-offline`   | `libs/meditation/offline`   | Download queue, storage, versioning, suggestions              |
| `@oshun/meditation-analytics` | `libs/meditation/analytics` | Privacy-conscious session analytics primitives                |

`@tara/web` directly depends on `@oshun/meditation-breathing` and
`@oshun/meditation-player`. `@tara/mobile` directly depends on the same two. The
remaining meditation libraries are part of the engine but are not declared as
direct dependencies of the Tara apps' `package.json`.

---

## 3. Technology Stack

The following tables list the exact package versions and technologies in use
across each application. Version constraints are taken directly from the
`package.json` files in the repository.

### Backend API (`@tara/api`)

| Component     | Technology / package                                              |
| ------------- | ----------------------------------------------------------------- |
| Framework     | Hono `^4.0.0` via `OpenAPIHono` (`@hono/zod-openapi ^0.14.0`)     |
| Server        | `@hono/node-server`                                               |
| API docs      | OpenAPI 3.1 doc + `@hono/swagger-ui` at `/api/docs`               |
| Language      | TypeScript                                                        |
| ORM           | Drizzle ORM `^0.38.0`, Drizzle Kit `^0.30.0`, `pg`                |
| Auth          | `@oshun/auth`, `@oshun/auth-primitives`; JWT issued in-domain     |
| Cache         | `@oshun/cache` (Redis)                                            |
| Validation    | Zod (`@hono/zod-openapi` re-export)                               |
| Storage       | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner`             |
| Billing       | `stripe ^17.7.0`                                                  |
| ID generation | `nanoid`                                                          |
| Monitoring    | `@tara/monitoring`                                                |
| Testing       | Vitest (unit + integration configs); k6 load tests in `test/load` |

### Web Application (`@tara/web`)

| Component     | Technology                                                        |
| ------------- | ----------------------------------------------------------------- |
| Framework     | Next.js `^14` (App Router, `[locale]` segment, standalone build)  |
| Language      | TypeScript                                                        |
| UI            | Radix UI primitives, Headless UI, Tailwind CSS, `lucide-react`    |
| State         | Zustand `^4.4.6`                                                  |
| Data fetching | TanStack Query `^5.89`                                            |
| i18n          | `next-intl ^3.19` (+ `@formatjs/intl-localematcher`, negotiator)  |
| Animation     | Framer Motion `^10.16`                                            |
| Meditation    | `@oshun/meditation-player`, `@oshun/meditation-breathing`         |
| Tara libs     | `@tara/ui`, `@tara/content`, `@tara/features`                     |
| Testing       | Vitest, Playwright, `@axe-core/playwright`, Storybook, Lighthouse |

### Mobile Application (`@tara/mobile`)

| Component     | Technology                                                       |
| ------------- | ---------------------------------------------------------------- |
| Framework     | Expo `~51.0` / React Native `0.74.5`                             |
| Routing       | Expo Router `~3.5` + React Navigation                            |
| Audio         | Expo AV `~14.0`                                                  |
| State         | Zustand                                                          |
| Storage       | `@react-native-async-storage/async-storage`, `expo-secure-store` |
| Subscriptions | `react-native-purchases` (RevenueCat) `^8.0`                     |
| Notifications | `expo-notifications`                                             |
| Auth          | `expo-apple-authentication`                                      |
| Meditation    | `@oshun/meditation-player`, `@oshun/meditation-breathing`        |
| Tara libs     | `@tara/features`                                                 |
| Testing       | Jest (`jest-expo`); Maestro E2E flows in `e2e/flows/`            |

---

## 4. Domain Objects — `@tara/api` (Drizzle Schema)

This section documents the tables in `apps/tara/api/src/db/schema.ts`. Every
field, type, and constraint below is taken directly from that file. The API
runtime exclusively uses this schema at runtime — it does **not** read from the
Prisma schema in `@tara/database`.

### 4.1 `users`

The central user identity record. `passwordHash` is nullable to accommodate
OAuth-only accounts. The `lockedUntil` / `failedLoginAttempts` pair implements
account lockout after repeated failed logins. `deletedAt` is a soft-delete
marker; hard deletion is performed separately.

| Field                 | Type                  | Notes                          |
| --------------------- | --------------------- | ------------------------------ |
| `id`                  | uuid, PK              | `defaultRandom()`              |
| `email`               | varchar(255), notNull | unique index                   |
| `username`            | varchar(30)           | unique index                   |
| `passwordHash`        | varchar(255)          | nullable (OAuth-only accounts) |
| `displayName`         | varchar(100)          |                                |
| `avatarUrl`           | varchar(500)          |                                |
| `role`                | `user_role` enum      | default `user`                 |
| `authProvider`        | `auth_provider` enum  | default `email`                |
| `authProviderId`      | varchar(255)          | indexed with `authProvider`    |
| `emailVerified`       | boolean               | default `false`                |
| `emailVerifiedAt`     | timestamptz           |                                |
| `lastLoginAt`         | timestamptz           |                                |
| `lockedUntil`         | timestamptz           | account lockout                |
| `failedLoginAttempts` | integer               | default `0`                    |
| `createdAt`           | timestamptz           | default now                    |
| `updatedAt`           | timestamptz           | default now                    |
| `deletedAt`           | timestamptz           | soft-delete marker             |

### 4.2 `user_preferences`

Per-user app preferences stored as a single row per user. `goals` is a JSONB
array of goal strings gathered during onboarding. Times are stored as `HH:MM`
strings in varchar(5) to avoid timezone ambiguity in reminder scheduling.

`id` (uuid PK), `userId` (FK → users, cascade), `goals` (jsonb `string[]`),
`experienceLevel` (`experience_level` enum, default `beginner`),
`preferredDuration` (integer minutes, default `10`), `preferredTime`
(varchar(5), HH:MM), `notificationsEnabled` (bool, default true),
`dailyReminderEnabled` (bool, default false), `dailyReminderTime` (varchar(5)),
`soundEnabled` (bool, default true), `hapticEnabled` (bool, default true),
`darkModeEnabled` (bool, nullable), `timezone` (varchar(50), default `UTC`),
`createdAt`, `updatedAt`.

### 4.3 `subscriptions`

One subscription row per user (unique index on `userId`). The `tier` and
`status` enums determine access to premium content. Stripe fields are nullable
because mobile subscriptions go through RevenueCat/App Store/Google Play instead
of Stripe.

`id` (uuid PK), `userId` (FK, cascade, unique index), `tier`
(`subscription_tier` enum, default `free`), `status` (`subscription_status`
enum, default `active`), `stripeCustomerId`, `stripeSubscriptionId`,
`stripePriceId` (varchar(255)), `currentPeriodStart`, `currentPeriodEnd`,
`cancelAtPeriodEnd` (bool, default false), `canceledAt`, `trialStart`,
`trialEnd`, `createdAt`, `updatedAt`.

### 4.4 `meditations`

The primary content table. `audioUrl` is a relative path or CDN key — the API
generates a signed CloudFront URL at request time rather than storing a signed
URL directly. `instructor` is denormalized from the teacher record for
performance in list queries. `deletedAt` enables soft deletion without breaking
foreign key references in `sessions`.

| Field                                   | Type                           | Notes                           |
| --------------------------------------- | ------------------------------ | ------------------------------- |
| `id`                                    | uuid, PK                       |                                 |
| `title`                                 | varchar(200), notNull          |                                 |
| `description`                           | text                           |                                 |
| `category`                              | `meditation_category` enum     | notNull, indexed                |
| `contentType`                           | `content_type` enum            | notNull, default `guided`       |
| `durationSeconds`                       | integer, notNull               |                                 |
| `audioUrl`                              | varchar(500), notNull          |                                 |
| `imageUrl`                              | varchar(500)                   |                                 |
| `instructor`                            | varchar(100)                   | denormalized teacher name       |
| `instructorBio`                         | text                           |                                 |
| `instructorImageUrl`                    | varchar(500)                   |                                 |
| `experienceLevel`                       | `experience_level` enum        | default `beginner`              |
| `isPremium`                             | boolean                        | notNull, default false, indexed |
| `isFeatured`                            | boolean                        | notNull, default false, indexed |
| `tags`                                  | jsonb `string[]`               | default `[]`                    |
| `metadata`                              | jsonb `Record<string,unknown>` | default `{}`                    |
| `playCount`                             | integer                        | notNull, default 0              |
| `averageRating`                         | decimal(3,2)                   | nullable                        |
| `ratingCount`                           | integer                        | notNull, default 0              |
| `publishedAt`                           | timestamptz                    | indexed; null = unpublished     |
| `createdAt` / `updatedAt` / `deletedAt` | timestamptz                    | `deletedAt` is soft-delete      |

### 4.5 `courses`

A course groups ordered meditations into a multi-day program.
`totalDurationSeconds` and `sessionCount` are maintained as aggregated counters
to avoid expensive joins on every course list request.

`id`, `title` (varchar(200) notNull), `description` (text), `shortDescription`
(varchar(500)), `imageUrl`, `instructor` (varchar(100)), `category`
(`meditation_category` enum, notNull), `experienceLevel` (`experience_level`
enum, default `beginner`), `totalDurationSeconds` (integer, default 0),
`sessionCount` (integer, default 0), `isPremium` (bool, default false),
`isFeatured` (bool, default false), `sortOrder` (integer, default 0), `tags`
(jsonb `string[]`), `metadata` (jsonb), `publishedAt`, `createdAt`, `updatedAt`,
`deletedAt`.

### 4.6 `course_meditations` (junction)

The many-to-many join between courses and meditations. `dayNumber` assigns a
meditation to a specific day in the course; `sortOrder` controls ordering within
a day if multiple meditations are scheduled on the same day.

Composite PK `(courseId, meditationId)`. `courseId` (FK, cascade),
`meditationId` (FK, cascade), `dayNumber` (integer, notNull), `sortOrder`
(integer, default 0), `isOptional` (bool, default false).

### 4.7 `sessions` (meditation listening sessions)

Records every time a user starts a meditation. `completedSeconds` vs
`durationSeconds` tracks partial completions; `completionPercentage` is the
derived ratio. `moodBefore` / `moodAfter` enable before-and-after mood tracking.

| Field                  | Type                  | Notes                                      |
| ---------------------- | --------------------- | ------------------------------------------ |
| `id`                   | uuid, PK              |                                            |
| `userId`               | uuid, FK (cascade)    | indexed                                    |
| `meditationId`         | uuid, FK (cascade)    | indexed                                    |
| `courseId`             | uuid, FK (set null)   | nullable — set when session is in a course |
| `durationSeconds`      | integer, notNull      | scheduled duration                         |
| `completedSeconds`     | integer, notNull      | actual listened seconds                    |
| `completionPercentage` | decimal(5,2), notNull | 0–100                                      |
| `isCompleted`          | boolean               | notNull, default false                     |
| `rating`               | integer               | nullable, 1–5                              |
| `feedback`             | text                  | free-form notes                            |
| `mood`                 | varchar(50)           |                                            |
| `moodBefore`           | varchar(50)           |                                            |
| `moodAfter`            | varchar(50)           |                                            |
| `startedAt`            | timestamptz, notNull  | indexed                                    |
| `completedAt`          | timestamptz           |                                            |
| `createdAt`            | timestamptz           | default now                                |

### 4.8 `user_progress` (aggregated stats)

A single denormalized row per user holding cumulative lifetime statistics. This
avoids aggregating across the entire `sessions` table on every progress page
load. `weeklyGoalMinutes` and `weeklyMinutesCompleted` are reset at the start of
each week via `weekStartDate`.

`id`, `userId` (FK, cascade), `totalMinutes`, `totalSessions`, `currentStreak`,
`longestStreak` (all integer, default 0), `streakStartDate`,
`lastMeditationDate` (timestamptz), `lastMeditationId` (FK → meditations),
`completedCourses` / `completedMeditations` / `achievements` (jsonb `string[]`),
`weeklyGoalMinutes` (integer, default `60`), `weeklyMinutesCompleted` (integer,
default 0), `weekStartDate` (timestamptz), `createdAt`, `updatedAt`.

### 4.9 `course_progress`

Tracks a single user's progress through a single course. `completedDays` is a
JSONB array of day numbers already completed, allowing sparse completion
(skipped days). The unique index on `(userId, courseId)` enforces one progress
row per user-course pair.

`id`, `userId` (FK, cascade), `courseId` (FK, cascade), `currentDay` (integer,
default 1), `completedDays` (jsonb `number[]`), `isCompleted` (bool, default
false), `completedAt`, `startedAt` (default now), `lastActivityAt` (default
now). Unique index on `(userId, courseId)`.

### 4.10 `favorites`

A simple join table recording user-meditation favorites. The composite PK
enforces uniqueness — favoriting the same meditation twice is a no-op at the
database level.

Composite PK `(userId, meditationId)`. `userId` (FK, cascade), `meditationId`
(FK, cascade), `createdAt`.

### 4.11 `downloads`

Records user-meditation download relationships. `fileSize` (integer bytes) is
stored so the UI can report storage usage. `expiresAt` enables time-limited
download licenses for DRM-light scenarios.

Composite PK `(userId, meditationId)`. `userId` (FK, cascade), `meditationId`
(FK, cascade), `downloadedAt` (default now), `expiresAt` (timestamptz),
`fileSize` (integer bytes).

### 4.12 `collections`

Curated groups of meditations (e.g., "Morning Routines," "Sleep Toolkit").
`coverColor` (varchar(7), hex) enables per-collection branded colors in the UI.
`isDaily` marks the collection surfaced as the "daily meditation."

`id`, `title` (varchar(200) notNull), `description` (text), `shortDescription`
(varchar(500)), `imageUrl`, `coverColor` (varchar(7), hex), `collectionType`
(`collection_type` enum, default `curated`), `category` (`meditation_category`
enum, nullable), `isPremium` / `isFeatured` / `isDaily` (bool, default false),
`sortOrder` (integer, default 0), `tags` (jsonb), `metadata` (jsonb),
`publishedAt`, `createdAt`, `updatedAt`, `deletedAt`.

### 4.13 `collection_meditations` (junction)

The many-to-many join between collections and meditations. `addedAt` records
when a meditation was added to the collection for changelog and freshness
tracking.

Composite PK `(collectionId, meditationId)`. `sortOrder` (integer, default 0),
`addedAt` (timestamptz, default now).

### 4.14 `user_devices` (push notifications)

Registers devices for push notification delivery. Each device has a platform
(`ios`/`android`/`web`) and a `pushToken` from the platform's push service
(APNs, FCM, or Web Push). The unique index on `(userId, deviceId)` prevents
duplicate registrations for the same physical device.

`id`, `userId` (FK, cascade), `deviceId` (varchar(255), notNull), `deviceName`
(varchar(100)), `platform` (`device_platform` enum, notNull), `osVersion`,
`appVersion` (varchar(50)), `pushToken` (varchar(500)), `pushProvider`
(`push_provider` enum), `pushEnabled` (bool, default true), `lastActiveAt`
(default now), `createdAt`, `updatedAt`. Unique index on `(userId, deviceId)`.

### 4.15 Auth Tables

Four tables collectively implement the full auth lifecycle: token rotation,
password reset, email verification, and login audit.

- **`refresh_tokens`** — `id`, `userId` (FK, cascade), `token` (varchar(500),
  notNull, unique), `family` (varchar(100), notNull — token-rotation family),
  `expiresAt`, `revokedAt`, `userAgent`, `ipAddress` (varchar(45)), `createdAt`.
- **`password_resets`** — `id`, `userId` (FK, cascade), `tokenHash`
  (varchar(255)), `expiresAt`, `usedAt`, `createdAt`.
- **`email_verifications`** — same shape as `password_resets`.
- **`login_attempts`** — `id`, `identifier` (varchar(255) — email or username),
  `ipAddress`, `userAgent`, `success` (bool, notNull), `failureReason`
  (varchar(100)), `timestamp` (default now).

### 4.16 Analytics Tables

Four tables back the analytics, experimentation, and feature-flag systems. The
analytics event table is append-only and heavily indexed for dashboard queries.

- **`analytics_events`** — `id`, `eventName` (varchar(100), notNull),
  `properties` (jsonb), `userId` (uuid), `anonymousId`, `sessionId`, `platform`,
  `deviceType`, `appVersion`, `osVersion`, `locale`, `timezone`,
  `eventTimestamp` (notNull), `receivedAt` (default now). Indexed on name, user,
  anonymous, session, both timestamps, and platform.
- **`experiments`** — `id` (varchar(100) PK), `name` (varchar(200)),
  `description`, `status` (`experiment_status` enum, default `draft`),
  `variants` (jsonb array of `{id, name, weight, config?}`), `targetAudience`
  (jsonb
  `{subscriptionTiers?, platforms?, minAppVersion?, maxAppVersion?, countries?, languages?, customSegments?}`),
  `trafficPercentage` (integer, default 100), `startDate`, `endDate`,
  `createdAt`, `updatedAt`.
- **`experiment_assignments`** — `id`, `experimentId` (FK → experiments,
  cascade), `variantId` (varchar(100)), `userId` (varchar(100)), `source`
  (varchar(50), default `deterministic`), `assignedAt`. Unique on
  `(experimentId, userId)`.
- **`feature_flags`** — `id` (varchar(100) PK), `name` (varchar(200)),
  `description`, `enabled` (bool, default false), `defaultValue` (jsonb
  `boolean | string | number | object`), `rules` (jsonb array of
  `{conditions[], percentage?, value}`), `createdAt`, `updatedAt`.

### 4.17 Drizzle Enums

These enums are defined in `apps/tara/api/src/db/schema.ts` and are the
canonical vocabulary for the API runtime's own types. They are distinct from the
Prisma enums in §7.2.

| Enum                  | Values                                                                                                                                                                                         |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscription_tier`   | `free`, `premium`, `lifetime`                                                                                                                                                                  |
| `subscription_status` | `active`, `canceled`, `expired`, `past_due`, `trialing`                                                                                                                                        |
| `meditation_category` | `sleep`, `stress`, `focus`, `anxiety`, `morning`, `evening`, `breathwork`, `body_scan`, `visualization`, `gratitude`, `self_compassion`, `relationships`, `work`, `creativity`, `general` (15) |
| `experience_level`    | `beginner`, `intermediate`, `advanced`                                                                                                                                                         |
| `content_type`        | `guided`, `unguided`, `music`, `soundscape`, `story`                                                                                                                                           |
| `auth_provider`       | `email`, `google`, `apple`, `facebook`                                                                                                                                                         |
| `user_role`           | `user`, `premium`, `admin`                                                                                                                                                                     |
| `device_platform`     | `ios`, `android`, `web`                                                                                                                                                                        |
| `push_provider`       | `apns`, `fcm`, `web_push`                                                                                                                                                                      |
| `collection_type`     | `featured`, `category`, `mood`, `time_of_day`, `seasonal`, `event`, `curated`                                                                                                                  |
| `experiment_status`   | `draft`, `running`, `paused`, `completed`                                                                                                                                                      |

---

## 5. API Surface — `@tara/api`

The Hono `OpenAPIHono` app (`apps/tara/api/src/app.ts`) listens on **port 3001**
(`PORT` env, host `0.0.0.0`). The OpenAPI document is served at
`/api/v1/openapi.json` (OpenAPI 3.1) and Swagger UI at `/api/docs`.

### 5.1 Global Middleware

Middleware is registered in a fixed order in `app.ts`. The order matters: for
example, `requestIdMiddleware` runs before `errorHandler` so every error
response carries a request ID. `authMiddleware()` is applied per route group
rather than globally, so public endpoints never incur JWT validation overhead.

The seven middleware steps in registration order:

1. `secureHeaders()` — security headers on all routes
2. `cors()` — origin from `CORS_ORIGIN` (default `*`), methods GET/POST/PUT/
   PATCH/DELETE/OPTIONS, credentials enabled
3. `logger()`, `timing()`, `prettyJSON()` (dev only)
4. `requestIdMiddleware` — assigns/propagates `X-Request-ID`
5. `errorHandler` — RFC 7807 Problem Details error responses
6. `rateLimitMiddleware` on `/api/*` — anonymous tier (see §8.2)
7. `authMiddleware()` applied per route group (see §5.3)

### 5.2 Operational Routes

These routes exist outside the `/api/v1` prefix and serve infrastructure
concerns — health checks, readiness probes, and API introspection.

| Method | Path                   | Purpose                                           |
| ------ | ---------------------- | ------------------------------------------------- |
| GET    | `/`                    | API name, version, endpoint index                 |
| GET    | `/health`              | DB + Redis health checks, uptime, environment     |
| GET    | `/ready`               | Readiness probe; 200 if all checks pass, else 503 |
| GET    | `/api/v1`              | API version + endpoint list                       |
| GET    | `/api/v1/openapi.json` | OpenAPI 3.1 document                              |
| GET    | `/api/docs`            | Swagger UI                                        |

### 5.3 Route Groups and Auth

All API route groups are mounted under `/api/v1`. Auth is enforced by
`authMiddleware()` on protected prefixes. Clients send a `Bearer` JWT in the
`Authorization` header. Note that some route groups are mixed — for example,
`/courses` allows public listing but requires auth for progress sub-routes, and
`/analytics` allows public event ingestion but requires auth for dashboard and
experiment endpoints.

| Prefix                    | Auth                                                            |
| ------------------------- | --------------------------------------------------------------- |
| `/api/v1/auth`            | Public (login/register issue tokens)                            |
| `/api/v1/users`           | Authenticated (`/api/v1/users/*`)                               |
| `/api/v1/meditations`     | Public listing                                                  |
| `/api/v1/courses`         | Public listing; progress sub-routes require auth                |
| `/api/v1/teachers`        | Public                                                          |
| `/api/v1/collections`     | Public                                                          |
| `/api/v1/search`          | Public                                                          |
| `/api/v1/sessions`        | Authenticated                                                   |
| `/api/v1/progress`        | Authenticated                                                   |
| `/api/v1/achievements`    | Authenticated                                                   |
| `/api/v1/favorites`       | Authenticated                                                   |
| `/api/v1/history`         | Authenticated                                                   |
| `/api/v1/downloads`       | Authenticated                                                   |
| `/api/v1/subscription`    | Authenticated except `POST /webhook` (public for Stripe)        |
| `/api/v1/notifications`   | Authenticated                                                   |
| `/api/v1/analytics`       | `/events` public; experiments/flags/user/dashboard require auth |
| `/api/v1/recommendations` | Public                                                          |

### 5.4 Auth Endpoints (`/api/v1/auth`)

These endpoints handle user registration, login, token lifecycle, email
verification, password reset, and OAuth. The Google and Apple OAuth callbacks
follow the standard OAuth 2.0 authorization code flow.

| Method | Path                   | Purpose                             |
| ------ | ---------------------- | ----------------------------------- |
| POST   | `/register`            | Register a new user                 |
| POST   | `/login`               | Login with email + password         |
| POST   | `/refresh`             | Exchange refresh token for new pair |
| POST   | `/logout`              | Logout (one device or all)          |
| POST   | `/forgot-password`     | Request a password-reset email      |
| POST   | `/reset-password`      | Reset password with token           |
| POST   | `/verify-email`        | Verify email address with token     |
| POST   | `/resend-verification` | Resend email verification           |
| GET    | `/google`              | Initialize Google OAuth             |
| GET    | `/google/callback`     | Handle Google OAuth callback        |
| GET    | `/apple`               | Initialize Apple OAuth              |
| POST   | `/apple/callback`      | Handle Apple OAuth callback         |

### 5.5 Users Endpoints (`/api/v1/users`, all bearer-auth)

User profile and device management. Avatar upload goes to S3 via the API; the
API returns a URL pointing to the uploaded object. Device registration is used
for push notification token management.

| Method | Path                  | Purpose                   |
| ------ | --------------------- | ------------------------- |
| GET    | `/me`                 | Get current user profile  |
| PATCH  | `/me`                 | Update profile            |
| POST   | `/me/change-password` | Change password           |
| DELETE | `/me`                 | Delete account            |
| POST   | `/me/avatar`          | Upload avatar (S3-backed) |
| DELETE | `/me/avatar`          | Remove avatar             |
| GET    | `/me/settings`        | Get user settings         |
| PATCH  | `/me/settings`        | Update user settings      |
| GET    | `/me/devices`         | List registered devices   |
| POST   | `/me/devices`         | Register a device         |
| PATCH  | `/me/devices/:id`     | Update a device           |
| DELETE | `/me/devices/:id`     | Delete a device           |

### 5.6 Content Endpoints

Content endpoints are public — unauthenticated users can browse and search the
catalog. Premium content is visible in listings but returns 403 for audio URLs
when requested without a valid premium subscription.

**Meditations** (`/api/v1/meditations`):

| Method | Path          | Purpose                                          |
| ------ | ------------- | ------------------------------------------------ |
| GET    | `/`           | List meditations (paginated, filterable, sorted) |
| GET    | `/featured`   | Featured meditations                             |
| GET    | `/categories` | List meditation categories                       |
| GET    | `/:id`        | Get meditation by ID (detail incl. `audioUrl`)   |
| GET    | `/{id}/audio` | Get audio URLs for a meditation                  |

**Courses** (`/api/v1/courses`):

| Method | Path                              | Purpose                      |
| ------ | --------------------------------- | ---------------------------- |
| GET    | `/`                               | List courses                 |
| GET    | `/:id`                            | Get course (with lessons)    |
| GET    | `/:id/lessons`                    | List course lessons          |
| GET    | `/:id/lessons/:lessonId`          | Get a specific lesson        |
| GET    | `/:id/progress`                   | Get course progress _(auth)_ |
| POST   | `/:id/start`                      | Start a course _(auth)_      |
| POST   | `/:id/lessons/:lessonId/complete` | Complete a lesson _(auth)_   |

**Teachers** (`/api/v1/teachers`):

| Method | Path               | Purpose               |
| ------ | ------------------ | --------------------- |
| GET    | `/`                | List teachers         |
| GET    | `/:id`             | Get teacher by ID     |
| GET    | `/:id/meditations` | Teacher's meditations |
| GET    | `/:id/courses`     | Teacher's courses     |

**Collections** (`/api/v1/collections`): `GET /` (list), `GET /:id` (by ID),
`GET /daily` (daily meditation).

**Search** (`/api/v1/search`): `GET /` — full-text search across meditations,
courses, and teachers with a `type` filter (`meditation` | `course` | `teacher`
| `all`).

### 5.7 Activity Endpoints

Activity endpoints are all authenticated. Sessions are the core usage record;
they power progress, streaks, and achievements.

**Sessions** (`/api/v1/sessions`, auth):

| Method | Path    | Purpose                                   |
| ------ | ------- | ----------------------------------------- |
| POST   | `/`     | Start a meditation session                |
| GET    | `/`     | List sessions (paginated, filterable)     |
| GET    | `/{id}` | Get session details                       |
| PATCH  | `/{id}` | Update session (progress, status, rating) |

**Progress** (`/api/v1/progress`, auth):

| Method | Path          | Purpose                                     |
| ------ | ------------- | ------------------------------------------- |
| GET    | `/streak`     | Practice-rhythm (streak) information        |
| GET    | `/statistics` | Aggregate meditation statistics             |
| GET    | `/history`    | Meditation history (grouped by granularity) |

**Achievements** (`/api/v1/achievements`, auth): `GET /` (list achievements with
unlock state), `GET /:id` (achievement details).

**Favorites** (`/api/v1/favorites`, auth): `GET /` (list), `GET /:meditationId`
(check), `POST /:meditationId` (add), `DELETE /:meditationId` (remove).

**History** (`/api/v1/history`, auth): `GET /` (list), `DELETE /` (clear all),
`DELETE /:id` (delete one entry).

**Downloads** (`/api/v1/downloads`, auth): `GET /` (list), `GET /:meditationId`
(check status), `POST /:meditationId` (register download),
`DELETE /:meditationId` (remove record).

### 5.8 Subscription Endpoints (`/api/v1/subscription`)

The Stripe webhook endpoint (`POST /webhook`) must remain **public** because
Stripe delivers events without a bearer token — its security comes from the
`Stripe-Signature` header validated against `STRIPE_WEBHOOK_SECRET`.

| Method | Path              | Purpose                             | Auth   |
| ------ | ----------------- | ----------------------------------- | ------ |
| GET    | `/`               | Get current subscription            | yes    |
| POST   | `/checkout`       | Create a Stripe checkout session    | yes    |
| POST   | `/cancel`         | Cancel subscription                 | yes    |
| POST   | `/restore`        | Restore a canceled subscription     | yes    |
| POST   | `/portal`         | Get Stripe customer-portal URL      | yes    |
| POST   | `/webhook`        | Stripe webhook handler              | public |
| POST   | `/ios/verify`     | Verify iOS App Store receipt        | yes    |
| POST   | `/android/verify` | Verify Android Google Play purchase | yes    |

### 5.9 Notifications Endpoints (`/api/v1/notifications`, auth)

Push notification preference management and device token administration.

`GET /preferences`, `PATCH /preferences`, `GET /devices`, `POST /devices`,
`DELETE /devices/:deviceId`.

### 5.10 Analytics Endpoints (`/api/v1/analytics`)

The `/events` ingestion endpoint is intentionally public so clients can record
events before a user has logged in (anonymous sessions use `anonymousId`). All
other analytics endpoints require authentication because they return or modify
per-user experiment state.

| Method | Path                      | Purpose                           | Auth   |
| ------ | ------------------------- | --------------------------------- | ------ |
| POST   | `/events`                 | Track a batch of analytics events | public |
| POST   | `/experiments/assignment` | Get experiment variant assignment | yes    |
| POST   | `/flags/evaluate`         | Evaluate a feature flag           | yes    |
| POST   | `/user/experiments`       | Get all experiment assignments    | yes    |

**Dashboard** sub-router (`/api/v1/analytics/dashboard`, auth): `GET /summary`,
`GET /trends`, `GET /realtime`, `GET /funnel`.

### 5.11 Recommendations (`/api/v1/recommendations`)

`GET /api/v1/recommendations/*` returns up to `limit` (1–50, default 12)
published meditations, optionally filtered by `category`. The ranking strategy
is `category_affinity` when a category is given, otherwise
`featured_popularity`; the endpoint falls back to featured + popular content
when no category matches and reports `fallbackUsed: true` in the response.

---

## 6. Request / Response Validation (Zod Schemas)

All Zod schemas live in `apps/tara/api/src/routes/<group>/schemas.ts`, one file
per route group. Every request body and query parameter is validated through
these schemas before the handler runs; validation failures produce RFC 7807
Problem Details error responses rather than 500s.

The standard success envelope is
`{ success, data, meta: { requestId, timestamp } }`. Error responses follow RFC
7807: `{ type, title, status, detail, instance, requestId?, timestamp, code? }`.

### 6.1 Auth Schemas

These schemas define the security constraints on user identity fields. The
password policy (`@tara/api/auth`) enforces the same rules as `passwordSchema`.

- **`emailSchema`** — valid email, max 255 chars.
- **`passwordSchema`** — 8–128 chars; must contain ≥1 uppercase, ≥1 lowercase,
  ≥1 digit, ≥1 special character.
- **`usernameSchema`** — 3–30 chars; `[a-zA-Z0-9_-]` only.
- **`registerRequestSchema`** — `email`, `password`, optional `username`,
  optional `displayName` (1–100), `acceptTerms` (must be `true`).
- **`loginRequestSchema`** — `email`, `password`, optional `deviceInfo`
  (`deviceType` ∈ `mobile|tablet|desktop|tv|unknown`, `os?`, `browser?`).
- **`tokenPairSchema`** — `accessToken`, `refreshToken`, `expiresIn` (seconds),
  `tokenType` literal `Bearer`.
- **`userResponseSchema`** — `id`, `email`, `username?`, `displayName?`,
  `avatarUrl?`, `role` ∈ `user|premium|admin`, `emailVerified`, `createdAt`.
- OAuth: `oauthProviderSchema` ∈ `google|apple|facebook`; `oauthStateSchema`,
  `oauthCallbackQuerySchema`, `appleCallbackRequestSchema`.

### 6.2 Session Schemas

Session schemas capture the full lifecycle of a listening session, including
mood tracking before and after. The `moodSchema` values drive the mood-before /
mood-after fields in the `sessions` table.

- **`sessionStatusSchema`** — `active`, `paused`, `completed`, `abandoned`.
- **`moodSchema`** — `calm`, `relaxed`, `peaceful`, `focused`, `anxious`,
  `stressed`, `tired`, `energized`, `happy`, `sad`, `neutral` (11 values).
- **`createSessionRequestSchema`** — `meditationId` (uuid), optional `courseId`
  (uuid), optional `moodBefore`.
- **`updateSessionRequestSchema`** — optional `completedSeconds` (≥0), `status`
  ∈ `paused|completed|abandoned`, `moodAfter`, `rating` (int 1–5), `feedback`
  (≤1000 chars).
- **`sessionsQuerySchema`** — `page` (≥1, default 1), `limit` (1–100, default
  20), optional `meditationId`/`courseId`/`status`/`startDate`/`endDate`,
  `sortBy` ∈ `startedAt|completedAt|durationSeconds` (default `startedAt`),
  `sortOrder` ∈ `asc|desc` (default `desc`).

### 6.3 Meditation Schemas

These schemas mirror the Drizzle enums (§4.17) and are used for both filtering
list queries and validating content creation payloads.

- **`meditationCategorySchema`** — same 15 values as the `meditation_category`
  Drizzle enum.
- **`contentTypeSchema`** — `guided`, `unguided`, `music`, `soundscape`,
  `story`.
- **`experienceLevelSchema`** — `beginner`, `intermediate`, `advanced`.
- **`meditationsQuerySchema`** — `page`/`limit` (string-coerced, clamped 1–100),
  filters `category`, `contentType`, `experienceLevel`, `isPremium`,
  `isFeatured`, `instructor`, `tag`, `minDuration`, `maxDuration`; `sortBy` ∈
  `title|duration|playCount|rating|publishedAt|createdAt`; `sortOrder` ∈
  `asc|desc`; `search`.

### 6.4 Progress Schemas

The progress schemas define the exact response shapes clients receive for streak
and statistics endpoints. `daysUntilStreakLoss` enables the "streak at risk"
notification logic — when this value reaches 0, the streak will be lost at
midnight.

- **`streakResponseSchema`** — `currentStreak`, `longestStreak`,
  `streakStartDate?`, `lastMeditationDate?`, `isStreakActive`,
  `daysUntilStreakLoss?`.
- **`statisticsResponseSchema`** — `totalMinutes`, `totalSessions`,
  `averageSessionMinutes`, `completedMeditations`, `completedCourses`,
  `currentStreak`, `longestStreak`, `weeklyGoalMinutes`,
  `weeklyMinutesCompleted`, `weeklyGoalProgress` (0–100), `categoryCounts`
  (record of category → count), `memberSince`.
- **`historyQuerySchema`** — `granularity` ∈ `day|week|month` (default `day`).

### 6.5 Achievement Schemas

The `@tara/api` achievement vocabulary uses a four-tier system (`bronze` →
`silver` → `gold` → `platinum`) and a six-category taxonomy. This differs from
the `@tara/database` Prisma `AchievementRarity` (which uses `COMMON` →
`UNCOMMON` → `RARE` → `EPIC` → `LEGENDARY`) — the two schemas model achievements
independently.

- **`achievementCategorySchema`** — `streak`, `time`, `sessions`, `courses`,
  `exploration`, `special`.
- **`achievementTierSchema`** — `bronze`, `silver`, `gold`, `platinum`.
- **`achievementSchema`** — `id`, `name`, `description`, `category`, `tier`,
  `iconUrl?`, `requirement` (`type` ∈
  `streak|total_minutes|total_sessions| courses_completed|categories_tried|special`,
  `value` int >0, `metadata?`), `points` (≥0), `isSecret`.

### 6.6 Subscription Schemas

These schemas cover the full billing surface: plan definitions, checkout, and
store receipt verification. `pricingPlanSchema.price.interval` being null is the
signal for a lifetime purchase (no recurring interval).

- **`subscriptionTierSchema`** — `free`, `premium`, `lifetime`.
- **`subscriptionStatusSchema`** — `active`, `canceled`, `expired`, `past_due`,
  `trialing`.
- **`pricingPlanSchema`** — `id`, `name`, `description`, `tier`, `price`
  (`amount` cents, `currency` default `usd`, `interval` ∈ `month|year|lifetime`
  or null), `features[]`, `isPopular`, `trialDays?`.
- Request schemas: `checkoutRequestSchema`, `iosReceiptRequestSchema`,
  `androidPurchaseRequestSchema`.

### 6.7 Notification Schemas

- **`timeSchema`** — `HH:MM` 24-hour regex, used for `dailyReminderTime` and
  quiet-hours boundaries.
- **`notificationPreferencesSchema`** — `notificationsEnabled`,
  `dailyReminderEnabled`, `dailyReminderTime?`, `streakReminderEnabled`,
  `newContentEnabled`, `courseProgressEnabled`, `weeklyDigestEnabled`,
  `marketingEnabled`, `timezone` (IANA).
- **`registerDeviceSchema`** — `deviceId` (1–255), `deviceName?` (≤100),
  `platform` ∈ `ios|android|web`, `osVersion?`, `appVersion?`, `pushToken`
  (≤500), `pushProvider` ∈ `apns|fcm|web_push`.

### 6.8 Search and Analytics Schemas

- **`searchTypeSchema`** — `meditation`, `course`, `teacher`, `all`.
- `searchResultSchema` — discriminated union on `type` over meditation/course/
  teacher result shapes.
- Analytics: `analyticsEventSchema`, `batchEventsRequestSchema`,
  `deviceInfoSchema` (`platform` ∈ `ios|android|web`, `deviceType` ∈
  `mobile|tablet|desktop|watch|tv`), `userContextSchema` (`subscriptionTier` ∈
  `free|premium|lifetime`), `experimentAssignmentRequestSchema`,
  `featureFlagRequestSchema` (response `source` ∈ `default|rule|override`).

---

## 7. `@tara/database` — Prisma Schema (Separate Layer)

`@tara/database` is a standalone library package whose Prisma schema lives in
`libs/tara/database/prisma/schema.prisma`. It is **not** used by the `@tara/api`
runtime at all — the API uses Drizzle exclusively. The Prisma schema exists to
serve library tooling, richer domain modeling, and the seed scripts. Its
datasource URL is `TARA_DATABASE_URL`, which may point to a different database
instance than the one the API reads from.

The Prisma schema enables four PostgreSQL extensions: `uuid-ossp`, `pgcrypto`,
`vector`, and `pg_trgm`. The initial migration is in
`prisma/migrations/20260506000000_initial/`.

### 7.1 Prisma Models (30)

The Prisma schema defines 30 models. Several of these represent domain concepts
that the Drizzle schema does not model independently (e.g., `Teacher` as a
first-class row, `Ritual`, `AmbientSound`, `Feedback`).

`User`, `Profile`, `Subscription`, `Payment`, `Device`, `Teacher`, `Meditation`,
`RelatedMeditation`, `AmbientSound`, `Course`, `Lesson`, `LessonContent`,
`Ritual`, `MeditationSession`, `UserProgress`, `Streak`, `Achievement`,
`UserAchievement`, `Favorite`, `Download`, `HistoryEntry`, `Notification`,
`UserSettings`, `Feedback`.

Notable Prisma-only constructs not present in the `@tara/api` Drizzle schema:

- **`Payment`** — payment history per `Subscription` (`amount` cents,
  `currency`, `status`, `platform`, store references, refund fields).
- **`Teacher`** — first-class teacher records (`name`, `slug`, `bio`, `fullBio`,
  `credentials[]`, `specializations[]`, social URLs, `featured`). The Drizzle
  schema denormalizes teacher data into the `meditations` table (`instructor`
  varchar); this Prisma model provides the canonical teacher record.
- **`AmbientSound`** — sound library (`name`, `slug`, `category`, `audioUrl`,
  `isLoop`, `isPremium`); meditations may reference one as `backgroundMusic`.
- **`Lesson` / `LessonContent`** — courses decompose into ordered `Lesson`s,
  each holding ordered `LessonContent` items (meditation / text / exercise).
  This provides a richer course structure than the Drizzle `course_meditations`
  junction table.
- **`Ritual`** — canonical ritual records (`slug`, `title`, `summary`,
  `primaryDomain` default `tara`, `domains[]`, `origin`, `kind`, `status`,
  `visibility`, JSON `schedule` / `context` / `components` / `intendedOutcomes`,
  `estimatedDurationMinutes`).
- **`Streak`** — dedicated streak record (current/longest streak, total days/
  minutes/sessions, weekly/monthly minute rollups). More granular than the
  inline streak fields in the Drizzle `user_progress` table.
- **`UserSettings`** — notification, playback, privacy, and display settings,
  including `reminderDays Int[]` and `theme`.
- **`Feedback`** — bug reports, feature requests, content ratings, app reviews,
  with admin response fields and status.

### 7.2 Prisma Enums

The Prisma enums use UPPER_CASE by convention, in contrast to the Drizzle
snake_case enums. Concept overlap (e.g., subscription tiers) is intentional but
the exact values differ — for example Prisma `SubscriptionStatus` includes
`GRACE_PERIOD` which has no equivalent in the Drizzle `subscription_status`
enum.

| Enum                 | Values                                                                                                                                           |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `UserStatus`         | `ACTIVE`, `SUSPENDED`, `DELETED`, `PENDING_VERIFICATION`                                                                                         |
| `ExperienceLevel`    | `BEGINNER`, `INTERMEDIATE`, `ADVANCED`                                                                                                           |
| `SubscriptionStatus` | `FREE`, `TRIAL`, `ACTIVE`, `CANCELLED`, `EXPIRED`, `GRACE_PERIOD`                                                                                |
| `SubscriptionPlan`   | `FREE`, `MONTHLY`, `YEARLY`, `LIFETIME`                                                                                                          |
| `Platform`           | `IOS`, `ANDROID`, `WEB`                                                                                                                          |
| `PaymentStatus`      | `PENDING`, `COMPLETED`, `FAILED`, `REFUNDED`                                                                                                     |
| `MeditationCategory` | `MEDITATION`, `SLEEP`, `BREATHING`, `FOCUS`, `STRESS`, `MORNING`, `SELF_COMPASSION`, `GRATITUDE`, `BODY_SCANS`, `WALKING`, `KIDS`, `SOUNDS` (12) |
| `DifficultyLevel`    | `BEGINNER`, `INTERMEDIATE`, `ADVANCED`                                                                                                           |
| `SoundCategory`      | `NATURE`, `RAIN`, `OCEAN`, `FOREST`, `AMBIENT`, `WHITE_NOISE`, `MUSIC`                                                                           |
| `LessonContentType`  | `MEDITATION`, `TEXT`, `EXERCISE`                                                                                                                 |
| `ProgressStatus`     | `NOT_STARTED`, `IN_PROGRESS`, `COMPLETED`                                                                                                        |
| `AchievementType`    | `STREAK_DAYS`, `TOTAL_SESSIONS`, `TOTAL_MINUTES`, `COURSES_COMPLETED`, `CATEGORY_MASTERY`, `SPECIAL`                                             |
| `AchievementRarity`  | `COMMON`, `UNCOMMON`, `RARE`, `EPIC`, `LEGENDARY`                                                                                                |
| `DownloadStatus`     | `PENDING`, `DOWNLOADING`, `COMPLETED`, `FAILED`, `EXPIRED`                                                                                       |
| `NotificationType`   | `REMINDER`, `STREAK`, `ACHIEVEMENT`, `NEW_CONTENT`, `PROMOTION`, `SYSTEM`                                                                        |
| `Theme`              | `LIGHT`, `DARK`, `SYSTEM`                                                                                                                        |
| `FeedbackType`       | `BUG_REPORT`, `FEATURE_REQUEST`, `CONTENT_RATING`, `APP_REVIEW`, `GENERAL`                                                                       |
| `FeedbackStatus`     | `PENDING`, `REVIEWED`, `IN_PROGRESS`, `RESOLVED`, `CLOSED`                                                                                       |

> The Drizzle (§4) and Prisma (§7) schemas overlap conceptually but differ in
> table names, field naming (snake_case maps), enum casing, and coverage. The
> `@tara/api` runtime uses Drizzle. `@tara/database` is a standalone package.

---

## 8. Authentication and Authorization

`@tara/api` issues and validates JWTs in-domain (`apps/tara/api/src/auth/`). The
auth layer depends on `@oshun/auth` primitives for token signing but owns its
own database tables and business rules.

### 8.1 JWT Configuration (`auth/service.ts`)

The following table shows the environment variables that control token behavior.
All TTL values are in seconds. The `TARA_JWT_SECRET` / `JWT_SECRET` fallback
order allows the secret to be shared across services if needed.

| Setting           | Env var                            | Default             |
| ----------------- | ---------------------------------- | ------------------- |
| Signing secret    | `TARA_JWT_SECRET` / `JWT_SECRET`   | dev fallback secret |
| Issuer            | `TARA_JWT_ISSUER`                  | `tara-api`          |
| Audience          | `TARA_JWT_AUDIENCE`                | `tara-app`          |
| Access token TTL  | `TARA_ACCESS_TOKEN_TTL` (seconds)  | `900` (15 minutes)  |
| Refresh token TTL | `TARA_REFRESH_TOKEN_TTL` (seconds) | `604800` (7 days)   |

Refresh tokens use rotation families (`refresh_tokens.family`). Email
verification and password-reset tokens are hashed before storage; the default
verification/reset token lifetime is 24 hours. Login attempts are recorded in
`login_attempts`; accounts lock via `lockedUntil` after repeated failures.

### 8.2 Rate Limiting (`middleware/rate-limit.ts`)

Rate limiting uses an in-memory sliding-window algorithm over 60-second windows.
Each authenticated request's tier is determined from the JWT claims — anonymous
requests use the lowest tier. Headers inform clients of their remaining budget.

| Tier      | Requests / minute |
| --------- | ----------------- |
| anonymous | 60                |
| free      | 300               |
| premium   | 1000              |
| admin     | 5000              |

The default `rateLimitMiddleware` on `/api/*` uses the **anonymous** tier; a
`tieredRateLimitMiddleware` selects a tier from the request's `userTier`
context. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
`X-RateLimit-Reset`; a 429 includes `Retry-After`.

### 8.3 Premium Content Access — CloudFront Signed URLs

Premium audio is never streamed through the API. Instead,
`apps/tara/api/src/services/cdn/signed-urls.ts` provides `SignedUrlService`,
which generates time-limited CloudFront signed URLs (and signed cookies,
optionally IP-restricted). The API checks the requesting user's subscription
tier before generating a URL; free-tier users receive a 403 for premium content.

Configuration: `TARA_CLOUDFRONT_DOMAIN`, `TARA_CLOUDFRONT_KEY_PAIR_ID`,
`TARA_CLOUDFRONT_PRIVATE_KEY`, `TARA_SIGNED_URL_EXPIRATION` (seconds, default
3600). Content tiers recognized: `free`, `premium`, `exclusive`.

---

## 9. `@tara/analytics` — Events, Experiments, Flags

`@tara/analytics` (`libs/tara/analytics`) is the typed telemetry library for the
entire Tara platform. It provides a strongly-typed event map, pluggable provider
architecture, experiment management, and feature flags.

### 9.1 Typed Event Map (`src/types/events.ts`)

`TaraEventMap` defines **45** typed events, each with a strongly-typed property
interface. Typing events prevents misnamed or missing properties from reaching
the analytics pipeline. Events are grouped by feature area:

- **Meditation** — `meditation_started`, `meditation_completed`,
  `meditation_paused`, `meditation_resumed`, `meditation_skipped`
- **Course** — `course_started`, `course_completed`, `lesson_completed`,
  `course_progress`
- **Timer** — `timer_started`, `timer_completed`, `timer_extended`
- **Breathing** — `breathing_started`, `breathing_completed`
- **Subscription** — `subscription_started`, `subscription_renewed`,
  `subscription_cancelled`, `subscription_trial_started`,
  `subscription_trial_ended`
- **Content** — `content_downloaded`, `content_deleted`, `content_favorited`,
  `content_unfavorited`, `content_rated`, `content_shared`
- **Search** — `search_performed`, `search_result_clicked`
- **Notification** — `notification_received`, `notification_opened`,
  `notification_dismissed`
- **Streak/Achievement** — `streak_milestone`, `achievement_unlocked`
- **Onboarding** — `onboarding_started`, `onboarding_step_completed`,
  `onboarding_completed`, `onboarding_skipped`
- **App lifecycle** — `app_opened`, `app_backgrounded`, `app_crashed`
- **Screen** — `screen_viewed`
- **Error** — `error_occurred`

Each event extends `BaseEventProperties` (`timestamp?`, `sessionId?`, `userId?`,
`anonymousId?`). For example, `MeditationCompletedProperties` carries
`scheduledDuration`, `actualDuration`, `completionPercentage`, `pauseCount`,
`totalPauseDuration`, `backgroundedDuration`, `wasInterrupted`, and an optional
`interruptionReason` ∈
`phone_call|notification|user_pause|app_background|system`.
`AchievementUnlockedProperties.achievementTier` ∈ `bronze|silver|gold|platinum`.

### 9.2 Common Analytics Types (`src/types/common.ts`)

Shared vocabulary types used across event properties:

`Platform` (`ios|android|web`), `DeviceType` (`phone|tablet|desktop|tv|watch`),
`SubscriptionTier` (`free|premium|lifetime`), `ContentType` (8 values incl.
`sleep_story`, `ambient_sound`), `MeditationTechnique` (10 values incl. `zen`,
`transcendental`, `mantra`), `MeditationCategory` (12 values), `TimeGranularity`
(`hour|day|week|month|quarter|year`), `MetricType`
(`counter|gauge|histogram|summary`).

### 9.3 Experiments and Feature Flags (`src/types/experiments.ts`)

These types define the full A/B experiment lifecycle, from draft through
completion. Variant assignments are deterministic using `murmurhash3js` so a
given user always sees the same variant across sessions.

- **`ExperimentStatus`** — `draft`, `running`, `paused`, `completed`,
  `archived`.
- **`ExperimentDefinition`** — `id`, `name`, `description`, `hypothesis`,
  `status`, `variants[]`, `primaryMetric`, `secondaryMetrics?`,
  `targetAudience?`, `trafficPercentage`, dates, timestamps.
- **`ExperimentVariant`** — `id`, `name`, `description?`, `weight` (0–100),
  `config`.
- **`ExperimentAssignment.source`** — `deterministic`, `random`, `override`.
- **`ExperimentConfig`** — `hashSeed`, `stickinessAttribute` ∈
  `userId|deviceId|sessionId`, `defaultTrafficPercentage`, `minimumSampleSize`,
  `confidenceLevel`, `minimumDetectableEffect`.
- **`FeatureFlagValue`** — `boolean | string | number | object`.
- **`FeatureFlagCondition.operator`** — `equals`, `not_equals`, `contains`,
  `not_contains`, `gt`, `lt`, `gte`, `lte`, `in`, `not_in`.

### 9.4 Providers and Tracker

`@tara/analytics` exports `TaraTracker` / `createTracker` as the main tracking
surface. The `ExperimentManager` (`createExperimentManager`) ships with an
`InMemoryExperimentStore` for testing. Three built-in providers are exported:
`InternalAnalyticsProvider`, `ConsoleAnalyticsProvider`, and
`MemoryAnalyticsProvider`. Event utilities include type guards, event builders
(`buildMeditationStarted`, etc.), `validateEventProperties`, and
`createEventId`. `murmurhash3js` handles deterministic variant hashing.

---

## 10. `@tara/monitoring` — Error Tracking and Performance

`@tara/monitoring` (`libs/tara/monitoring`) provides production observability.
It uses a pluggable provider architecture so the underlying backend (Sentry, a
custom log aggregator, or in-memory for tests) can be swapped without changing
call sites.

- **Error tracking** — `TaraErrorTracker` with `captureException()`,
  `captureMessage()`, `addBreadcrumb()`, `setUser()`, `setTag()`, `setExtra()`,
  `withScope()`. `ErrorSeverity` covers `fatal`, `error`, `warning`, `info`,
  `debug`.
- **Tara-specific contexts** — `MeditationErrorContext`,
  `SubscriptionErrorContext`, `AudioErrorContext`, and a
  `TaraBreadcrumbCategory` taxonomy for structured breadcrumb labeling.
- **Performance** — `PerformanceMonitor` with `startTransaction`, `trace`,
  `traceSync`, `Span`/`Transaction`, `Measurement`, and `WebVitalName`.
- **Providers** — `ConsoleErrorProvider`, `MemoryErrorProvider` (testing), and
  `ServerErrorProvider` (with `InMemoryErrorStore`). `@sentry/node` and
  `@sentry/react` are **optional** peer dependencies — the library compiles and
  runs without them.
- `@tara/api` wires monitoring through `middleware/error-tracking.ts`.

---

## 11. `@tara/config` — Runtime Configuration and Feature Flags

`@tara/config` (`libs/tara/config`) provides type-safe runtime configuration and
a feature flag system for all Tara applications. It reads environment variables
at startup and exposes a structured config object rather than raw `process.env`
access scattered through the codebase.

- **`TaraEnvironment`** — `development`, `staging`, `production`, `test`.
- **`TaraPlatform`** — `web`, `ios`, `android`, `api`.
- **`TaraRuntimeConfig`** — structured config covering:
  - `environment`: the active `TaraEnvironment`
  - `app`: name (`Tara`), version, `bundleIds` (`com.oshun.tara`),
    `deepLinkSchemes` (`[tara, com.oshun.tara]`), `universalLinksDomain`
    (`tara.oshun.app`)
  - `endpoints`: `apiBaseUrl`, `webBaseUrl`, `cdnBaseUrl`
  - `analytics`: `enabled`, `provider` ∈ `internal|console|disabled`
  - `crashReporting`: `enabled`, `provider` ∈ `sentry|none`
- **`createTaraConfig()`** / **`configFromEnvironment()`** — build a config
  object; analytics is automatically disabled in `test`, crash reporting is
  enabled in `staging` and `production`. Env keys: `TARA_ENV`, `TARA_API_URL`,
  `TARA_WEB_URL`, `TARA_CDN_URL`, `TARA_ANALYTICS_ENABLED`,
  `TARA_CRASH_REPORTING_ENABLED`.

### 11.1 Feature Flags (`TaraFeatureFlagKey`, 10 flags)

Ten typed feature flags control platform-specific feature availability. Flags
default to `true` in all environments unless the platform is not in the flag's
allow-list. This means features like `enableOfflineMode` are on by default for
iOS and Android but simply not evaluated for web.

| Flag                       | Default | Platforms              |
| -------------------------- | ------- | ---------------------- |
| `enableOfflineMode`        | `true`  | ios, android           |
| `enableSocialFeatures`     | `true`  | web, ios, android      |
| `enablePremiumContent`     | `true`  | web, ios, android, api |
| `enableNotifications`      | `true`  | ios, android, api      |
| `enableAnalytics`          | `true`  | web, ios, android, api |
| `enableCrashReporting`     | `true`  | web, ios, android, api |
| `enableBreathingExercises` | `true`  | web, ios, android      |
| `enableTimerMode`          | `true`  | web, ios, android      |
| `enableBackgroundSounds`   | `true`  | web, ios, android      |
| `enableProgressInsights`   | `true`  | web, ios, android      |

`isFeatureEnabled()` returns `false` when the requesting platform or environment
is not in a flag's allow-list; otherwise it honors an explicit override or the
flag default. `createFeatureResolver()` and `listEnabledFeatures()` build
context-bound resolvers for use in app initialization code.

---

## 12. `@tara/content` — Content Model and Hooks

`@tara/content` (`libs/tara/content`) is the richest of the domain libraries. It
combines TypeScript type definitions for all content objects, a typed HTTP
client with error hierarchy, a multi-layer cache (in-memory, persistent,
stale-while-revalidate), filter utilities, a full-text search engine, WebVTT
accessibility helpers, and React data hooks.

### 12.1 Core Content Types (`src/types/`)

The types in this library are more expressive than the API's Drizzle-backed
types — they model the full content object graph including audio sets,
responsive images, transcript markers, chapters, and teacher profiles with
specialties.

- **`Meditation`** — full content object: `id`, `slug`, `title`,
  `shortDescription`, `description`, `type`, `categories[]`, `difficulty`,
  `duration`, `teacher`, `audio` (`AudioSet`), `images` (`ResponsiveImageSet`),
  optional `visual`, `markers[]`, `chapters[]`, `transcript`, `settings`,
  `meta`, `stats`, `relatedIds`, `tags`, `isFeatured`, `isNew`.
- **`MeditationType`** (12) — `guided`, `unguided`, `sleep`, `focus`,
  `breathwork`, `body-scan`, `visualization`, `mantra`, `mindfulness`,
  `loving-kindness`, `walking`, `movement`.
- **`MeditationCategory`** (25) — `stress`, `sleep`, `focus`, `anxiety`,
  `depression`, `self-esteem`, `relationships`, `gratitude`, `productivity`,
  `creativity`, `morning`, `evening`, `commute`, `work`, `exercise`, `pain`,
  `healing`, `grief`, `anger`, `happiness`, `calm`, `energy`, `emergency`,
  `beginner`, `intermediate`, `advanced`.
- **`DifficultyLevel`** — `beginner`, `intermediate`, `advanced`, `all-levels`.
- **`AudioFormat`** — `mp3`, `aac`, `ogg`, `wav`, `flac`, `m4a`.
- **`AudioQuality`** — `low`, `standard`, `high`, `lossless`.
- **`AccessLevel`** — `free`, `premium`, `subscriber`, `purchase`.
- **`ContentStatus`** — `draft`, `published`, `archived`, `scheduled`.
- **`Course`** — `format` (`daily|weekly|self-paced|scheduled|live`),
  `sections[]` of `CourseSection`, `lessonCount`, `outcomes[]`,
  `prerequisites?`, `certificate?`. `Lesson` is a union of seven lesson types:
  `MeditationLesson`, `VideoLesson`, `ArticleLesson`, `ExerciseLesson`,
  `QuizLesson`, `ReflectionLesson`, `DiscussionLesson`. `LessonStatus` ∈
  `locked|available|in-progress|completed`. `EnrollmentStatus` ∈
  `not-enrolled|enrolled|in-progress|completed|abandoned`.
- **`Teacher`** — `bio` (`TeacherBio` short/medium/full), `photo`,
  `specialties[]` (`TeacherSpecialty`, 25 values incl. `trauma-informed`,
  `sound-healing`), `credentials[]`, `socialProfiles[]`, `teachingStyle`,
  `featuredContent`. `TeacherProfile` extends with `availability`, `rates`,
  testimonials, FAQ, gallery.
- **`Collection`** — `type` (`CollectionType`, 9 values), `items[]` of
  `CollectionItem`. **`Program`** — multi-day `type` (`ProgramType`:
  `challenge|journey|foundation|deep-dive|seasonal|retreat|live`), `structure`
  (`daily`/`weekly`), `days[]`/`weeks[]`, `milestones[]`,
  `ProgramProgress`/`ProgramDayProgress`. `DayStatus` ∈
  `locked|available|completed|skipped`.
- **Sound types** — `AmbientSound` (`AmbientCategory` 10, `AmbientType` ~60
  values), `BackgroundMusic` (`MusicMood` 17), `BellSound` (`BellType` 12),
  `BinauralBeat` (`BinauralFrequency` delta/theta/alpha/beta/gamma), `SoundMix`,
  `SoundPreferences`.

### 12.2 API Client, Cache, Search, and Hooks

These are the runtime-facing exports that application code actually calls.

- **`ContentClient`** — typed HTTP client with presets
  (`createProductionClient`, `createDevelopmentClient`, `createTestClient`,
  resilient/fast-fail variants). A full error hierarchy: `ContentApiError`,
  `NetworkError`, `TimeoutError`, `NotFoundError`, `UnauthorizedError`,
  `ForbiddenError`, `RateLimitError`, `ValidationError`, `ServerError`,
  `ServiceUnavailableError`, `CacheError`.
- **Cache** — `InMemoryContentCache`, `PersistentContentCache`,
  `SWRCacheManager` (stale-while-revalidate) with prefetching.
- **Filters** — composable filter/sort utilities plus meditation- and
  course-specific filters (`sleepMeditations`, `beginnerCourses`, etc.).
- **Search** — `ContentSearchEngine`, `ContentSearchIndex`, tokenizer, spelling
  correction (`levenshteinDistance`), trending-search calculation.
- **Accessibility** — WebVTT caption parsing/validation (`parseWebVtt`,
  `validateCaptionCues`, `calculateCaptionCoverage`).
- **Hooks** — React data hooks: `useMeditations`, `useInfiniteMeditations`,
  `useMeditation`, `useCourses`, `useCourseProgress`, `useTeachers`,
  `useCollections`, `usePrograms`, `useDailyContent`, plus async primitives
  (`useAsync`, `useMutation`). React is an optional peer dependency.

---

## 13. `@tara/features` — Feature-State and Ritual Logic

`@tara/features` (`libs/tara/features`) derives product-level state from raw
progress data, gates features by subscription and platform, and models the
concept of "rituals" — structured practice sequences composed of multiple steps.
It depends on `@tara/config` and is consumed by both `@tara/web` and
`@tara/mobile`.

### 13.1 Core Types (`src/types.ts`)

These types form the input/output contract for progress and streak calculations.
`TaraProgressSnapshot` is the input — a client builds it from locally-cached
session data — and `ProgressSummary` / `StreakSummary` are the derived outputs.

- **`TaraSessionRecord`** — `id`, `startedAt`, `durationSeconds`, `completed`,
  `type` ∈ `timer|breathing|guided|ambient|sleep|custom`, `contentId?`.
- **`TaraProgressSnapshot`** — `sessions[]`, `weeklyGoalMinutes`, `timezone?`.
- **`ProgressSummary`** — `totalSessions`, `completedSessions`, `totalMinutes`,
  `weeklyGoalMinutes`, `weeklyMinutes`, `weeklyGoalProgress`.
- **`StreakSummary`** — `currentStreak`, `longestStreak`, `completedToday`.
- **`FeatureGateResult`** — `{ key: TaraFeatureFlagKey, enabled }`.

### 13.2 Modules (`src/index.ts` re-exports)

`@tara/features` re-exports from a collection of focused modules. The
cross-domain helpers at the end of this list are the integration points where
Tara surfaces content from other Oshun domains:

`progress`, `streak`, `gates` (feature gating), `ritual-template`,
`ritual-session`, `ritual-completion-event`, `continuation-state`,
`humane-recovery`, `resume-memory`, `library-actions`, `duration-buckets`,
`context-tags`, and taxonomy modules (`mood-taxonomy`, `theme-taxonomy`,
`modality-taxonomy`, `lineage-taxonomy`), plus a `triggers/` subsystem
(`trigger-engine`, `scheduling-rules`) and cross-domain helpers
(`nisaba-passage-companions`, `arete-next-steps`, `nyx-perspective-prompts`,
`veritas-sophia-explanatory-notes`, `assistant-follow-ups`).

Key grounded constants and types:

- `RITUAL_COMPLETION_EVENT_NAME` = `tara.ritual.completed`.
- `RitualStepKind` — `meditation`, `breathwork`, `sound`, `movement`, `posture`,
  `visualization`, `prayer`, `devotional-reading`, `passage`, `journaling`,
  `nyx-perspective`.
- `BreathworkPattern` — `box`, `4-7-8`, `coherent`, `alternate-nostril`.
- `RitualTemplateStatus` — `draft`, `review`, `active`, `retired`.
- `MoodTaxonomy` — 12 values (`anxious`, `scattered`, `restless`, `heavy`,
  `low`, `neutral`, `curious`, `joyful`, `agitated`, `grieving`, `fearful`,
  `peaceful`); `MoodDistressLevel` ∈ `none|low|moderate|high` plus
  `MoodDistressSignal` adds `crisis`.

---

## 14. `@tara/ui` — Component Library

`@tara/ui` (`libs/tara/ui`) is a React component library (`react`/`react-dom`
peer deps) with design tokens and a theme provider. It is the visual building
block shared across `@tara/web` and any future Tara applications.

### 14.1 Components (`src/components/`)

There are 29 component directories, each with an implementation file, tests, and
an index export:

`AchievementBadge`, `AudioPlayer`, `Avatar`, `Badge`, `BottomSheet`,
`Breadcrumb`, `BreathingVisualizer`, `Button`, `Card`, `Chip`, `CourseProgress`,
`Divider`, `Grid`, `Header`, `IconButton`, `Input`, `MeditationCard`,
`MiniPlayer`, `Modal`, `ProgressChart`, `SafeArea`, `ScrollView`,
`SegmentedControl`, `SessionComplete`, `Skeleton`, `SoundMixer`,
`StreakDisplay`, `TabBar`, `TeacherCard`, `TimerDisplay`, `Toast`, `Typography`.

### 14.2 Tokens, Theme, and Animations

- **Tokens** (`src/tokens/`) — five token modules: `colors`, `typography`,
  `spacing`, `effects`, `breakpoints`.
- **Theme** (`src/theme/`) — a `provider` component exposing design tokens for
  both light and dark mode.
- **Animations** (`src/animations/`) — `primitives`, `micro`, `transitions`.
  Every animation provides a reduced-motion variant as required by the WCAG 2.1
  AA accessibility target.
- An accessibility spec (`__tests__/accessibility.spec.tsx`) uses `jest-axe` to
  validate component-level accessibility.

---

## 15. `@tara/api-client` and `@tara/database`

These two libraries provide the type-safe data access layer for code outside the
API server itself.

- **`@tara/api-client`** (`libs/tara/api-client`) — a typed HTTP client
  (`client.ts`) plus generated OpenAPI types (`src/generated/openapi.ts`);
  re-exports `components`, `operations`, `paths`, `webhooks` from the generated
  types. Use this library — not hand-rolled fetch calls — when accessing the
  Tara API from any TypeScript client.
- **`@tara/database`** (`libs/tara/database`) — the Prisma schema documented in
  §7, generated client, `client.ts` connection helper, and seed scripts
  (`prisma/seed.ts`, `prisma/seed-test.ts`). Peer dependency:
  `@prisma/client ^6.0.0`.

---

## 16. Configuration

### 16.1 `@tara/api` Environment Variables

The following environment variables configure the `@tara/api` runtime. All
variables without defaults must be set in production; the Drizzle migration tool
reads `DATABASE_URL` or `TARA_DATABASE_URL`.

| Variable                                                      | Purpose                                  |
| ------------------------------------------------------------- | ---------------------------------------- |
| `PORT` / `HOST`                                               | API listen port (3001) / host (0.0.0.0)  |
| `NODE_ENV`                                                    | Environment                              |
| `CORS_ORIGIN`                                                 | Allowed CORS origin (default `*`)        |
| `TARA_JWT_SECRET` / `JWT_SECRET`                              | JWT signing secret                       |
| `TARA_JWT_ISSUER` / `TARA_JWT_AUDIENCE`                       | JWT issuer / audience claims             |
| `TARA_ACCESS_TOKEN_TTL` / `TARA_REFRESH_TOKEN_TTL`            | Token TTLs in seconds                    |
| `TARA_CLOUDFRONT_DOMAIN`                                      | CloudFront distribution domain           |
| `TARA_CLOUDFRONT_KEY_PAIR_ID` / `TARA_CLOUDFRONT_PRIVATE_KEY` | CloudFront signing key                   |
| `TARA_SIGNED_URL_EXPIRATION`                                  | Signed-URL lifetime (seconds, def. 3600) |

Drizzle migrations are configured via `drizzle.config.ts`; `@tara/database` uses
`TARA_DATABASE_URL`.

### 16.2 `@tara/config` Environment Variables

These variables are read by `configFromEnvironment()` in `@tara/config`. They
control which analytics and crash-reporting providers are activated at runtime.

`TARA_ENV` (or `NODE_ENV`), `TARA_API_URL`, `TARA_WEB_URL`, `TARA_CDN_URL`,
`TARA_ANALYTICS_ENABLED`, `TARA_CRASH_REPORTING_ENABLED`.

---

## 17. Integration Points

Understanding which shared platform packages Tara depends on — and which
external services it calls — is essential for onboarding and for security
reviews.

### 17.1 Internal Oshun Dependencies

These are the monorepo-internal packages from `libs/shared/` and `libs/oshun/`
that Tara consumes. The boundary matters: Tara never owns these packages — bug
fixes and upgrades flow from the shared library teams.

| Dependency                                | Used by                         | Role                                |
| ----------------------------------------- | ------------------------------- | ----------------------------------- |
| `@oshun/auth`, `-auth-primitives`         | `@tara/api`, `@tara/web`        | JWT auth primitives                 |
| `@oshun/cache`                            | `@tara/api`                     | Redis cache                         |
| `@oshun/database`                         | `@tara/api`                     | Database client primitives          |
| `@oshun/logging`                          | `@tara/monitoring`, `@tara/web` | Structured logging                  |
| `@oshun/contracts`                        | `@tara/ui`                      | Shared contracts                    |
| `@oshun/http-client`, `-errors`, `-types` | `@tara/web`                     | HTTP client / shared errors / types |
| `@oshun/meditation-player`                | `@tara/web`, `@tara/mobile`     | Audio playback engine               |
| `@oshun/meditation-breathing`             | `@tara/web`, `@tara/mobile`     | Breathing exercise engine           |

### 17.2 External Services

Tara integrates with eight external services. Engineers touching billing or
notification code should ensure they understand which service handles which
platform — for example, web billing uses Stripe directly while mobile billing
uses RevenueCat as an abstraction layer over App Store and Google Play.

| Service         | Purpose                                                        |
| --------------- | -------------------------------------------------------------- |
| Stripe          | Web checkout, customer portal, billing webhooks (`@tara/api`)  |
| RevenueCat      | Cross-platform IAP validation (`react-native-purchases`)       |
| Apple App Store | iOS receipt verification (`/subscription/ios/verify`)          |
| Google Play     | Android purchase verification (`/subscription/android/verify`) |
| AWS S3          | Avatar upload storage (`@aws-sdk/client-s3`)                   |
| AWS CloudFront  | Signed-URL delivery of premium audio                           |
| APNs / FCM      | Push notifications (`apns.ts`, `fcm.ts` services)              |
| Expo EAS        | Mobile build and OTA pipeline (`eas.json`)                     |

---

## 18. Testing and Acceptance Criteria

### 18.1 Test Tooling

Each application and library has its own test setup. Load tests for the API use
k6, which is not part of the standard Vitest/Playwright suite.

| App / lib      | Tooling                                                                                                   |
| -------------- | --------------------------------------------------------------------------------------------------------- |
| `@tara/api`    | Vitest (`vitest.config.ts` + `vitest.integration.config.ts`); k6 load tests in `test/load/api-load.k6.js` |
| `@tara/web`    | Vitest, Playwright (+ `@axe-core/playwright`), Storybook, Lighthouse CI                                   |
| `@tara/mobile` | Jest (`jest-expo`); Maestro flows (`e2e/flows/`)                                                          |
| `libs/tara/*`  | Vitest co-located `*.spec.ts(x)` per package                                                              |

### 18.2 Acceptance Criteria

The following criteria define a passing deployment and test run for the Tara
domain:

- API boots on port 3001, serves `/health` and `/ready`, and exposes OpenAPI 3.1
  at `/api/v1/openapi.json` with Swagger UI at `/api/docs`.
- All 16 `/api/v1` route groups (plus the inline `/recommendations` handler)
  mount and validate inputs through their Zod schemas; protected groups reject
  unauthenticated requests.
- Auth issues an access+refresh token pair; refresh rotation works via token
  families; password and email-verification tokens are stored hashed.
- Rate limiting attaches `X-RateLimit-*` headers and returns 429 + `Retry-After`
  past the per-tier limit.
- Drizzle migrations in `apps/tara/api/drizzle/` apply cleanly; the
  `@tara/ database` Prisma migration applies cleanly.
- `@tara/analytics`, `@tara/monitoring`, `@tara/config`, `@tara/content`,
  `@tara/features`, and `@tara/ui` build, type-check, and pass their co-located
  Vitest suites.
- Web app builds (`next build`, standalone) and serves `[locale]`-prefixed
  routes; mobile app type-checks and runs Maestro smoke flows.

---

## 19. Source-of-Truth Index

This table maps documentation concerns to the actual source files. Use it when
you need to find the canonical definition of a type, schema, or piece of
configuration.

| Concern                          | Path                                                   |
| -------------------------------- | ------------------------------------------------------ |
| API app + middleware             | `apps/tara/api/src/app.ts`, `.../middleware/`          |
| API Drizzle schema               | `apps/tara/api/src/db/schema.ts`                       |
| API Drizzle migrations           | `apps/tara/api/drizzle/`                               |
| API routes + Zod schemas         | `apps/tara/api/src/routes/<group>/{routes,schemas}.ts` |
| API auth (JWT)                   | `apps/tara/api/src/auth/`                              |
| CDN signed URLs                  | `apps/tara/api/src/services/cdn/`                      |
| Prisma schema (`@tara/database`) | `libs/tara/database/prisma/schema.prisma`              |
| Analytics events / experiments   | `libs/tara/analytics/src/types/`                       |
| Monitoring                       | `libs/tara/monitoring/src/`                            |
| Runtime config + flags           | `libs/tara/config/src/`                                |
| Content types / hooks            | `libs/tara/content/src/`                               |
| Feature-state / rituals          | `libs/tara/features/src/`                              |
| UI components / tokens           | `libs/tara/ui/src/`                                    |
| Web app routes                   | `apps/tara/web/src/app/[locale]/`                      |
| Mobile app routes                | `apps/tara/mobile/app/`                                |
| Meditation engine                | `libs/meditation/*/src/`                               |
