jonnify·codemojex /codemojex/commerce/the-revenue-ledger

Codemojex · C6.1 · a dive of The Revenue Ledger and the KeyShop

The house’s own signed book.

Where C5 moves the players’ money, this dive is the house’s. The revenue_ledger (RVL-branded) is the deliberate inverse of players: a signed delta with no non-negative CHECK, no exactly-once index, uniqueness the RVL primary key alone. Every platform movement funnels through one primitive — Wallet.house_post/5 — and the chapter law is conservation over three observable columns.

Grounding — echo/…/codemojex/wallet.ex (house_post/5 · book_house/5 · house_balance/1 · revenue_breakdown/1 · the buy_in/2 booking site) · schemas/revenue_ledger.ex · rooms.ex (SEAM-1 · SEAM-2) · the revenue-ledger story.

The five house movements — select one
movement reason house delta what else moves the seed deposit_seed −100 keys pool opens at 1000 diamonds member 1–2 deposit_recovery +8 keys each pool unchanged · player −8 member 3 revenue +4 keys pool +40 diamonds · player −8 member 4+ revenue +8 keys pool unchanged · player −8 void reclaim deposit_reclaim +100 keys no refund · exactly one row
the seed → the golden game forms and Rooms books the seed through book_house (SEAM-1): one deposit_seed row of −100 keys, in the SAME Repo.transaction as the games-row write that opens the pool at 1000 diamonds. The house swings negative — no non-negative CHECK stands in the way (D-1).
§1

The inverse of players

schemas/revenue_ledger.ex

The players row guards a balance: a non-negative CHECK (players_non_negative), a FOR UPDATE row lock on every mutation, and the buy-in marker’s (player, ref) exactly-once index. The revenue_ledger inverts each choice on purpose — each house post is a distinct accrual, so nothing deduplicates it; the negative seed row is legitimate, so nothing forbids it.

echo/…/codemojex/schemas/revenue_ledger.ex · the shape source
@primary_key {:id, :string, autogenerate: false}

schema "revenue_ledger" do
  field :account, :string
  field :currency, :string
  field :delta, :integer                              # signed — no non-negative CHECK (D-1)
  field :reason, :string
  field :ref, :string
  timestamps(type: :utc_datetime_usec, updated_at: false) # append-only — no row is ever updated
end

The balance is the sum of rows, never an in-place mutation. The table holds only platform movements — account = "platform", the @house module attribute behind Wallet.house_account/0, the sole account this rung — so the platform balance is a clean aggregate with no player rows to exclude (cm.6 D-1). And because house_post/5 touches no balance column, it needs no player lock and can never breach players_non_negative.

The revenue-ledger story pins the shape: account=platform + currency=keys + a signed delta — a negative seed row exists on the real book, and no non-negative CHECK stands in its way (D-1).

§2

One primitive, five movements

wallet.ex · house_post/5

house_post/5 mints EchoData.BrandedId.generate!("RVL"), inserts one signed %RevenueLedger{} row — account, currency, delta, reason, ref — and returns the RVL id. Rooms reaches it through the public boundary alias book_house/5 for the two seam movements (the seed debit, SEAM-1; the void reclaim, SEAM-2); buy_in/2 calls it directly for the per-member credit, inside the buy-in’s own transaction under the games-row lock — atomic double-entry, no new transaction, no new lock.

echo/…/codemojex/wallet.ex · buy_in/2 — the paired house credit source
pool_keys =
  Economy.entry_fee_split_keys(
    ordinal,
    g.start_threshold || 0,
    g.first_movers || 0,
    g.entry_fee_revenue_percentage || 0,
    fee
  )

reason = if ordinal <= (g.start_threshold || 0), do: "deposit_recovery", else: "revenue"
house_post(@house, "keys", fee - pool_keys, reason, game) # the exact keys complement

The cut fee − pool_keys is computed from the keys floor directly, never re-divided from the diamonds, so house_keys + pool_keys == fee exactly — the revenue-ledger story proves the partition across the fee × revenue-percentage grid, including a div(pool, 9) ≠ div(pool, 10) divergence cell. cm.7 adds a sixth booking site: the KeyShop settlement books its "purchase" row through the same primitive (C6.2).

§3

The conservation check

wallet.ex · revenue_breakdown/1

The chapter law, stated exactly: Σ(player key debits) == Σ(house key credits, excl. the deposit legs) + Σ(pool key portions) — conservation over three observable columns: the players.keys deltas, the revenue_ledger rows, and games.prize_pool ÷ 10. Explicitly not Σ all-ledger-rows = 0: the player debit is a bare keys-column move and the pool is a games column, so a naive single-table row-sum does not balance — it is the house balance, not the system total (the D-3 honesty statement).

The conservation check — step the golden game
the seed house −100 member 1 +8 recovery member 2 +8 recovery member 3 +4 rev · pool 4 member 4 +8 revenue players.keys Σ debits 16 == revenue_ledger Σ credits * 16 + prize_pool ÷ 10 Σ pool portions 0 house net (all rows): −84 keys pool: 1000 diamonds * recovery + revenue rows only — the deposit legs (the seed, the reclaim) sit outside the identity (the D-3 honesty statement)
16 == 16 + 0 → member 2 completes the recovery band: house net = 16 − 100 = −84 — still negative, the zero-loss made explicit (the revenue-ledger story).
echo/…/codemojex/wallet.ex · the reconciliation reads source
def house_balance(account \\ @house) do
  Repo.all(
    from r in RevenueLedger,
      where: r.account == ^account,
      group_by: r.currency,
      select: {r.currency, type(sum(r.delta), :integer)}
  )
  |> Map.new()
end

def revenue_breakdown(game) do
  Repo.all(
    from r in RevenueLedger,
      where: r.ref == ^game,
      group_by: r.reason,
      select: {r.reason, type(sum(r.delta), :integer)}
  )
  |> Map.new()
end

house_balance/1 returns %{"keys" => Σ} for the golden game, and the KeyShop’s "stars" purchase rows sum into their own bucket with no read change (the multi-currency seam, D-5). revenue_breakdown/1 reads the four-member game as %{"deposit_seed" => -100, "deposit_recovery" => 16, "revenue" => 12}; a voided room adds its "deposit_reclaim" leg. The :bigint SUM is cast to :integer — keys is integer-exact (D-2), so finance reads a plain integer, never a Decimal.

§4

References

sources · related

Sources

Related