jonnify·codemojex /codemojex/branded-systems/branded-ids-are-the-keys

Codemojex · C1.1

Branded ids are the keys.

A Codemojex id is 14 bytes — a three-character uppercase namespace and eleven Base62 characters over a 63-bit snowflake. The brand is the type: an id is validated by shape, not by a registry, so the brand carried on a value is what gets checked at every boundary. Because it carries its mint time, the same id is a sound cache version and a sound idempotency token.

Pick a field below. The cells it occupies light up — the namespace is the type; the eleven Base62 characters pack the snowflake.

U S R 0 K H T O W n G L u C ts · 41 bits node · 10 seq · 12
The example id USR0KHTOWnGLuC from the codec's own doctest — 3 namespace cells (lead) + 11 Base62 cells (teal) over a ts(41) | node(10) | seq(12) snowflake. Source: EchoData.BrandedId.

namespace — the three-character uppercase brand (USR here). It is the type: EchoData.BrandedId.parse/1 reads it back, and every boundary checks the brand by shape, not against a registry.

§1

The brand is the type

EchoData.BrandedId

One module owns the codec — echo_data/lib/echo_data/branded_id.ex. Its type is fixed at @type t :: <<_::112>> (112 bits, exactly 14 bytes), and the shape is the guard: defguard is_branded(id) when is_binary(id) and byte_size(id) == 14. Minting is generate!(ns) — it encodes a fresh EchoData.Snowflake.next() under the namespace; reading back is parse/1, which answers {:ok, namespace, snowflake} or :error; and validation is valid?(id), defined as parse(id) != :error. Validation is a parse, not a registry lookup — the brand carried on the value is the whole check.

echo_data · the EchoData.BrandedId moduledoc doctestiex
iex> EchoData.BrandedId.encode!("USR", 274557032793636864)
"USR0KHTOWnGLuC"
iex> EchoData.BrandedId.parse("USR0KHTOWnGLuC")
{:ok, "USR", 274557032793636864}
iex> EchoData.BrandedId.hash32(274557032793636864)
234878118

The eleven Base62 characters encode the snowflake, epoch 1704067200000: the high 41 bits are milliseconds since the epoch, so ids sort by creation; 10 bits are the minting node (0–1023), so a node mints without asking anyone; and 12 bits are the per-millisecond counter (0–4095), so a node mints up to 4096 ids per millisecond before the timestamp advances. Time-ordered and coordination-free at once.

§2

The fifteen-namespace roster

design §Identity

Fifteen namespaces carry the game today — the founding nine and the six their shipped systems added. The distinction that matters is roster versus live mint: thirteen have a live generate!("…") call site; two are designed brands the code folds elsewhere or reserves forward.

Select a brand — the readout names its entity and its live mint site (module:line), or the as-built truth for the two designed brands.

PLRplayer ROMroom GAMgame GESguess EMSemoji set TXNtransaction JOBbus work NOTnotification CMDbot command SESsession RVLrevenue PKGkey package ORDorder OTXorder txn WHKwebhook

PLR — the player. Minted at wallet.ex:26; the id keys the players row on the Postgres floor and the player's lane and leaderboard entry in Valkey.

The two dim cells are the tell. CMD is a designed namespace for an inbound bot command, but the shipped bridge in echo_bot.ex mints a JOB id — each command rides its own JOB-id lane, so CMD names the concept while JOB keys the work. WHK is the forward schema for the first push rail (on-chain TON): its webhooks table is not created this rung and it carries no live mint — the designed shape a later push-rail rung adds cleanly. Roster fifteen, live mints thirteen, and the gap is documented, not hidden.

§3

The id as the primary key

schemas/*.ex

Every one of the eleven Ecto schemas under lib/codemojex/schemas/ declares the same primary key: @primary_key {:id, :string, autogenerate: false}. The key is a string the application mints, not a serial the database hands out — autogenerate: false — because the id exists before the row does, minted on any node with no coordination. The same 14-byte value then keys the Valkey lane, the leaderboard sorted-set member, the near-cache entry, and the bus job. There is no translation table between tiers: a piece of state is one name asked at different depths, which is the whole subject of the next dive.

§4

References

sources · related

Sources

Related