Data Models
Data Models
Section titled “Data Models”High-level guide to the data models across the monorepo. For full field-level details, refer to the Prisma schema and BUN model definitions directly.
Database Architecture
Section titled “Database Architecture”A local PostgreSQL server (rump:5432) hosts separate databases:
| Database/Schema | ORM | Owner Service | Purpose |
|---|---|---|---|
rump (multiple BUN schemas) |
BUN | sirloin | Billing, user credits, runtime state, admin, fraud |
fennec (fennec schema) |
Prisma | brain | Workflows, generation records, character details, training, config |
Brain and Sirloin may share a PostgreSQL server locally, but own separate databases/schemas and never cross-query. Sirloin calls Brain over HTTP; both use stable domain IDs rather than cross-database constraints.
Domain Ownership
Section titled “Domain Ownership”Each domain entity has a single source of truth. Some entities exist in both services with different responsibilities.
Users And Credits
Section titled “Users And Credits”Source of truth: sirloin (credits/billing), brain (identity)
Brain.User (id, clerkId, email, firstName, lastName) ↕ synced via Clerk IDSirloin.Credit (userId, standardCredits, fullAccessCredits, referralCode) → Sirloin.Purchase (itemPriceId, transactionId, price, currency) → Sirloin.Referral (referringUserId, referredUserId, creditsEarned)Characters
Section titled “Characters”Source of truth: sirloin (status, shop/reservation), brain (detailed attributes, training)
Sirloin tracks runtime state: status, availability, reservation, purchase, KYC verification, credit spend. Brain tracks detailed appearance attributes, onboarding images, body type, lora settings, and training jobs.
Sirloin.Character status, availabilityStatus, reservedBy, reservedUntil, purchasedBy, priceTier → Sirloin.ReferenceDatasetImage (imagePath, etag)
Brain.Character characterName, gender, ethnicity, hairColor, bodyType, lora, style, trigger → Brain.OnboardingImages (imageUrl, validationMethod, analysis) → Brain.BodyType (uiLabel, gender, defaultPromptValue) → Brain.CharacterControlGroup (name)Media And Generations
Section titled “Media And Generations”Source of truth: sirloin (user-facing state), brain (generation records)
Sirloin owns favorites, archives, download tracking, and cost. Brain owns the permanent generation record with prompts, logs, and model parameters.
Sirloin.Media userId, characterId, status, isFavorite, isArchived, cost, controls → Sirloin.Example (description, descriptionVersion, embedding, embeddingVersion, characterModels[], explicitnessLevel) → Sirloin.Tag (via ExampleTag join table)
Brain.Generation genId, characterId, prompt, success, mediaType, logs, inputVariables → Brain.MotionType (name, prompt, lora) → Brain.GenerationExample → Brain.GenerationTagExample semantic search
Section titled “Example semantic search”Sirloin versions generated descriptions and embeddings independently. A source checksum change clears both artifacts. The maintenance workers also rebuild any row whose stored version differs from its current media-specific contract.
Descriptions use the GA gemini-3.7-flash
model through OpenRouter, pinned to the google-vertex provider with provider
fallbacks disabled. Image and carousel examples send up to four source
images. Video examples send a four-frame spritesheet even when the row also has
a thumbnail. The video prompt describes observable motion, pose changes, camera
movement, and scene changes across those frames. The worker does not upload full
videos. Full-video input adds transfer, token, privacy, and latency costs and
needs a task-specific retrieval evaluation before adoption. Further model or
provider changes also require retrieval evaluation.
Description batches contain up to 25 rows to leave headroom within the
five-minute task timeout; maintenance runs every five minutes.
The document embedding combines the visual description with curated tags, location, and media type. It excludes generation prompts because the generated media can differ from the request. Search queries use BGE’s query instruction, while stored documents do not.
If Gemini returns no text after safety filtering, including for legal adult content, Sirloin records a current, empty description. The embedding then uses curated tags, location, and media type. This terminal fallback keeps rollout readiness measurable without adding unobserved visual claims. Malformed or truncated provider responses remain pending even after retries are exhausted; they are not recorded as successful metadata-only fallbacks. Disabling configurable thresholds does not bypass provider-level safety policy.
The legacy search worker applies the same metadata-only fallback to two failures that repeat on every attempt. In the first, the example has no video and none of its carousel images can be used: each is empty or larger than the 16 MiB download cap. The worker tries carousel paths in order until four images download, so a later image replaces an earlier one that fails. In the second, OpenRouter rejects the request as too large (HTTP 413). The worker then logs a warning, plus one warning per unusable image, and publishes the fallback once instead of retrying every 30 minutes. Other failures keep the 30-minute retry, including a carousel download that fails for another reason, rate limits, and provider errors.
Vector search applies access, tag, media-type, Kitsune, welcome, and example-ID
filters in PostgreSQL. A materialized statement-local setting enables strict
iterative HNSW scanning through transaction-pooled connections. The HNSW
candidate query is LATERAL and depends on that setting, so PostgreSQL applies
it before the index scan starts. Interactive
skip_total requests avoid separate BEGIN, SET LOCAL, and COMMIT round
trips. Exact-count requests keep a read-only repeatable-read transaction. A
bounded inner query orders by distance so PostgreSQL can use HNSW. An outer
query adds an ID tie-breaker over the bounded candidates. The default contract
returns an exact total from the same snapshot. Interactive feeds set
skip_total and paginate with has_more.
Sirloin also caches successful query embeddings for 15 minutes by SHA-256 key.
The cache does not retain raw query text and does not cache failures.
Unbounded requests return the materialized row count.
Verify pgvector 0.8.0 or newer before rollout because older versions do not
support strict iterative scans; schema migrations do not enforce this version
requirement. An embedding-service failure returns an
unavailable error. It never changes semantic search into an unfiltered catalog
request.
Three flags guard the vector-space migration. New semantic fields do not replace the legacy description, embedding, or readiness fields during rollout.
-
Deploy Round first. Requests without a pooling option keep mean pooling.
-
Deploy Sirloin with backfill, cutover readiness, and search flags
false. Wait for old instances. -
Enable
SIRLOIN_EXAMPLE_SEMANTIC_V2_BACKFILL_ENABLED. Keep search disabled. -
Wait until this query returns zero eligible rows:
SELECT count(*)FROM media.examplesWHERE deleted_at IS NULLAND (image_path <> ''OR cardinality(coalesce(image_paths, '{}')) > 0OR coalesce(video_path, '') <> ''OR coalesce(public_video_path, '') <> '')AND (semantic_description_version IS DISTINCT FROM CASEWHEN type IN ('VIDEO', 'MEDIA_TYPE_VIDEO')OR media_type IN ('VIDEO', 'MEDIA_TYPE_VIDEO')THEN 'openrouter-gemini-3.7-flash-observable-video-v3'ELSE 'openrouter-gemini-3.7-flash-observable-v7'ENDOR semantic_embedding_version IS DISTINCT FROM 'bge-small-en-v1.5-cls-v2'); -
Set
SIRLOIN_EXAMPLE_SEMANTIC_V2_CUTOVER_READY=trueto record the completed zero-backlog check. -
Enable
SIRLOIN_EXAMPLE_SEMANTIC_V2_SEARCH_ENABLED.
During backfill, search reads the intact legacy embedding column. After cutover,
search reads only current semantic CLS vectors. Configuration rejects search
cutover unless backfill remains enabled. Before serving the new legacy search
path, run apps/sirloin/ops/semantic_search_fallback_indexes_concurrent.sql
to prepare its HNSW and tag-first join indexes, including on an empty database.
The concurrent operation repairs mismatched indexes as well as interrupted
builds; a valid same-named index alone is not sufficient. Index readiness is an
operator prerequisite, not a schema migration gate. See the
index rollout procedure.
For rollback, disable the search flag first. Reads immediately return to the legacy vector column. Then clear the cutover-ready flag, disable backfill, and wait for active jobs to stop. Do not clear either vector column during rollback.
Billing And Payments
Section titled “Billing And Payments”Source of truth: sirloin (all billing)
Sirloin.DunningAttempt — payment retry attempts for failed invoicesSirloin.FailedOperation — billing ops needing retry (cancel, apply_credits, rollback, etc.)Sirloin.SubsAll (bi schema) — Chargebee subscription sync (email, customerId, mrr, planUnitPrice)
Sirloin.FraudEvent — fraud signal audit logSirloin.FraudFlag — persistent per-user fraud statusSirloin.FraudCooldown — velocity rule blockingSirloin.HighRiskBin — high-risk BIN listSirloin.CardFingerprintAccount — maps card fingerprints to user accounts
Sirloin.PrimerPollingState — cursor for Primer settled payment pollingShop VI
Section titled “Shop VI”Source of truth: sirloin (reservations/purchases), brain (catalog metadata)
Sirloin.Character fields: priceTier, availabilityStatus, catalogueImagePaths, reservedBy, reservedUntil, reservationToken, purchasedBy, purchasedAt
Brain.ShopVIImportJob (directoryPath, status, stats)Admin And Audit
Section titled “Admin And Audit”Source of truth: sirloin
Sirloin.StripAuditLog (userId, action, entityType, entityId, changes, metadata)Sirloin.StripAdminRole (userId, role: root|admin|creator|support|viewer)Brain keeps its own append-only trail for safety-threshold and workflow publishing changes, with a deliberately matching column set:
Brain.AuditLog (userId, userEmail, action, entityType, entityId, changes, metadata)Foxy360
Section titled “Foxy360”Source of truth: sirloin
Sirloin.MCPThread → MCPRun → MCPRunStep → MCPArtifact → MCPConnectorCall MCPMessage (role: user|assistant|system|tool)Configuration And Settings
Section titled “Configuration And Settings”Brain owns application config: Settings, Preset, ApplicationSettings.
Sirloin owns infrastructure config: ScalingDecision (autoscaling decisions with worker counts, prediction methods, spike detection).
Special Field Types
Section titled “Special Field Types”| Type | Examples | Notes |
|---|---|---|
| JSONB | Media.controls, FailedOperation.operationData, FraudEvent.metadata, StripAuditLog.changes |
Structured metadata, varies per record |
| Arrays | Character.captions[], Example.characterModels[], Character.catalogueImagePaths[] |
PostgreSQL native arrays |
| Vector | Example.embedding |
Used for semantic search over examples |
| Soft deletes | deletedAt on Character, Media, ReferenceDatasetImage |
BUN and Prisma both use soft delete patterns |
Key Indexes
Section titled “Key Indexes”Brain (Prisma):
Generation(characterId),Generation(characterId, success),Generation(mediaType, createdAt DESC, success)— generation queriesGeneration(modelUrls)GIN index — array filteringOnboardingImages(characterId, createdAt DESC)— failed images queryInferenceProviderSpend(date, inferenceProvider)unique — spend dedup
Sirloin (BUN):
- Standard FK indexes on
userId,characterId,mediaId - Billing tables indexed on
invoiceId,customerId,subscriptionId - Fraud tables indexed on
userId,cardFingerprint,bin6
Proto To Model Mapping
Section titled “Proto To Model Mapping”Proto messages in proto/sirloin/v5/ map closely to BUN models but are not 1:1. Key differences:
- Proto
Charactermessage is a projection — combines fields from both sirloin and brain character records - Proto
Mediamessage includes computed fields (thumbnail_url,video_preview_url) not stored in DB - Proto
ShopCharacteris a specialized view ofCharacterwith shop-specific fields - Proto billing messages (
SubscriptionDetails,Product,CouponInfo) aggregate data from Chargebee + local billing tables
Source Locations
Section titled “Source Locations”| What | Path |
|---|---|
| Brain Prisma schema | apps/brain/prisma/schema.prisma |
| Sirloin BUN models | apps/sirloin/internal/pkg/models/ |
| Proto definitions | proto/sirloin/v5/, proto/round/v1/ |
| Generated proto docs | api/server.md |