Skip to content

Data Model Overview

This page is the spine for data knowledge across the monorepo: where state lives, who owns each table, how schemas migrate, and how data flows between services. For per-domain entity detail, see the companion Data Models page.

Persistent Stores

StoreOwnerConnectionEnv vars
Postgres — sirloin DBsirloin (BUN ORM); brain owns the fennec schema inside it via PrismaSingle Postgres host (rump:5432 in dev)SIRLOIN_DATABASE_URL, BRAIN_DATABASE_URL, DATABASE_URL, DIRECT_DATABASE_URL
Object storage — S3-compatiblesirloin (primary), brain (R2 endpoint)MinIO in dev (tenderloin:9000); Cloudflare R2 in prod (per BRAIN_AWS_S3_APIURL)SIRLOIN_S3_ENDPOINT, SIRLOIN_S3_BUCKET, SIRLOIN_S3_ACCESS_KEY, SIRLOIN_S3_SECRET_KEY, SIRLOIN_S3_PUBLIC_URL, BRAIN_AWS_S3_APIURL, BRAIN_S3_BUCKET_NAME, BRAIN_PUBLIC_BUCKET_URL
Redisbrain (BullMQ queues)Co-located with brainREDIS_HOST, REDIS_PORT, REDIS_PASSWORD (apps/brain/src/config/queue.config.ts:8-10); dev wiring docker-compose.yml:263-264
Neon (managed Postgres)Production hosting layer for the Postgres database aboveBranch-per-PR via .github/workflows/neon-branching.ymlTODO(@law): production DATABASE_URL shape (Neon connection string)
Chargebeesirloin (read-through, not a local store)HTTPS APISIRLOIN_CHARGEBEE_* (see apps/sirloin/Makefile)

Evidence: env var names from apps/brain/.env.example and apps/sirloin/.env.example; Redis queue usage from apps/brain/src/modules/bullBoardQueues.module.ts and apps/brain/src/modules/domain/character/character.module.ts; Neon branching from .github/workflows/neon-branching.yml.

Schema Ownership

Brain and sirloin share a single Postgres instance but own disjoint schemas. They never cross-query — all reads cross the gRPC boundary.

Postgres (rump:5432)
├── schema: public, users, characters, media, billing, bi, audits,
│ autoscaling, mcp ← owned by sirloin (BUN)
└── schema: fennec ← owned by brain (Prisma)
SchemaORMOwnerSource
users, characters, media, billing, bi, audits, autoscaling, mcpBUNsirloinapps/sirloin/internal/pkg/models/ (users.go, characters.go, media.go, bi.go, etc.)
fennecPrismabrainapps/brain/prisma/schema.prisma
Stripnone — read-only over gRPCstripNo DB; consumes sirloin gRPC. Confirmed: strip imports no database/sql/bun.DB driver — StripAuditLog rows in strip code are proto-generated types from apps/strip/internal/pkg/pb/sirloin/v5/strip.pb.go:3618.

Per-Service Entity Overview

sirloin (BUN, schemas above)

Top entities, sourced from apps/sirloin/internal/pkg/models/*.go:

EntityPurposeKey relations
User (users.go)Identity projection keyed by Clerk IDCredit, Character, Media
CreditStandard + full-access credit balancesUser, Purchase, Referral
Character (characters.go)Runtime state, reservation, shopMedia, ReferenceDatasetImage
Media (media.go)Generated assets, favourites, archiveCharacter, Example
ReferenceDatasetImageTraining reference rowsCharacter
DunningAttemptFailed-invoice retries→ billing
FailedOperation (failedoperation.go)Saga retry queue(see ADR 2026-04-27-saga-pattern-for-distributed-payments)
FraudFlag, FraudCooldown, FraudEvent, HighRiskBin, CardFingerprintAccountFraud signalsUser
PrimerPollingState (primer_polling_state.go)Cursor for Primer settled-payment polling(see ADR 2026-04-27-primer-payment-gateway)
FlankWorkflow, FlankExecution, FlankAdapterWorkflow engine state(flank service)
MonitorProbeSynthetic monitoring
StripAuditLog, StripAdminRoleAdmin audit + RBACUser

brain (Prisma, schema fennec)

Top entities, sourced from apps/brain/prisma/schema.prisma:

EntityPurposeKey relations
UserIdentity recordCharacter, Generation
CharacterDetailed appearance + training metadataOnboardingImages, BodyType, CharacterControlGroup, TrainingJob
GenerationPermanent generation record (prompts, logs)Character, GenerationExample, GenerationTag
TrainingJobLoRA training lifecycleCharacter
DatasetGenerationRequestRunPod dataset jobCharacter
OnboardingImagesValidated onboarding inputsCharacter
Pack, Item, ShopVIImportJobShop catalogue + importCharacter
Settings, CharacterSettings, ApplicationSettings, WappySettings, IfastConfig, PresetApp + character configuration
InferenceProviderSpendPer-provider spend trackingunique on (date, inferenceProvider)
ImageModerationResult, ImageModerationTagModeration outcomes
MotionType, VirtualCharacterTemplate, GenerationExample, GenerationTagGeneration reference data

Cross-Service References

The two services hold soft references to each other’s identifiers; there are no enforced FKs across the schema boundary.

FromToMechanismNotes
Brain.User.clerkIdSirloin.User.clerkIdShared Clerk IDBoth sides keyed by the Clerk subject
Brain.Character.idSirloin.Character.idSame UUID, written through gRPCsirloin owns runtime state, brain owns appearance
Brain.Generation.characterIdSirloin.Character.idUUID matchsirloin → brain via apps/sirloin/pkg/brain-client/
Sirloin.Media.characterIdBrain.Character.idUUID match
Chargebee customer_idSirloin.SubsAll.customerId (bi schema)External-system FKsirloin pulls per ADR 2026-04-27-chargebee-polling-over-webhooks

Confirmed: apps/brain/prisma/schema.prisma contains no subscriptionId/invoiceId/chargebee columns — brain holds no sirloin-side billing FKs.

Data Flow

1. Signup → customer creation

sequenceDiagram
autonumber
participant U as User
participant Brisket as brisket (Next.js)
participant Clerk
participant Sirloin as sirloin (Go)
participant Brain as brain (NestJS)
participant CB as Chargebee
U->>Brisket: Sign up
Brisket->>Clerk: OAuth / email auth
Clerk-->>Brisket: clerkId, JWT
Brisket->>Sirloin: REST /users (JWT)
Sirloin->>Sirloin: upsert User + Credit (clerkId)
Sirloin->>Brain: gRPC EnsureUser(clerkId)
Brain->>Brain: upsert fennec.User
Sirloin->>CB: create Customer
CB-->>Sirloin: customer_id

2. Generation request → media row

sequenceDiagram
autonumber
participant Brisket
participant Sirloin
participant Brain
participant Round as round (ML)
participant S3
Brisket->>Sirloin: REST GenerateMedia
Sirloin->>Sirloin: debit Credit (txn)
Sirloin->>Brain: gRPC StartGeneration(characterId, prompt)
Brain->>Brain: enqueue BullMQ job + insert Generation
Brain->>Round: gRPC inference call
Round-->>Brain: image bytes / embedding
Brain->>S3: upload asset
Brain-->>Sirloin: gRPC NotifyMediaReady(mediaId, path)
Sirloin->>Sirloin: insert Media (status=COMPLETED)

3. Payment → subscription state

sequenceDiagram
autonumber
participant CB as Chargebee
participant Sirloin
participant Brain
Note over Sirloin: Pull-based, not webhooks (ADR 2026-04-27-chargebee-polling-over-webhooks)
Sirloin->>CB: poll subscription / invoice events
CB-->>Sirloin: events
Sirloin->>Sirloin: distributed lock per customer (ADR 2026-04-27-distributed-locks-for-payments)
Sirloin->>Sirloin: write SubsAll (bi), DunningAttempt, FailedOperation
Sirloin->>Sirloin: defer activation if needed (ADR 2026-04-27-deferred-subscription-activation)
Sirloin->>Brain: gRPC update entitlements (if any)

Migration Discipline

ServiceToolSource of truthNotes
brainPrisma migrateapps/brain/prisma/migrations/ (timestamped, e.g. 20241231153339_init_fennec)Prisma client generated on start:dev (prisma generate && nest start --watch, apps/brain/package.json). Migrations are applied at container boot via pnpm exec prisma migrate deploy (apps/brain/startup.sh:9); CI (.github/workflows/brain.yml) only runs prisma generate for lint/typecheck/test, no migrate deploy step.
sirloinCustom Go runnerapps/sirloin/internal/app/migrate/migrate.go, SQL files in apps/sirloin/internal/app/migrate/schema/*.sqlmake verify-migrations checks numbers are unique (apps/sirloin/Makefile); CI gate ci-all runs it.
strip, round, fennec, brisket, chuck, shank, flankn/aNo owned schema.

Branch-per-PR Postgres copies are provisioned by .github/workflows/neon-branching.yml.

Backups, Retention, PII

  • TODO(@law): production backup schedule and RPO/RTO targets.
  • TODO(@law): point-in-time recovery window on Neon.
  • TODO(@law): retention policy for StripAuditLog, FraudEvent, Generation rows.
  • PII surface: Brain.User.email/firstName/lastName, Sirloin.User, Sirloin.SubsAll.email, Chargebee customer records, S3 generated assets. See Security Model.

Cross-References

Open follow-ups

  1. Production DATABASE_URL shape on Neon (TODO(@law)).
  2. Production backup schedule, RPO/RTO targets (TODO(@law)).
  3. Neon point-in-time recovery window (TODO(@law)).
  4. Retention policy for audit, fraud, generation rows (TODO(@law)).