# Aphrodite Domain — Technical Specifications

> Technical specification of the **implemented** Aphrodite surface: the adult
> live-streaming / creator-economy domain. This document is grounded in code —
> the Prisma data model, the shared event contracts, the core constants and
> types, the streaming and payment libraries, and the live Hono routes. Sections
> marked `(planned)` describe library directories that exist as feature code but
> are not yet wired into a deployed service. Where the spec describes types or
> endpoints, the cited file is the source of truth.

This document is the authoritative technical reference for engineers integrating
with or extending Aphrodite. It covers the Prisma data model, shared event
contracts, streaming and payment subsystem types, application API surface,
device protocols, compliance subsystem, configuration, and the full library
catalogue. The companion documents are `features.md` (product-level feature
descriptions) and `architecture.md` (system topology and cross-domain
dependencies).

---

## Scope and Implementation Status

Aphrodite is a very large domain. The repository contains:

- **`libs/aphrodite/*`** — 190 library directories. Roughly 186 contain real,
  substantial TypeScript implementations under `src/`. **Four are thin
  scaffolds** (`@aphrodite/chat`, `@aphrodite/payment`, `@aphrodite/safety`,
  `@aphrodite/sdk`): each exports a real `types.ts` but its functional
  subdirectories contain only a version constant and the comment
  `implementation will be added during service migration`. Treat these four as
  **scaffold** — types are usable, behaviour is not.
- **`apps/aphrodite/*`** — 17 applications, each a Hono service with real route
  source (see [Application Surface](#application-surface)).
- **`services/aphrodite/`** — does not exist. Aphrodite has no `services/` tier;
  runnable services live under `apps/aphrodite/`.

Because the domain is so wide, this specification gives **normative detail only
for the verified cross-cutting surface** — data model, events, streaming,
payments, devices, compliance, configuration — and summarises the remaining
library tree as a status-labelled catalogue in
[Library Catalogue](#library-catalogue). Every schema, enum, endpoint, and event
below is traceable to a cited file.

---

## Technology Stack

The table below summarises the key technology choices at each layer. All API
services are Hono (TypeScript); all validation uses Zod schemas; the database is
PostgreSQL accessed through Prisma. This consistency across services reduces the
cognitive overhead of moving between different Aphrodite apps.

| Layer               | Technology                                                                         |
| ------------------- | ---------------------------------------------------------------------------------- |
| API framework       | Hono (TypeScript), with `@hono/zod-validator` for request validation               |
| Validation          | Zod schemas (per-library `types.ts`, plus `@aphrodite/database` schemas)           |
| Database            | PostgreSQL via Prisma (`libs/aphrodite/database/prisma/schema.prisma`)             |
| DB connectivity     | `@oshun/database` (pooling, transactions, query builder) via `@aphrodite/database` |
| Cache               | Redis (`@aphrodite/cache`)                                                         |
| Event contracts     | `@oshun/contracts` (`libs/contracts/src/events/aphrodite.ts`)                      |
| Event publishing    | `@aphrodite/event-publisher` (publisher/subscriber over the shared bus)            |
| Streaming protocols | RTMP, WebRTC (WHIP/WHEP), HLS / LL-HLS, SRT — `@aphrodite/streaming-core`          |
| Object storage      | S3 / MinIO-compatible (`@aphrodite/storage`)                                       |
| Build / test        | tsup, Vitest                                                                       |

---

## Core Data Model

The authoritative persistence schema is
`libs/aphrodite/database/prisma/schema.prisma` (PostgreSQL,
`provider = "postgresql"`, `url = env("APHRODITE_DATABASE_URL")`). It defines
**20 models** and **22 enums**. Understanding this schema is the fastest way to
understand Aphrodite's domain model: every core entity — broadcasters, streams,
devices, tips, subscriptions — has a model here. Integer money/token fields are
stored as PostgreSQL `Int` (token counts) or `Int` cents (`price_usd_cents`,
`amount_cents`); `Recording.fileSize` is `BigInt`.

### Models

The 22 models fall into four natural groups: people (Broadcaster, Viewer),
streaming (Stream, PrivateShow, PrivateShowRequest, PrivateShowParticipant,
StreamSchedule, StreamGoal, Recording), financial (Tip, TipMenuItem,
TokenPurchase, Payout, Subscription, SubscriptionTier), and social/device
(Follow, Device, DeviceGroup, DeviceGroupMember, DeviceControl, ChatMessage,
ViewerBlock).

| Model                    | Table                       | Purpose                                                              |
| ------------------------ | --------------------------- | -------------------------------------------------------------------- |
| `Broadcaster`            | `broadcasters`              | Content creators; profile, verification, pricing, denormalised stats |
| `Viewer`                 | `viewers`                   | Watching users; token balance, verification, preferences             |
| `Stream`                 | `streams`                   | Live streaming sessions; technical URLs, viewer/earnings stats       |
| `PrivateShow`            | `private_shows`             | One-on-one / group / spy private sessions, per-minute billing        |
| `PrivateShowRequest`     | `private_show_requests`     | Viewer request to join a private show                                |
| `PrivateShowParticipant` | `private_show_participants` | Viewer participation record within a private show                    |
| `StreamSchedule`         | `stream_schedules`          | Scheduled (optionally recurring) stream times                        |
| `StreamGoal`             | `stream_goals`              | Tip goals with target/current amount                                 |
| `Recording`              | `recordings`                | Stream recordings / VOD assets                                       |
| `Tip`                    | `tips`                      | Token tips viewer → broadcaster, with fee breakdown                  |
| `TipMenuItem`            | `tip_menu_items`            | Predefined tip amounts with optional device actions                  |
| `TokenPurchase`          | `token_purchases`           | Real-money → token conversions                                       |
| `Payout`                 | `payouts`                   | Broadcaster earnings payouts, per period                             |
| `Subscription`           | `subscriptions`             | Viewer subscription to a broadcaster tier                            |
| `SubscriptionTier`       | `subscription_tiers`        | Broadcaster-defined subscription tiers                               |
| `Follow`                 | `follows`                   | Viewer follows broadcaster, with notification prefs                  |
| `Device`                 | `devices`                   | Interactive (teledildonic) devices owned by a broadcaster            |
| `DeviceGroup`            | `device_groups`             | Named cluster of devices for synchronised control                    |
| `DeviceGroupMember`      | `device_group_members`      | Device membership in a group, with intensity multiplier              |
| `DeviceControl`          | `device_controls`           | Viewer-issued device control events                                  |
| `ChatMessage`            | `chat_messages`             | Stream chat messages, with moderation flags                          |
| `ViewerBlock`            | `viewer_blocks`             | Broadcaster block of a viewer (temporary or permanent)               |

> Note: `Broadcaster` and `Viewer` each carry a unique `userId` referencing a
> platform user managed outside this schema. There is no `users` table in the
> Aphrodite Prisma schema.

### Key Model Shapes

The field lists below are verbatim from `schema.prisma` — use these as the
authoritative field reference when writing queries or building API payloads.
Note that `Broadcaster` and `Viewer` both carry denormalised counts
(`followerCount`, `totalEarnings`, `totalWatchTime`, etc.) for performance on
the hot read paths; these are updated asynchronously from event handlers, not
via live aggregation.

`Broadcaster` — `id`, `userId`, `stageName`, `bio?`, `avatarUrl?`, `bannerUrl?`,
`profileVideoUrl?`, `status` (`BroadcasterStatus`), `verificationStatus`,
`verifiedAt?`, `ageVerified`, `idVerified`, `isOnline`, `isStreaming`,
`lastSeenAt?`, `timezone`, `categories[]`, `tags[]`, `languages[]`,
`tokenPerMinute`, `privateShowPrice`, `tipMenuEnabled`, `followerCount`,
`subscriberCount`, `totalStreams`, `totalStreamMinutes`, `totalEarnings`,
`pendingPayout`, `lastPayoutAt?`, `createdAt`, `updatedAt`.

`Viewer` — `id`, `userId`, `displayName`, `avatarUrl?`, `ageVerified`,
`ageVerifiedAt?`, `status` (`ViewerStatus`), `isBanned`, `banReason?`,
`bannedUntil?`, `tokenBalance`, `lifetimePurchased`, `lifetimeSpent`,
`preferredCategories[]`, `showExplicit`, `notificationsEnabled`,
`totalWatchTime`, `totalTipped`, `createdAt`, `updatedAt`.

`Stream` — `id`, `broadcasterId`, `title`, `description?`, `thumbnailUrl?`,
`previewUrl?`, `status` (`StreamStatus`), `streamType` (`StreamType`),
`streamKey` (unique), `rtmpUrl?`, `hlsUrl?`, `webrtcUrl?`, `isExplicit`,
`categories[]`, `tags[]`, `ageRestricted`, `currentViewers`, `peakViewers`,
`totalViewers`, `totalTips`, `totalTokens`, `startedAt?`, `endedAt?`,
`duration?`, `devicesEnabled`, `deviceSensitivity`, `createdAt`, `updatedAt`.

`Tip` — `id`, `viewerId`, `broadcasterId`, `streamId?`, `amount`, `message?`,
`isAnonymous`, `tipMenuItemId?`, `deviceTriggered`, `deviceDuration?`,
`deviceIntensity?`, `platformFee`, `broadcasterShare`, `createdAt`.

`TokenPurchase` — `id`, `viewerId`, `tokenAmount`, `priceUsd`
(`price_usd_cents`), `currency`, `exchangeRate`, `paymentProvider`,
`paymentMethodType?`, `externalPaymentId?`, `status` (`PaymentStatus`),
`failureReason?`, `createdAt`, `completedAt?`.

`Payout` — `id`, `broadcasterId`, `amount` (`amount_cents`), `currency`,
`payoutMethod` (`PayoutMethod`), `externalPayoutId?`, `payoutDetails?` (`Json`),
`status` (`PayoutStatus`), `failureReason?`, `periodStart`, `periodEnd`,
`createdAt`, `processedAt?`, `completedAt?`.

`Device` — `id`, `broadcasterId`, `name`, `deviceType` (`DeviceType`),
`manufacturer?`, `model?`, `connectionType` (`DeviceConnectionType`),
`connectionStatus` (`DeviceConnectionStatus`), `deviceId?`, `lastConnectedAt?`,
`isActive`, `allowViewerControl`, `maxIntensity`, `defaultIntensity`,
`supportedPatterns[]`, `createdAt`, `updatedAt`.

`DeviceControl` — `id`, `deviceId`, `viewerId`, `controlType`
(`DeviceControlType`), `intensity`, `duration?`, `pattern?`, `sourceType`
(`DeviceControlSource`), `sourceTipId?`, `status` (`DeviceControlStatus`),
`executedAt?`, `createdAt`.

### Enums

The 22 Prisma enums define the valid state transitions and type classifications
throughout the domain. When adding a new status value, update the Prisma enum
and the corresponding Zod schema in `@aphrodite/database` together — both must
be kept in sync, and the Prisma enum is authoritative for stored data.

| Enum                       | Values                                                                      |
| -------------------------- | --------------------------------------------------------------------------- |
| `BroadcasterStatus`        | `PENDING`, `ACTIVE`, `SUSPENDED`, `BANNED`, `DELETED`                       |
| `VerificationStatus`       | `UNVERIFIED`, `PENDING`, `VERIFIED`, `REJECTED`                             |
| `ViewerStatus`             | `ACTIVE`, `SUSPENDED`, `BANNED`, `DELETED`                                  |
| `StreamStatus`             | `OFFLINE`, `STARTING`, `LIVE`, `PAUSED`, `ENDING`, `ENDED`                  |
| `StreamType`               | `PUBLIC`, `PRIVATE`, `TICKET`, `GROUP`, `FAN_CLUB`                          |
| `PrivateShowStatus`        | `PENDING`, `ACTIVE`, `ENDED`, `CANCELLED`                                   |
| `PrivateShowType`          | `ONE_ON_ONE`, `GROUP`, `SPY`                                                |
| `PrivateShowRequestStatus` | `PENDING`, `ACCEPTED`, `REJECTED`, `EXPIRED`                                |
| `ScheduleStatus`           | `SCHEDULED`, `LIVE`, `COMPLETED`, `CANCELLED`                               |
| `GoalStatus`               | `ACTIVE`, `COMPLETED`, `EXPIRED`, `CANCELLED`                               |
| `PaymentStatus`            | `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `REFUNDED`, `CANCELLED`     |
| `PayoutStatus`             | `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`                 |
| `PayoutMethod`             | `BANK_TRANSFER`, `PAYPAL`, `CRYPTO`, `CHECK`                                |
| `SubscriptionStatus`       | `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED`, `PAST_DUE`                      |
| `BillingCycle`             | `MONTHLY`, `QUARTERLY`, `YEARLY`                                            |
| `DeviceType`               | `VIBRATOR`, `ROTATOR`, `CONSTRICTION`, `STROKER`, `MULTI_FUNCTION`, `OTHER` |
| `DeviceConnectionType`     | `BLUETOOTH`, `WIFI`, `USB`, `NETWORK`                                       |
| `DeviceConnectionStatus`   | `DISCONNECTED`, `CONNECTING`, `CONNECTED`, `ERROR`                          |
| `DeviceControlType`        | `VIBRATE`, `ROTATE`, `CONSTRICT`, `PATTERN`, `STOP`                         |
| `DeviceControlSource`      | `TIP`, `TIP_MENU`, `SUBSCRIPTION`, `MANUAL`, `GOAL`                         |
| `DeviceControlStatus`      | `PENDING`, `EXECUTING`, `COMPLETED`, `FAILED`                               |
| `ChatMessageType`          | `TEXT`, `TIP`, `SYSTEM`, `EMOTE`, `STICKER`                                 |

### Zod Validation Layer

The Prisma schema defines the storage shape; the Zod schemas in
`@aphrodite/database` provide runtime validation of the data flowing in and out
of service endpoints. These are two separate but related artefacts — keep them
in sync when adding new fields.

`@aphrodite/database` (`libs/aphrodite/database/src/schemas/index.ts`,
`SCHEMA_VERSION = '0.2.0'`) provides runtime-validation Zod schemas for database
operations, covering users, streams, transactions, content items, photo sets,
content purchases/licenses, download tracking, subscriptions, subscription
perks, subscription billing, and fan-club posts/likes/comments. These schemas
are runtime guards layered over the Prisma model — where a Zod enum and the
Prisma enum differ, the Prisma schema is authoritative for stored data.

`@aphrodite/database` also exports a tenant-isolation layer
(`libs/aphrodite/database/src/isolation/index.ts`): `setTenantContext`,
`withTenantIsolation`, `withTenantRead`, `withAdminBypass`,
`validateBroadcasterOwnership`, `validateViewerOwnership`, `checkRecordAccess`,
`enforceTenantFilter`, plus PostgreSQL row-level-security policy generators
(`generateRLSPoliciesSQL`, `generateFullRLSSetupSQL`) and
`DEFAULT_ISOLATION_POLICIES`. A `BaseRepository` pattern (`REPOSITORY_VERSION`,
`createRepository`) is provided for data access.

---

## Domain Types and Constants (`@aphrodite/core`)

`@aphrodite/core` (`libs/aphrodite/core/src/`) is the foundational library that
every other Aphrodite library may depend on. It is the single source of truth
for shared types, platform constants, domain business logic, and limits. Any
change to a core type or constant can affect the entire domain — treat changes
to this library with extra care. It is organised into `types/`, `constants/`,
`domain/`, and `utils/`.

### Core Type Unions (`core/src/types/`)

These discriminated union types are used as literal type constraints throughout
the codebase. They define the valid values for roles, statuses, platforms, and
currencies. Using these types (rather than plain strings) ensures that
mismatched values are caught at compile time.

- `UserRole` — `viewer`, `broadcaster`, `studio`, `admin`, `moderator`
- `AccountStatus` — `active`, `suspended`, `banned`, `pending_verification`,
  `deactivated`
- `BroadcasterVerificationLevel` — `unverified`, `basic`, `verified`, `premium`,
  `exclusive`
- `AgeVerificationStatus` — `pending`, `verified`, `rejected`, `expired`
- `ContentRating` — `sfw`, `suggestive`, `sensual` (Aphrodite is capped at
  music-video-grade sensuality; the platform does not accept `nsfw` / `explicit`
  content. See `libs/aphrodite/core/src/domain/content-policy.ts` for the cap
  matrix.)
- `Platform` — `web`, `ios`, `android`, `vr`, `desktop`
- `CurrencyCode` — `USD`, `EUR`, `GBP`, `CAD`, `AUD`, `JPY`
- `PayoutInfo.method` — `bank_transfer`, `paypal`, `crypto`, `paxum`, `cosmo`

`core/types/common.ts` also defines the shared envelope types `ApiResponse<T>`
(`{ success, data?, error?, meta? }`), `PaginatedResponse<T>`,
`PaginationParams`, `Money`, and `GeoLocation`, each with a matching Zod schema.

### Platform Constants (`core/src/constants/platform.ts`)

These constants define the static configuration of the platform: the token
exchange rate, supported video quality levels, streaming protocols, content
categories, device brands, and VR platforms. Changing these constants changes
platform-wide behaviour, so they are centralised here rather than duplicated
across services.

- `TOKEN_EXCHANGE_RATE = 20` (20 tokens = $1); `MINIMUM_AGE = 18`
- `VIDEO_QUALITY_LEVELS` — `480p` (854×480, 1500 kbps), `720p` (1280×720, 3000),
  `1080p` (1920×1080, 6000), `4k` (3840×2160, 15000)
- `STREAMING_PROTOCOLS` — `rtmp`, `hls`, `webrtc`, `srt`
- `CONTENT_CATEGORIES` — `amateur`, `professional`, `couples`, `solo`, `group`,
  `fetish`, `roleplay`, `interactive`, `vr`, `gaming`, `asmr`, `fitness`, `art`,
  `music`, `talk`
- `SUPPORTED_DEVICE_BRANDS` — `lovense`, `kiiroo`, `ohmibod`, `we-vibe`,
  `handy`, `satisfyer`, `generic-buttplug`
- `PAYMENT_METHODS` — `credit_card`, `debit_card`, `paypal`, `crypto`,
  `gift_card`, `wire_transfer`
- `PAYOUT_METHODS` — `bank_transfer`, `paypal`, `paxum`, `cosmo_payment`,
  `crypto`, `check`
- `VR_PLATFORMS` — Oculus Quest / Quest 2 / Quest 3 / Rift, HTC Vive / Vive Pro,
  Valve Index, PSVR, PSVR2, Apple Vision Pro, Windows Mixed Reality
- `WS_EVENTS` — canonical client WebSocket event-name map (see
  [WebSocket Event Names](#websocket-event-names))
- `API_VERSIONS` — `v1`, `v2`; `CURRENT_API_VERSION = 'v1'`

### Platform Limits (`core/src/constants/limits.ts`)

Limits are enforced in service middleware and business logic rather than left to
each endpoint to implement independently. The table below lists the key limits.
When implementing a new feature that accepts user input, check this table and
apply the relevant limit — do not introduce ad hoc limits that diverge from
these constants.

Selected machine-enforceable limits:

| Group       | Limit                       | Value                                       |
| ----------- | --------------------------- | ------------------------------------------- |
| Stream      | `MAX_STREAM_DURATION_HOURS` | 24                                          |
| Stream      | `MAX_BITRATE_KBPS`          | 15000 (`MIN_BITRATE_KBPS` 500)              |
| Stream      | `STREAM_KEY_LENGTH`         | 48; rotation every 90 days                  |
| Stream      | concurrent viewers          | 1000 basic / 10000 verified / 50000 premium |
| Chat        | `MESSAGE_MAX_LENGTH`        | 500; 30 msg/min, 3 msg/s                    |
| Transaction | tips                        | 1–100000 tokens, 10 tips/min                |
| Transaction | token purchase              | $4.99–$500, $2000/day cap                   |
| Transaction | subscription price          | $2.99–$49.99                                |
| Transaction | `MIN_PAYOUT_USD`            | 50; processing 5 days                       |
| Device      | per user                    | 5 devices, 3 simultaneous                   |
| Device      | command                     | 0–100 intensity, 100–30000 ms, 60 cmd/min   |
| Goal        | amount                      | 100–1000000 tokens, 3 active                |

### Pricing Domain Logic (`core/src/domain/pricing.ts`)

The pricing module contains the pure functions that calculate platform
economics. These functions are intentionally side-effect-free so they can be
unit tested in isolation — they take inputs and return outputs with no database
calls. All earning calculations in the platform flow through this module to
ensure consistent fee application.

`DEFAULT_PLATFORM_FEES` — `baseFeePercent: 20`, `paymentProcessingPercent: 2.9`,
`chargebackFeeFlat: 15`, `minimumPayout: 50`, `withdrawalFee: 0`.

Pure functions: `calculateBroadcasterEarnings`, `calculateTokenValue`,
`calculateTipPayout` (default 20 % platform cut), `applyDiscount`,
`calculatePrivateShowCost`, `getBestPackageForTokens`, `calculateVipLevel`,
`convertCurrency` (uses `CURRENCY_RATES`). `DEFAULT_TOKEN_PACKAGES` (5 tiers,
Starter→Ultimate) and `DEFAULT_SUBSCRIPTION_TIERS` (Fan / Supporter / VIP) are
provided as catalogue defaults.

---

## Streaming Subsystem (`@aphrodite/streaming-core`)

`@aphrodite/streaming-core` (`libs/aphrodite/streaming-core/src/`) provides the
low-level streaming primitives that the `streaming` app builds on: protocol
handlers for WebRTC, HLS, and RTMP, plus a stream manager. Its `types.ts` is the
authoritative type source for all streaming-related types in the domain. If you
are working on the streaming service, start here.

### Protocols and Lifecycle

The streaming lifecycle moves through a well-defined state machine. A stream is
created (state: `created`), transitions through `starting` and `connecting` as
the ingest connection is established, becomes `live` when the video feed is
active, and transitions to `ended` when the session completes. The protocol enum
determines which delivery path is active for a given stream session.

- `StreamProtocol` — `webrtc`, `hls`, `ll-hls`, `rtmp`, `srt`, `whip`, `whep`
- `StreamState` — `created`, `idle`, `starting`, `connecting`, `publishing`,
  `live`, `playing`, `paused`, `stopping`, `stopped`, `ended`, `error`, `closed`
- `VideoCodec` — `h264`, `h265`, `vp8`, `vp9`, `av1`
- `AudioCodec` — `aac`, `opus`, `mp3`, `pcm`
- `ConnectionQuality` — `excellent`, `good`, `fair`, `poor`, `disconnected`
- `RecordingFormat` — `mp4`, `webm`, `mkv`, `flv`, `ts`, `hls`

### Quality Presets and Adaptive Bitrate

The quality ladder defines the encoding targets for transcoding and the steps
through which the ABR algorithm cycles in response to bandwidth changes. The
`QUALITY_PRESETS` object is the single source of truth for resolution and
bitrate targets — do not hardcode these values in service code.

`QUALITY_PRESETS` defines five ladder rungs: `1080p` (6000 kbps), `720p` (3000),
`480p` (1500), `360p` (800), `240p` (400). `DEFAULT_BITRATE_TIERS` defines six
adaptive tiers — `ultra` (8000 kbps, 1080p60), `high` (4500, 1080p30), `medium`
(2500, 720p30), `low` (1200, 480p30), `mobile` (600, 360p24), and `audio-only`
(128 kbps audio, no video). `createDefaultStreamConfig` yields a 720p / h264 /
opus / WebRTC low-latency config.

### Signalling and Rooms

WebRTC sessions require a signalling exchange before media can flow. The
signalling message types below model the offer/answer/ICE negotiation sequence.
The room model supports co-streaming: multiple broadcasters sharing a stream
session, with configurable layouts, role assignments, and revenue share.

WebRTC signalling messages are `OfferMessage`, `AnswerMessage`,
`IceCandidateMessage`, `JoinMessage`, `LeaveMessage`. Co-streaming rooms model
`Participant` (`ParticipantRole`: `host`, `co-host`, `guest`, `viewer`,
`moderator`), `BroadcasterSlot`, `RoomLayout` (`grid`, `spotlight`, `sidebar`,
`pip`, `custom`), `RoomState` (`waiting`, `live`, `paused`, `ended`), and a
`RevenueShareConfig` (`equal` / `weighted` / `custom`).

### Streaming Errors

`StreamingError` carries a typed `StreamingErrorCode` enum that categorises
failures by subsystem. Use these codes in error responses rather than free-form
messages so clients can programmatically react to specific failure modes (for
example, retrying on `CONNECTION_LOST` but surfacing a permission dialog on
`MEDIA_ACCESS_DENIED`).

`StreamingError` carries a `StreamingErrorCode` enum: connection
(`CONNECTION_FAILED`, `CONNECTION_TIMEOUT`, `CONNECTION_LOST`), media
(`MEDIA_ACCESS_DENIED`, `MEDIA_NOT_SUPPORTED`, `MEDIA_TRACK_ENDED`), WebRTC
(`WEBRTC_OFFER_FAILED`, `WEBRTC_ANSWER_FAILED`, `WEBRTC_ICE_FAILED`,
`WEBRTC_NEGOTIATION_FAILED`), room (`ROOM_NOT_FOUND`, `ROOM_FULL`,
`ROOM_CLOSED`, `NOT_AUTHORIZED`), stream (`STREAM_NOT_FOUND`,
`STREAM_ALREADY_LIVE`, `STREAM_KEY_INVALID`, `INGEST_FAILED`), and general
(`UNKNOWN_ERROR`, `INVALID_CONFIG`, `RATE_LIMITED`).

> The streaming-core library models DASH only as an optional `PlaybackUrls.dash`
> field; the implemented distribution path is HLS / LL-HLS / WebRTC.

---

## Payment Subsystem (`@aphrodite/payments`)

`@aphrodite/payments` (`libs/aphrodite/payments/src/`, `VERSION = '1.0.0'`) is
the fully-implemented payment library for the domain. It handles payment
processing, the token economy, tips, subscriptions, earnings, payouts, fraud
evaluation, and tax documents. It is organised into `processors/`, `tokens/`,
`tips/`, `subscriptions/`, `earnings/`, `fraud/`, `tax/`, and `webhooks/`.

Note: This is **distinct** from the scaffold `@aphrodite/payment` library — that
library exports types only. `@aphrodite/payments` (plural) is the real
implementation. See [Library Catalogue](#library-catalogue) for the full list.

### Payment Types (`payments/src/types.ts`)

The payment type system models the full lifecycle of a transaction: the
processor that handles it, the current status, the payment method, the webhook
events that confirm completion, and the payout destination. Amounts are always
in the smallest currency unit (cents) to avoid floating-point rounding errors in
financial calculations.

- `PaymentProcessorName` — `ccbill`, `epoch`, `segpay`, `stripe`, `custom`
- `PaymentStatus` — `pending`, `processing`, `completed`, `failed`, `refunded`,
  `cancelled`
- `PaymentMethodType` — `card`, `crypto`, `wire`, `ach`, `paypal`, `other`
- `WebhookEventType` — `payment.completed`, `payment.failed`,
  `payment.refunded`, `subscription.created`, `subscription.renewed`,
  `subscription.cancelled`, `subscription.expired`, `chargeback.initiated`,
  `chargeback.reversed`
- `PayoutMethod` / `PayoutProvider` — payout method details model ACH, wire,
  PayPal, and crypto payout destinations (`AchPayoutDetails`,
  `WirePayoutDetails`, `PaypalPayoutDetails`, `CryptoPayoutDetails`)

The `IPaymentProcessor` interface defines the processor contract:
`buildCheckoutUrl`, `verifyWebhookSignature`, `parseWebhookPayload`,
`processRefund`, `getTransactionStatus`. Amounts in `CheckoutParams` are
expressed in the smallest currency unit (cents).

### Token Ledger

Tokens are tracked with an append-only ledger rather than a mutable balance
field. This ensures auditability: every token movement is a ledger entry with a
type and source, making it possible to reconstruct any balance at any point in
time. Holds (reservations against a balance) allow the platform to reserve
tokens for a pending private show without deducting them until the show
completes.

Tokens are tracked with a ledger model — `LedgerEntry`, `LedgerEntryType`,
`LedgerSource`, `TokenBalance`, and `TokenHold` (reservations against a
balance). Tips model `Tip`, `TipMenuItem`, `TipAction` / `TipActionType`,
`TipGoal`, and `TipLeaderboardEntry`. Earnings model `EarningsEntry`,
`EarningsSource`, `EarningsSummary`, and a `RevenueShareConfig`.

---

## Application Surface

All 17 Aphrodite apps (`apps/aphrodite/*`) are Hono services. Each registers a
`/health` route group and uses a standard Hono middleware stack: `requestId`,
`logger`, `timing`, `secureHeaders`, `cors` (the viewer app additionally adds
`compress` for response compression). API route groups are listed below as
**registered route prefixes** — the live source for each is
`apps/aphrodite/<app>/src/routes/`. Endpoint paths combine the prefix with the
route declared in the corresponding route file.

The auth middleware on the streaming, viewer, and payment apps currently carries
placeholder JWT and API-key validation (commented `TODO`). Real production
authentication is not yet wired — treat these endpoints as not
production-secured until that is resolved.

The table lists the route group prefixes registered in each app's `app.ts`.
Individual endpoint paths (e.g., `POST /api/streams/:streamId/start`) are in the
route files under `src/routes/`.

| App                   | Registered API route groups                                                                                                                               |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `streaming`           | `/api/streams`, `/api/ingest`, `/api/transcoding`, `/api/distribution`, `/api/recording`, `/api/thumbnails`, `/api/quality`, `/api/discovery`, `/metrics` |
| `viewer`              | `/api/streams`, `/api/chat`, `/api/tips`, `/api/following`, `/api/settings`, `/api/devices`, `/api/shows`, `/api/history`, `/api/notifications`           |
| `broadcaster`         | `/v1/streams`, `/v1/profile`, `/v1/settings`, `/v1/analytics`, `/v1/devices`, `/v1/shows`                                                                 |
| `payment`             | `/api/v1/tokens`, `/api/v1/tips`, `/api/v1/subscriptions`, `/api/v1/payouts`, `/api/v1/transactions`, `/api/v1/webhooks`, `/api/v1/earnings`              |
| `chat`                | `/api/v1/rooms`, `/api/v1/messages`, `/api/v1/moderation`, `/api/v1/dm`, `/api/v1/settings`                                                               |
| `devices`             | `/api/v1/devices`, `/api/v1/patterns`, `/api/v1/triggers`                                                                                                 |
| `auth`                | `/auth`, `/auth/2fa`                                                                                                                                      |
| `notifications`       | `/api/v1/notifications`                                                                                                                                   |
| `analytics`           | `/api/v1/events`, `/api/v1/dashboard`                                                                                                                     |
| `vr`                  | `/api/v1/capture`, `/api/v1/playback`                                                                                                                     |
| `admin`               | `/settings`, `/admin`                                                                                                                                     |
| `admin-bi-dashboard`  | dashboard / BI route groups (`src/routes/`)                                                                                                               |
| `analytics-dashboard` | analytics route groups (`src/routes/`)                                                                                                                    |
| `cdn`                 | CDN origin / segment route groups (`src/routes/`)                                                                                                         |
| `realtime-analytics`  | real-time analytics pipeline (no HTTP route group; `src/` worker)                                                                                         |
| `mobile-broadcaster`  | React Native broadcaster client (`src/`, no HTTP route group)                                                                                             |
| `mobile-viewer`       | React Native viewer client (`src/`, no HTTP route group)                                                                                                  |

### Streaming App Endpoints

The streaming app is the most complex service. It manages the full stream
lifecycle (create → start → live → stop), RTMP ingest hooks, WebRTC signalling,
recording, transcoding, and content discovery. The endpoints below are the
currently registered routes in `apps/aphrodite/streaming/src/routes/`.

The `/api/streams` group exposes: `POST /` (create, `CreateStreamSchema`),
`GET /:streamId`, `PATCH /:streamId`, `DELETE /:streamId`,
`POST /:streamId/state`, `POST /:streamId/start`, `POST /:streamId/stop`,
`POST /:streamId/key`, `POST /:streamId/key/rotate`, `POST /:streamId/auth`,
`GET /:streamId/stats`, `GET /:streamId/viewers`.

`/api/ingest` exposes RTMP hooks (`POST /rtmp/auth`, `POST /rtmp/on-publish`,
`POST /rtmp/on-publish-done`, `GET /rtmp/endpoints`) and WebRTC signalling
(`POST /webrtc/offer`, `POST /webrtc/candidate`, `GET /webrtc/:streamId/state`,
`POST /webrtc/:streamId/disconnect`, `GET /webrtc/capabilities`), plus ingest
settings and health. `/api/recording` covers recording lifecycle, DVR config,
segment access, and VOD export. `/api/transcoding` manages transcoding jobs,
profiles, workers, and metrics. `/api/discovery` covers search, live, trending,
featured, categories, tags, recommendations, and geo discovery.

### Payment App Endpoints

The payment app handles all financial operations: token purchases, tips,
subscriptions, and payouts. Its endpoints follow a consistent
`/api/v1/<resource>` structure and validate all input with Zod schemas. The
source is at `apps/aphrodite/payment/src/routes/`.

`/api/v1/tokens` exposes token packages, balance and history,
`POST /purchase/initiate` and `/purchase/confirm`, `POST /transfer`, token holds
(`POST /holds`, `DELETE /holds/:holdId`, `GET /holds/:userId`), `POST /refund`,
and `GET /rate`. `/api/v1/tips` exposes `POST /` (send tip), sent/received
history, tip-menu CRUD, tip-goal CRUD, and stream / creator leaderboards.
`/api/v1/subscriptions` exposes tier CRUD, tier perk CRUD, `POST /subscribe`,
user/creator subscription listing, and billing retry. `/api/v1/payouts` exposes
payout-method CRUD, schedule, payout requests, verification, and tax documents
(`GET /:userId/tax/1099`).

---

## Events

The Aphrodite event contract defines every cross-service notification the domain
emits. Other services (and external consumers) react to these events rather than
polling Aphrodite endpoints directly. All events flow through the shared event
bus via `@aphrodite/event-publisher`.

The authoritative event contract is `libs/contracts/src/events/aphrodite.ts`
(`@oshun/contracts`). It defines **22 events** across six categories — stream
lifecycle, transactions, user lifecycle, device state, chat, moderation, and
content publishing — each with a Zod payload schema wrapped by
`createEventSchema(<topic>, 'aphrodite', <payloadSchema>)`.
`@aphrodite/event-publisher` publishes and subscribes to these topics over the
shared event bus.

The event table below lists every topic and its key payload fields. Note that
the event `StreamType` and `StreamStatus` enums use lowercase forms (e.g.,
`public`, `private`) rather than the uppercase Prisma enum values — this is a
known distinction between the contract and the database model.

| Event topic                                    | Payload (key fields)                                                                                                           |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `aphrodite.stream.started`                     | `streamId`, `broadcasterId`, `title`, `type` (`StreamType`), `categories[]`, `tags[]`, `isRecording`, `deviceConnected`        |
| `aphrodite.stream.ended`                       | `streamId`, `broadcasterId`, `duration`, `peakViewers`, `totalViewers`, `totalTips`, `newSubscribers`, `recordingUrl?`         |
| `aphrodite.stream.viewer_count_updated`        | `streamId`, `broadcasterId`, `viewerCount`, `peakViewerCount`, `anonymousCount`, `registeredCount`, `premiumCount`             |
| `aphrodite.stream.goal_reached`                | `streamId`, `broadcasterId`, `goalId`, `title`, `targetAmount`, `totalAmount`, `contributorCount`                              |
| `aphrodite.transaction.tip_received`           | `transactionId`, `streamId`, `senderId?`, `senderName`, `recipientId`, `amount`, `message?`, `isAnonymous`, `triggerDevice`    |
| `aphrodite.transaction.tokens_purchased`       | `transactionId`, `userId`, `amount`, `packageId`, `paymentMethod`, `currency`, `price`, `bonusTokens?`                         |
| `aphrodite.transaction.subscription_created`   | `subscriptionId`, `subscriberId`, `broadcasterId`, `tierId`, `tierName`, `price`, `period`, `isRenewal`, `expiresAt`           |
| `aphrodite.transaction.subscription_cancelled` | `subscriptionId`, `subscriberId`, `broadcasterId`, `reason?`, `cancelledAt`, `expiresAt`                                       |
| `aphrodite.transaction.payout_requested`       | `payoutId`, `broadcasterId`, `amount`, `currency`, `method`, `estimatedArrival?`                                               |
| `aphrodite.transaction.payout_completed`       | `payoutId`, `broadcasterId`, `amount`, `currency`, `method`, `transactionRef`, `completedAt`                                   |
| `aphrodite.user.registered`                    | `userId`, `username`, `email`, `role`, `registrationMethod`, `referredBy?`                                                     |
| `aphrodite.user.verified`                      | `userId`, `verificationType` (`email`/`age`/`identity`/`broadcaster`), `verifiedAt`, `verificationDetails?`                    |
| `aphrodite.user.followed`                      | `followerId`, `followedId`, `notificationsEnabled`                                                                             |
| `aphrodite.user.banned`                        | `userId`, `bannedBy`, `reason`, `duration?`, `expiresAt?`                                                                      |
| `aphrodite.device.connected`                   | `deviceId`, `broadcasterId`, `deviceType`, `deviceName`, `protocol`, `firmwareVersion?`, `batteryLevel?`                       |
| `aphrodite.device.control_sent`                | `deviceId`, `broadcasterId`, `streamId`, `command` (`DeviceCommand`), `intensity`, `duration?`, `pattern?`, `triggeredBy?`     |
| `aphrodite.device.state_updated`               | `deviceId`, `broadcasterId`, `connected`, `batteryLevel?`, `intensity`, `currentPattern?`                                      |
| `aphrodite.chat.message_sent`                  | `messageId`, `streamId`, `senderId`, `senderName`, `content`, `isModerator`, `isPremium`, `badges?`                            |
| `aphrodite.chat.user_muted`                    | `streamId`, `userId`, `mutedBy`, `reason?`, `duration?`, `expiresAt?`                                                          |
| `aphrodite.moderation.content_flagged`         | `contentId`, `contentType`, `streamId?`, `reporterId?`, `reason`, `category`, `severity`, `autoDetected`                       |
| `aphrodite.moderation.content_reviewed`        | `contentId`, `contentType`, `reviewerId`, `decision`, `reason?`, `actionsTaken?`, `reviewedAt`                                 |
| `aphrodite.content.recording_ended`            | `recordingId`, `streamId`, `broadcasterId`, `duration`, `fileSize`, `storageUrl`, `thumbnailUrl?`                              |
| `aphrodite.content.vod_published`              | `vodId`, `streamId`, `broadcasterId`, `title`, `description?`, `duration`, `thumbnailUrl`, `playbackUrl`, `visibility`         |
| `aphrodite.content.clip_created`               | `clipId`, `streamId`, `creatorId`, `broadcasterId`, `title`, `startTime`, `endTime`, `duration`, `thumbnailUrl`, `playbackUrl` |

Supporting enums in the contract: `StreamType` (`public`, `private`, `ticket`,
`group`, `fan_club` — lowercase form of the Prisma enum), `StreamStatus`
(`initializing`, `live`, `paused`, `ending`, `ended`, `error`), `DeviceCommand`
(`vibrate`, `rotate`, `stop`, `pattern`), `DeviceTriggerType` (`tip`,
`subscription`, `manual`). `content_flagged.category` is one of `illegal`,
`underage`, `non_consensual`, `violence`, `spam`, `other`.

### WebSocket Event Names

WebSocket events are client-facing — they travel between the server and the
viewer or broadcaster client over a WebSocket connection, as opposed to the
Kafka bus events above which travel between services. `@aphrodite/core`
(`core/src/constants/platform.ts`) defines the `WS_EVENTS` map as the canonical
name registry. Use these constants rather than string literals when sending or
subscribing to WebSocket events. Names use a `domain:action` form:

- Connection — `connect`, `disconnect`, `error`, `reconnect`
- Stream — `stream:start`, `stream:end`, `stream:update`, `viewer:join`,
  `viewer:leave`, `viewer:count`
- Chat — `chat:message`, `chat:delete`, `chat:clear`, `chat:timeout`, `chat:ban`
- Transaction — `tip:received`, `goal:progress`, `goal:complete`
- Device — `device:connect`, `device:disconnect`, `device:command`
- Notification — `notification`
- Private show — `private:request`, `private:start`, `private:end`

---

## Interactive Devices

Device integration connects physical haptic hardware to the platform's event
system. The data model is grounded in the Prisma `Device` / `DeviceGroup` /
`DeviceControl` models; real-time device state flows through the
`aphrodite.device.*` events; and the protocol implementation lives in the device
libraries. Implemented device libraries include `@aphrodite/buttplug`
(Buttplug.io protocol client wrapper), `@aphrodite/haptics`,
`@aphrodite/device-protocols`, `@aphrodite/device-safety` (intensity caps,
safe-word kill switch), `@aphrodite/device-reaction-engine`,
`@aphrodite/audio-haptics`, `@aphrodite/motion-haptics`,
`@aphrodite/bidirectional-haptics`, `@aphrodite/sensation-bus`,
`@aphrodite/smart-home`, and `@aphrodite/wearables`.

Verified rules from code:

- A device's `maxIntensity` (default 100) bounds all viewer-issued intensity.
  `DEVICE_LIMITS` in `@aphrodite/core` caps intensity to `0–100`, command
  duration to `100–30000 ms`, and 60 commands/minute.
- `DeviceControl.sourceType` records control provenance: `TIP`, `TIP_MENU`,
  `SUBSCRIPTION`, `MANUAL`, or `GOAL`; `sourceTipId` links a tip-triggered
  control to its `Tip`.
- The `devices` app exposes `POST /api/v1/devices/:deviceId/command`,
  `POST /api/v1/devices/stop-all` (safe-word all-stop), and device-brand pairing
  routes for Lovense, Kiiroo, Handy, and Buttplug.

---

## Compliance Subsystem

The compliance subsystem is legally critical infrastructure. Failures in 2257
record-keeping carry criminal penalties; failures in age verification expose the
platform to regulatory action; failures in consent tracking undermine the
platform's liability protection. These libraries are not optional features —
they are load-bearing legal infrastructure.

### 18 U.S.C. 2257 (`@aphrodite/compliance-2257`)

`@aphrodite/compliance-2257` (`libs/aphrodite/compliance-2257/src/`) provides a
service-oriented implementation: `performer-service`, `content-service`,
`custodian-service`, `inspection-service`, and a top-level `compliance-service`,
each with its own types. It supports performer ID document handling, custodian
of records designation, record indexing by content ID, and inspection-request
workflows.

### Age verification (`@aphrodite/age-verification`)

`@aphrodite/age-verification` (`libs/aphrodite/age-verification/src/`)
implements multi-provider age/identity verification. **Implemented providers:
Yoti, Veriff, Stripe Identity**, plus a `mock` provider for testing, all behind
a `base` provider abstraction and a provider registry. The
`AgeVerificationService` covers verification start, session management, and
audit logging, with a compliance sub-module addressing UK Online Safety Act, EU
DSA, US state laws, and COPPA.

### Consent (`@aphrodite/consent-engine`)

`@aphrodite/consent-engine` (`libs/aphrodite/consent-engine/src/`) implements a
graduated **traffic-light consent model** — `ConsentLevel`: `green`, `yellow`,
`red`. `LimitType` is `hard`, `soft`, or `enthusiastic_yes`. `ActivityCategory`
covers `device_control`, `visual_effects`, `viewer_interaction`,
`audio_effects`, `intensity_modulation`, and more. A `ConsentEvent` enum
(prefixed `consent:`) covers profile changes, level transitions, activity
enable/disable, panic activation/clear, consent decay/expiry, check-ins, and
state-machine violation events. The consent state machine blocks any system from
exceeding declared limits and audit-logs every state change.

### V2 Game-Platform Contracts

Several Aphrodite compliance libraries expose stable, cross-domain contracts
consumed by the separate **V2** game-platform program. V2 is a mature-rated
fighting-game platform, not an adult-content platform, so it reuses Aphrodite's
compliance primitives under a narrowed profile rather than forking them. Each
contract is implemented in an Aphrodite `libs/aphrodite/*` package and composed
by a thin V2 service under `apps/v2/aphrodite-*`. Every contract is
backend-only, hides the Aphrodite brand from players, and carries
`mayInfluenceRollback: false` so age, consent, and likeness decisions can never
perturb the deterministic match simulation. The subsections below document each
contract as a producer/consumer pair: what V2 consumes, the real package and
function names, the inputs/outputs, and the safety posture.

#### V2 Age-Gate Driver Contract

The **V2 Age-Gate Driver Contract** lets `@aphrodite/age-verification` act as
the backend driver for V2's content age gate, consumed by the
`@v2/aphrodite-age-gate` service. The driver function
`buildV2AgeGateContentDecision` (`age-verification/src/v2-age-gate-driver.ts`,
driver id `aphrodite.age-verification.v2-age-gate-driver`) takes the player's
account id, country/region code, verification status and level, an estimated
age, the requested **gore tier**, requested adult-fatality flag, and the list of
requested cinematic ids; it resolves the per-region rule and returns a decision
that governs three controlled surfaces — the effective gore tier, the
**adult-fatality enablement** flag, and the per-cinematic gate actions for
**region-conditional cinematics**. The default region table encodes the launch
markets (`US`, `GB`, `DE`, `AU`, `NZ`, `KR`, `CN`, and a `GLOBAL` fallback) with
their minimum ages, required verification levels, maximum gore tier, whether
adult fatalities are allowed, and the cinematic policy (`full`, `softened`, or
`removed`). The decision sets `replacesStandaloneV2AgeGate: true`,
`ageVerificationBackendOnly: true`, and `mayInfluenceRollback: false`. The V2
service publishes `v2.age-gate.decision.created`,
`v2.content.gore-tier.changed`, and `v2.cinematic.region-gated` events from this
output. Documented at `V2/docs/integration/aphrodite-age-gate.md`.

#### V2 Per-Feature Consent Surface Contract

The **V2 Per-Feature Consent Surface Contract** lets `@aphrodite/consent-engine`
own V2's per-feature privacy consent, consumed by the
`@v2/aphrodite-consent-surfaces` service. `buildV2FeatureConsentSurface`
(`consent-engine/src/v2-feature-consent-surfaces.ts`, surface id
`aphrodite.consent-engine.v2-feature-consent-surfaces`) takes the subject id,
region code, and the player's recorded consent grants, then returns a receipt-
backed decision (`enabled`, `limited`, or `disabled`) for each of three
controlled features: telemetry, voice processing, and behavioral profiling.
Telemetry defaults to a `limited`, privacy-preserving fallback; voice processing
and behavioral profiling require explicit opt-in and default to `disabled`. Each
decision lists the allowed data classes, the data classes redacted by the
privacy-preserving fallback, and the retention window, and references the
consent receipt that authorised it. The surface sets
`consentReceiptsRequired: true`, `privacyPreservingByDefault: true`, and
`mayInfluenceRollback: false`, and records that the deprecated
`@aphrodite/consent` package is absent. The V2 service emits
`v2.consent.surface.evaluated`, `v2.telemetry.consent.changed`,
`v2.voice-processing.consent.changed`, and
`v2.behavioral-profiling.consent.changed`.

#### V2 Licensed-Fighter Likeness Safety + Revocation Contract

The **V2 Licensed-Fighter Likeness Safety + Revocation Contract** is composed
from two Aphrodite packages — `@aphrodite/performer-sovereignty` and
`@aphrodite/performer-autonomy` — and consumed by the
`@v2/aphrodite-licensed-likeness-safety` service. The safety gate
`buildAphroditeV2LicensedFighterLikenessSafetyGate`
(`performer-sovereignty/src/v2-licensed-fighter-likeness-safety.ts`) takes a
fighter id, a requested likeness surface (playable character, cinematic cameo,
cosmetic skin, voice-line bank, commentary mention, esports broadcast, or
creator-suite upload), and a `@themis/likeness` ledger stamp; it allows the use
only when the ledger status is `active`, the rights manifest matches, the
performer has opted in via sovereignty, and the surface is on the
performer-approved list. On any failure it returns `blocksLikenessSurface` and
`blocksBellonaCook`, refusing the downstream Bellona cook. The revocation flow
`buildAphroditeV2LikenessRevocationFlow`
(`performer-autonomy/src/v2-likeness-revocation-flow.ts`) emits
`aphrodite.performer-autonomy.likeness.revoked`, mirrors the upstream
`themis.license.revoked` event, blocks the Bellona cook, and notifies the Themis
likeness ledger, Kuan-yin performer protection, and the Aja consent/NIL ledger.
Both contracts carry `mayInfluenceRollback: false` and record that the
deprecated `@aphrodite/performer-safety` package is absent; the V2 service takes
a workspace dependency on the two Aphrodite packages but never on the
forthcoming `@themis/likeness` package directly. The V2 service emits
`v2.likeness.safety.evaluated`, `v2.likeness.revocation.requested`, and
`v2.bellona.cook.likeness.blocked`.

#### V2 Age-Gate Primitive-Fitness Audit

The **V2 Age-Gate Primitive-Fitness Audit** is the design record explaining why
V2 reuses the Aphrodite age primitive rather than promoting a new identity
primitive. `buildV2AgeGatePrimitiveFitnessAudit`
(`age-verification/src/v2-age-gate-driver.ts`, audit id
`aphrodite.age-verification.v2-primitive-fitness-audit`) returns the verdict
"fit with V2 mature-game profile" and the decision to _parameterize the
Aphrodite primitive_ under the `mature-rated-fighting-game` profile (ESRB Mature
17+ default with 18+ regional variants). It sets
`promoteOshunIdentityPrimitive: false` — V2 deliberately does **not** promote a
new shared primitive into `@oshun/identity`, because the existing Aphrodite
primitive already covers provider selection, region rules, retry/lockout, and
audit logging. The audit rejects the adult-content default profile
(`adultContentDefaultRejected: true`) and asserts that 2257 record-keeping,
performer-protection, and anti-CSAM performer workflows are _not_ required for
V2 game-content gating (`record2257Required: false`,
`performerProtectionWorkflowRequired: false`,
`antiCsamPerformerWorkflowRequired: false`), while parental-control hand-off to
platform family controls _is_ required (`parentalControlHandOffRequired: true`).
Documented at `V2/docs/integration/age-gate-fitness-audit.md`.

#### V2 Regional Content Rules Inputs

The **V2 Regional Content Rules Inputs** contract is the one cross-domain
boundary that does _not_ live in Aphrodite. The regional cook engine is owned by
shared infrastructure as `@oshun/region-rules`
(`libs/shared/region-rules/src/v2-regional-content-rules.ts`), not as a
hypothetical `@aphrodite/regional-content-rules` package — that Aphrodite-owned
alias was rejected during the owner decision so that region cook plans stay a
neutral shared service rather than an Aphrodite-domain dependency.
`buildV2RegionalContentCookPlan` resolves a per-country SKU cook profile for
`CN`, `DE`, `AU`, `NZ`, and `KR`, applying cuts such as `remove-blood`,
`recolor-red-blood`, `soften-finisher-cinematics`, and
`require-finisher-preclearance`. It consumes the Aphrodite age-gate and consent
outputs as _inputs_ (requested gore tier, adult-fatality allowance, and whether
consent permits adult content) but treats them as advisory; the engine always
applies the **China most-restrictive default** when a region is unknown
(`missing-region-default-most-restrictive`,
`defaultProfileIsMostRestrictive: true`). Like the other contracts it carries
`mayInfluenceRollback: false`. Documented at
`V2/docs/integration/region-rules.md`.

---

## Configuration Reference

Aphrodite services are configured through environment variables. There is no
central config file — operational defaults are expressed as code constants in
`@aphrodite/core` (see
[Platform Constants](#platform-constants-coresrcconstantsplatformts) and
[Platform Limits](#platform-limits-coresrcconstantslimitsts)), and the only
required external variable is the database connection string.

Configuration is sourced from environment variables. The single required
database variable is `APHRODITE_DATABASE_URL` (consumed by `schema.prisma` and
`@aphrodite/database`). App services additionally read `ALLOWED_ORIGINS`,
`NODE_ENV`, and (streaming) `INTERNAL_API_KEY` from the environment. Operational
defaults are expressed as code constants rather than a single config object:

- Platform fees — `DEFAULT_PLATFORM_FEES` in `@aphrodite/core` (20 % base fee,
  2.9 % processing, $50 minimum payout).
- Token economy — `TOKEN_EXCHANGE_RATE = 20` tokens/USD;
  `DEFAULT_TOKEN_PACKAGES`.
- Stream / chat / device / transaction limits — the constant groups in
  `core/src/constants/limits.ts` (see
  [Platform limits](#platform-limits-coresrcconstantslimitsts)).
- Streaming defaults — `createDefaultStreamConfig`, `DEFAULT_BITRATE_TIERS`,
  `QUALITY_PRESETS` in `@aphrodite/streaming-core`.
- Rate limiting — each app's `app.ts` defines an in-memory limiter
  (`RATE_LIMIT_MAX` 100 for the viewer app, 1000 for the streaming service, over
  a 60-second window).

---

## Library Catalogue

The full set of 190 `libs/aphrodite/*` directories grouped by functional area.
Unless flagged otherwise, each library has a substantial `src/` implementation.
Only the four **scaffold** libraries (`chat`, `payment`, `safety`, `sdk`) are
not behaviourally implemented. This catalogue is the index — for detailed
per-library API documentation, see the library's own `src/` and `README.md`.

| Area                              | Libraries                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Platform / infrastructure**     | `core`, `database`, `cache`, `storage`, `event-publisher`, `testing`, `platform-accessibility`, `platform-analytics`                                                                                                                                                                                                                                                                                                                                      |
| **Scaffold** (`types.ts` only)    | `chat`, `payment`, `safety`, `sdk` — functional subdirectories are placeholders pending service migration                                                                                                                                                                                                                                                                                                                                                 |
| **Streaming**                     | `streaming-core`, `multi-source`, `stream-upscaling`, `stream-watermarking`, `stream-content-analysis`, `cloud-recording`, `highlight-clipping`, `thumbnail-generation`, `vod-chaptering`, `spatial-audio`                                                                                                                                                                                                                                                |
| **Devices / haptics**             | `buttplug`, `haptics`, `bidirectional-haptics`, `audio-haptics`, `motion-haptics`, `device-protocols`, `device-safety`, `device-reaction-engine`, `sensation-bus`, `smart-home`, `wearables`                                                                                                                                                                                                                                                              |
| **VR / AR / avatars**             | `vr-core`, `vr-haptics`, `vr-quest`, `eye-tracking`, `volumetric-capture`, `gaussian-splatting`, `ar-overlays`, `viewer-avatars`, `avatar-renderer`, `avatar-motion`, `avatar-physics`, `avatar-customization-ui`, `animation-blending`                                                                                                                                                                                                                   |
| **Chat / interaction**            | `chat-core`, `chat-moderation`, `chat-entertainment`, `tip-acknowledgment`, `tip-effects`, `tip-goals`, `virtual-gifts`, `viewer-interactions`, `crowd-control`, `interactables`                                                                                                                                                                                                                                                                          |
| **Payments / monetisation**       | `payments`, `crypto-payments`, `privacy-coins`, `revenue-sharing`, `ppv`, `nft-integration`, `treasury`, `gamification`, `growth`                                                                                                                                                                                                                                                                                                                         |
| **Compliance / safety**           | `compliance`, `compliance-2257`, `age-verification`, `gdpr-compliance`, `content-takedown`, `drm`, `recording-prevention`, `e2e-encryption`, `consent-engine`, `performer-autonomy`, `performer-sovereignty`, `safety-intelligence`, `ethics`, `legal`, `regulatory`, `body-dignity`, `risk-crisis`                                                                                                                                                       |
| **AI / agents**                   | `ai-assistant`, `recommendations`, `notification-intelligence`, `scouting-agents`, `research-agents`, `agent-platform`, `content-tagging`, `market-intelligence`                                                                                                                                                                                                                                                                                          |
| **Performer economy**             | `career`, `talent`, `property`, `partnerships`, `production`, `multi-performer`, `performer-presence`, `performer-camera`, `performer-compositor`, `remote-guest`, `audience-experience`, `technical-direction`                                                                                                                                                                                                                                           |
| **Wellbeing**                     | `aftercare`, `aftercare-engine`, `arousal-engine`, `wellness`, `meditation`, `reaction-enhancement`, `neuro-design`                                                                                                                                                                                                                                                                                                                                       |
| **Music / audio**                 | `advanced-music`, `ai-music`, `music-force`, `cinematic-music-engine`, `ai-orchestration`, `composition-engine`                                                                                                                                                                                                                                                                                                                                           |
| **Cinematic production**          | `auteur-camera`, `cinematic-camera`, `lighting-director`, `scene-composer`, `neural-rendering`, `vfx-particles`, `vfx-post-processing`, `vfx-volumetric`, `video-effects`, `animation-triggers`, `animations`, `drone-integration`                                                                                                                                                                                                                        |
| **Immersive / cultural / sacred** | `venue-management`, `immersive-theater`, `ceremony`, `ritual-engine`, `cultural-heritage`, `sacred-dance`, `sacred-architecture`, `art-traditions`, `goddess-traditions`, `archetype-engine`, `symbol-engine`, `literary-engine`, `procedural-art`, `abstract-scenes`, `scene-abstract`, `scene-cosmic`, `scene-fantasy`, `scene-goddess`, `scene-nature`, `scene-urban`                                                                                  |
| **Identity / movement / fashion** | `face-identity`, `biometric-engine`, `movement-engine`, `body-art`, `fashion`, `digital-couture`, `vip-areas`, `virtual-rooms`                                                                                                                                                                                                                                                                                                                            |
| **Embedded games**                | `game-runtime`, `game-ecs`, `game-event-bus`, `game-physics`, `game-input`, `game-renderer`, `game-assets`, `game-audio`, `game-networking`, `game-monetization`, `game-performance`, `game-accessibility`, `game-testing`, `game-dev-tools`; genre templates `game-fps`, `game-rpg`, `game-fighting`, `game-platformer`, `game-puzzle`, `game-rhythm`, `game-horror`, `game-strategy`, `game-sports`, `game-racing`, `game-openworld`, `game-simulation` |
| **Domain bridges**                | `maya-bridge`, `hathor-game-bridge`, `nyx-game-bridge`, `isis-game-bridge`, `lilith-game-bridge`, `nike-game-bridge`, `oauth-providers`                                                                                                                                                                                                                                                                                                                   |

> Library directories beyond the verified cross-cutting surface contain feature
> code but are not all wired into a deployed `apps/aphrodite/*` service. Service
> wiring status should be confirmed per library before integration.

---

## Cross-Domain Integration Points

The table below lists every verified cross-domain dependency. These are the
boundaries where Aphrodite's data and events cross into other domains or consume
other domains' services. As with the architecture doc, only dependencies
confirmed in `package.json` files are listed here.

| System             | Direction             | Purpose                                                                                      |
| ------------------ | --------------------- | -------------------------------------------------------------------------------------------- |
| `@oshun/database`  | aphrodite → shared    | PostgreSQL pooling, transactions, query builder (via `@aphrodite/database`)                  |
| `@oshun/contracts` | aphrodite → contracts | Event envelope schemas, the `aphrodite.*` event catalogue                                    |
| Shared event bus   | aphrodite → shared    | Event publication via `@aphrodite/event-publisher`                                           |
| V2 program         | aphrodite → V2        | Age-gate, consent-surface, and licensed-likeness contracts consumed by `apps/v2/aphrodite-*` |

> Earlier draft documentation referenced `@oshun/auth`, `@oshun/storage`,
> `@oshun/websocket`, `@oshun/metrics`, `@oshun/rate-limit`, `@aje/*`, and
> `@lilith/llm` as Aphrodite dependencies. Those are not present in the verified
> `package.json` files of the apps and core libraries reviewed; the apps depend
> on `@aphrodite/core`, `@aphrodite/database`, domain-specific Aphrodite
> libraries, and `hono` / `@hono/zod-validator`. Treat additional cross-domain
> wiring as unverified until confirmed in a `package.json`.

---

## Acceptance Criteria

Before merging a change to the Aphrodite domain, verify all seven criteria
below. These criteria are not a checklist to rush through — each one protects a
real invariant (schema integrity, event contract compatibility, compliance
gating) that is difficult to repair after a regression.

A change to the Aphrodite domain is acceptable when:

1. **Schema integrity** — new persisted fields are added to
   `libs/aphrodite/database/prisma/schema.prisma` and reflected in the
   `@aphrodite/database` Zod schemas; enum changes preserve the Prisma enum as
   the authoritative form.
2. **Event integrity** — any new cross-service event is added to
   `libs/contracts/src/events/aphrodite.ts` with a Zod payload schema and a
   `createEventSchema(...)` wrapper, and registered in `AphroditeEventTypes`.
3. **API validation** — every new Hono route validates its request with a Zod
   schema via `@hono/zod-validator`, and returns the shared error envelope
   (`{ error: { code, message } }`).
4. **Limit enforcement** — token, tip, stream, chat, and device operations
   respect the constants in `core/src/constants/limits.ts`; device intensity is
   bounded by `Device.maxIntensity`.
5. **Compliance gating** — broadcasting and access to age-restricted content
   remain gated on age verification; 2257 and consent workflows are not
   bypassed.
6. **Tests** — affected libraries pass their Vitest suites.
7. **No scaffold regressions** — code does not depend on the behaviour (only the
   types) of the four scaffold libraries until they are implemented.
