jonnify·codemojex /codemojex/branded-systems/the-four-layers

Codemojex · C1.2

The four layers.

Codemojex is four layers, and the layering is the architecture. A request enters at a thin Phoenix surface; durable work travels on the EchoMQ bus to a small set of authorities; and state rests in three storage tiers, each chosen by what its data needs. Each layer owns its tier, and the branded id is the one value that threads all of them — money, the secret, and the commitment rest on the durable floor, while everything in Valkey is derived and reconstructable.

Pick a layer below. The band it names lights up, and the readout says what that layer owns.

Phoenix surfaceCodemojexWeb · JSON API + Channels · privacy-safe views ▾ Codemojex facade → domain systemsRooms · Guesses · Scoring · Wallet · Board ▾ EchoMQ bus on Valkeyfair lanes: guesses · settle · notify · commands ▾ Storage tiersPostgres SoR · Valkey bus/board/L2 · EchoStore L1
Four layers, top to bottom; the branded id is the one value that crosses every boundary. Source: design §The architecture at a glance.

surface — the thin Phoenix application: a JSON API under /api and Channels. Every action calls the facade and the privacy-safe views; a guess returns an accepted-and-on-its-way response while the bus carries the scoring. There is no process per room.

§1

Four layers, four owners

design §Fault tolerance

The surface is deliberately small; the facade delegates and never holds logic; the bus carries durable work to the authorities; and the storage tiers hold the truth. The order is the dependency order — the system of record first, then the fan-out and the wire, then the near-cache, then the consumers, and the HTTP surface last, so a live channel is the last thing to start and the last thing to fall.

design §Fault tolerance — the dependency-ordered supervision treetree
Codemojex.Supervisor (one_for_one)
├─ Codemojex.Repo                     # the system of record
├─ Phoenix.PubSub (Codemojex.PubSub)  # the live fan-out
├─ Codemojex.Bus                      # the shared RESP3 connector
├─ Codemojex.Tables (rest_for_one)    # the EchoStore near-cache tier
│  ├─ EchoStore.Directory             # started first; a restart cascades
│  ├─ EchoStore.Table :cm_games
│  └─ EchoStore.Table :cm_emojisets
├─ Codemojex.RateLimiter
├─ Codemojex.EchoBot
├─ EchoMQ.Consumer :cm_score          # the scoring authority
├─ EchoMQ.Consumer :cm_settle         # the settlement worker
├─ EchoMQ.Consumer :cm_notify         # the rate-limited notifier
├─ EchoMQ.Consumer :cm_commands       # inbound bot commands
├─ EchoData.ChampServer (Leaderboard) # an in-memory CHAMP projection
├─ [EchoStore.Graft.Committer]        # optional, when :graft_volume is set
└─ CodemojexWeb.Endpoint              # the HTTP/WS surface

The as-built Codemojex.Tables also starts a third table, :cm_sessions (cm.4) — the design's diagram predates the auth floor, so the code is the authority for the roster (see §3). Each consumer leases the job it is working, so a crashed consumer's in-flight job becomes visible again rather than lost, and its supervisor restarts it in place.

§2

Durable and derived

design §Storage tiers

The storage split is exact. Some state is on the Postgres floor and cannot be regenerated; the rest lives in Valkey and is rebuildable from the floor. Toggle the two halves — the cells that belong to each light up.

players balancesfloor leaderboard ZSETderived the secretfloor player countersderived TXN ledgerfloor the close lockderived the commitmentfloor L1 / L2 cachederived
The floor cells (lead) are irreplaceable; the derived cells (teal) rebuild from the system of record. Source: design §Storage tiers.

on the floor — irreplaceable, in Postgres: the players balances, the append-only transactions ledger, the games secret and nonce, and the golden commitment. These need locks, constraints, and ordered queries, so the design keeps them relational.

§3

The near-cache tier

Codemojex.Tables

Codemojex.Tables declares three L1-over-L2 caches, each keyed by a branded id: :cm_games (kind "GAM", coherence :none), :cm_emojisets (kind "EMS", :none), and :cm_sessions (kind "SES", cm.4 — the first mutable table, coherence :tracking, so a revoked session is evicted from every holder's L1 at once). A read is a caller-side :ets lookup; a miss coalesces onto one in-flight fill that checks L2 and falls through to a loader that reads Postgres, writing both layers under a TTL. The loader returns {:ok, term_to_binary(map), id} — the entity's own 14-byte id as the L2 version, so the frame carries the name the value belongs to. The two game caches are immutable for a game's life, so their coherence is :none and the cache never goes stale; the directory is supervised first under :rest_for_one, so the roster cannot silently empty out from under the readers.

§4

References

sources · related

Sources

Related