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 undersrc/. Four are thin scaffolds (@aphrodite/chat,@aphrodite/payment,@aphrodite/safety,@aphrodite/sdk): each exports a realtypes.tsbut its functional subdirectories contain only a version constant and the commentimplementation 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).services/aphrodite/— does not exist. Aphrodite has noservices/tier; runnable services live underapps/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. 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:
BroadcasterandViewereach carry a uniqueuserIdreferencing a platform user managed outside this schema. There is nouserstable 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,moderatorAccountStatus—active,suspended,banned,pending_verification,deactivatedBroadcasterVerificationLevel—unverified,basic,verified,premium,exclusiveAgeVerificationStatus—pending,verified,rejected,expiredContentRating—sfw,suggestive,sensual(Aphrodite is capped at music-video-grade sensuality; the platform does not acceptnsfw/explicitcontent. Seelibs/aphrodite/core/src/domain/content-policy.tsfor the cap matrix.)Platform—web,ios,android,vr,desktopCurrencyCode—USD,EUR,GBP,CAD,AUD,JPYPayoutInfo.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 = 18VIDEO_QUALITY_LEVELS—480p(854×480, 1500 kbps),720p(1280×720, 3000),1080p(1920×1080, 6000),4k(3840×2160, 15000)STREAMING_PROTOCOLS—rtmp,hls,webrtc,srtCONTENT_CATEGORIES—amateur,professional,couples,solo,group,fetish,roleplay,interactive,vr,gaming,asmr,fitness,art,music,talkSUPPORTED_DEVICE_BRANDS—lovense,kiiroo,ohmibod,we-vibe,handy,satisfyer,generic-buttplugPAYMENT_METHODS—credit_card,debit_card,paypal,crypto,gift_card,wire_transferPAYOUT_METHODS—bank_transfer,paypal,paxum,cosmo_payment,crypto,checkVR_PLATFORMS— Oculus Quest / Quest 2 / Quest 3 / Rift, HTC Vive / Vive Pro, Valve Index, PSVR, PSVR2, Apple Vision Pro, Windows Mixed RealityWS_EVENTS— canonical client WebSocket event-name map (see 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,whepStreamState—created,idle,starting,connecting,publishing,live,playing,paused,stopping,stopped,ended,error,closedVideoCodec—h264,h265,vp8,vp9,av1AudioCodec—aac,opus,mp3,pcmConnectionQuality—excellent,good,fair,poor,disconnectedRecordingFormat—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.dashfield; 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 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,customPaymentStatus—pending,processing,completed,failed,refunded,cancelledPaymentMethodType—card,crypto,wire,ach,paypal,otherWebhookEventType—payment.completed,payment.failed,payment.refunded,subscription.created,subscription.renewed,subscription.cancelled,subscription.expired,chargeback.initiated,chargeback.reversedPayoutMethod/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_LIMITSin@aphrodite/corecaps intensity to0–100, command duration to100–30000 ms, and 60 commands/minute. DeviceControl.sourceTyperecords control provenance:TIP,TIP_MENU,SUBSCRIPTION,MANUAL, orGOAL;sourceTipIdlinks a tip-triggered control to itsTip.- The
devicesapp exposesPOST /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 and
Platform Limits), 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_FEESin@aphrodite/core(20 % base fee, 2.9 % processing, $50 minimum payout). - Token economy —
TOKEN_EXCHANGE_RATE = 20tokens/USD;DEFAULT_TOKEN_PACKAGES. - Stream / chat / device / transaction limits — the constant groups in
core/src/constants/limits.ts(see Platform limits). - Streaming defaults —
createDefaultStreamConfig,DEFAULT_BITRATE_TIERS,QUALITY_PRESETSin@aphrodite/streaming-core. - Rate limiting — each app's
app.tsdefines an in-memory limiter (RATE_LIMIT_MAX100 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/llmas Aphrodite dependencies. Those are not present in the verifiedpackage.jsonfiles of the apps and core libraries reviewed; the apps depend on@aphrodite/core,@aphrodite/database, domain-specific Aphrodite libraries, andhono/@hono/zod-validator. Treat additional cross-domain wiring as unverified until confirmed in apackage.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:
- Schema integrity — new persisted fields are added to
libs/aphrodite/database/prisma/schema.prismaand reflected in the@aphrodite/databaseZod schemas; enum changes preserve the Prisma enum as the authoritative form. - Event integrity — any new cross-service event is added to
libs/contracts/src/events/aphrodite.tswith a Zod payload schema and acreateEventSchema(...)wrapper, and registered inAphroditeEventTypes. - 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 } }). - Limit enforcement — token, tip, stream, chat, and device operations
respect the constants in
core/src/constants/limits.ts; device intensity is bounded byDevice.maxIntensity. - Compliance gating — broadcasting and access to age-restricted content remain gated on age verification; 2257 and consent workflows are not bypassed.
- Tests — affected libraries pass their Vitest suites.
- No scaffold regressions — code does not depend on the behaviour (only the types) of the four scaffold libraries until they are implemented.