Social LiveDocumentationPLATFORM DOCS

Data Model & Rollout

Proposed Prisma schema additions, new ledger types, and the phased shipping plan.

Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

TL;DR -The proposed engineering blueprint for the Creator Program: the new database tables, the new ledger transaction types, the single monthly job that pays everything out, and the five-phase order in which it would ship. All additive -no existing table changes shape.

Who this is for -Engineering & Backend: the whole page. Product & Operations: the rollout-phases table and the open questions at the end.

Status: PROPOSED DESIGN -see the Creator Program overview.

What this page is, in plain terms

This is the build sheet. The other pages describe what each layer does; this one collects the database schema, the money-movement record types, and the scheduled job that computes payouts, then lays out the order to ship them in.

The guiding constraint is that everything is additive. No existing table changes shape (except some new optional metadata fields), so the program can be built and rolled out one phase at a time without risky migrations to the economy that's already live.

Schema additions (Prisma sketch)

Below is the proposed set of new tables -one per program concept (tier state, stream sessions, monthly quota rollups, agencies and their memberships/rebates, PK results, and payout methods). Diamond amounts use the same BigInt-micros convention as the rest of the economy.

Everything is additive -no existing table changes shape except new nullable metadata. Diamond amounts follow the existing BigInt-micros convention.

enum HostTier { RISING BRONZE SILVER GOLD STAR LEGEND }

model HostTierState {
  userId        String   @id
  tier          HostTier @default(RISING)
  multiplierBps Int      @default(10000)
  monthKey      String   // evaluation month, e.g. "2026-07"
  evaluatedAt   DateTime
}

model StreamSession {
  id             String    @id @default(cuid())
  userId         String
  roomId         String
  startedAt      DateTime
  endedAt        DateTime?
  validMinutes   Int       @default(0) // minutes with >= 3 concurrent viewers
  peakViewers    Int       @default(0)
  @@index([userId, startedAt])
}

model HostQuotaMonth {
  userId              String
  monthKey            String
  track               QuotaTrack @default(CASUAL)
  validHours          Int        @default(0)
  validDays           Int        @default(0)
  earnedDiamondMicros BigInt     @default(0) // settled, paid-coin, pre-split
  bonusStatus         QuotaBonusStatus @default(PENDING) // PENDING/PAID/MISSED/WITHHELD
  @@id([userId, monthKey])
}
enum QuotaTrack { CASUAL REGULAR PROFESSIONAL }

model Agency {
  id          String @id @default(cuid())
  name        String
  code        String @unique // join code
  ownerUserId String
  status      AgencyStatus @default(PENDING) // PENDING/ACTIVE/SUSPENDED/TERMINATED
  memberships AgencyMembership[]
}

model AgencyMembership {
  id         String @id @default(cuid())
  agencyId   String
  userId     String
  shareBps   Int    // 0..2000, DB check constraint
  status     MembershipStatus @default(INVITED) // INVITED/ACTIVE/NOTICE/TERMINATED
  acceptedAt DateTime?
  endsAt     DateTime? // set when notice starts; share applies until here
  @@index([userId, status]) // app enforces one ACTIVE/NOTICE per user
}

model AgencyRebate {
  agencyId            String
  monthKey            String
  rosterDiamondMicros BigInt
  rebateBps           Int
  rebateDiamondMicros BigInt
  qualifyingHosts     Int
  @@id([agencyId, monthKey])
}

model PkBattleResult {
  battleId          String  @id // FK -> existing PkBattle
  winnerUserId      String? // null = tie
  scoreAPaidCoins   Int
  scoreBPaidCoins   Int
  bonusDiamondMicros BigInt @default(0)
  ladderPointsA     Int     @default(0)
  ladderPointsB     Int     @default(0)
  endedAt           DateTime
}

model PayoutMethod {
  userId            String @id
  rail              PayoutRail // STRIPE / PAYONEER / LOCAL_BANK
  destinationMasked String
  verifiedAt        DateTime?
  changedAt         DateTime   // drives the 7-day change freeze
}

New ledger transaction types

Every program payout is recorded as a ledger row, just like every other money movement in the economy. The existing LedgerTransaction table doesn't change shape -its type enum just gains four new values. The table lists each new type, which way the money flows, the idempotency-key shape that makes it safe to retry, and what gets snapshotted into metadata.

The append-only LedgerTransaction model is unchanged; the type enum grows. Every row keeps the same-$transaction-as-balance-change + idempotency-key rules from Economy.

TypeDirectionIdempotency key shapeMetadata snapshot
AGENCY_SHAREagency wallet credit (host-split)agency_share:<giftLedgerId>agencyId, shareBps, host id
AGENCY_REBATEagency wallet credit (platform)agency_rebate:<agencyId>:<monthKey>roster micros, band, qualifying hosts
QUOTA_BONUShost credit (platform)quota_bonus:<userId>:<monthKey>track, hours, days, base micros, bps
PK_BONUShost credit (platform)pk_bonus:<battleId>[:a|:b]combined gross, pair-decay step

Plus one metadata extension: DIAMOND_EARNING rows gain tierAtCredit, multiplierBps, and (when split) agencyId/shareBps.

Monthly-keyed idempotency keys make the whole monthly job re-runnable: a crashed run resumes safely because every credit it might repeat replays instead of double-paying.

The monthly job

A single scheduled job does all the monthly payouts, in a deliberate order so each stage's output feeds the next. Because every stage is independently idempotent, a crash mid-run can simply be re-run from the top.

One scheduled job (1st of month, 00:10 UTC), ordered so each stage feeds the next, each stage idempotent independently:

  1. Settle any matured diamonds for affected wallets (reuses lazy settlement logic).
  2. Tier evaluation → HostTierState upserts.
  3. Quota evaluation → QUOTA_BONUS credits.
  4. Agency rebates → AGENCY_REBATE credits.
  5. Season close (every 4th run) → ladder prizes + league reshuffle.

Config

All program numbers live in code, not environment variables -they're economics that belong in code review, not deployment config.

All constants land in creator-program.constants.ts next to economy.constants.ts, exported as creatorProgramConfig, snapshot into ledger metadata at credit time. No new env vars -these are economics, not deployment config, and belong in code review, not .env edits.

Rollout phases

The five layers ship in a fixed order, each chosen so its dependencies are already live. The table gives what each phase ships and why it comes when it does.

PhaseShipsWhy this order
1 -Sessions & tiersStreamSession tracking (dark), HostTierState, multiplier at gift time, tier badgeMultiplier is the smallest ledger-touching change; sessions must accumulate history before quotas can launch honestly
2 -QuotasTracks, progress UI, QUOTA_BONUSNeeds ≥ 1 full month of session data to sanity-check valid-hour rates before money attaches
3 -PK economyPkBattleResult, winner bonus, ladderBuilds on shipped PK mechanics; needs tier data for league seeding
4 -AgenciesEntities, splits, rebates, admin Agencies pageHighest-complexity money movement; rebate floor needs the quota system live
5 -Payout railsFinish Stripe Connect, batch clearing, then PayoneerIndependent of 1–4; sequenced last only because current volume clears through manual admin review fine

Each phase: constants land first behind a flag, admin-portal read views ship before money moves, and the phase's page in this section flips from PROPOSED to normative in the same commit that enables it.

Open questions (decide before Phase 1)

These are the design decisions still unresolved -the ones that need a call before the first phase ships.

  1. Tier thresholds vs. actual early traffic -the table's numbers assume Poppo-scale spend; likely need a 5–10× reduction for launch and a planned re-rating.
  2. Do stage guests accrue tier progress on gifts targeted at them (current gifting allows it)? Design says yes (it's their earning), but confirm quota/session attribution UX.
  3. Regional pricing: coin packages are USD-only today; agency recruiting in SEA/MENA will immediately raise localized-pricing demands -out of scope here, flagged.