Home › Docs › 32 · Record Numbers

32 · Record Numbers

Every record in the database — a city, a restaurant, a booking, an order — is numbered the way a paper ledger is numbered: 1, 2, 3, …, counted separately inside each kind of record. The two cities are 1 and 2. The twenty-eight restaurants are 1 to 28. Nothing else is attached to the number: it says where the record stands in its own list, and that is all it has ever needed to say.

This replaced an older scheme where every record carried a long scrambled label (cmruh6v6s000613qq…). Those labels worked, but nobody could read them: not a person checking why a booking points at the wrong restaurant, not a support call reading a number over the phone, not an eye scanning a table for the row that does not belong. A number you can follow with a finger beats a label you have to copy-paste.

What the app sees instead

The plain numbers stay inside the building. When the API hands a record to the app, every number that identifies a record travels as an encrypted token — a sealed version of the same number, made with AES-256-GCM encryption. The app stores the token and hands it back untouched; it never opens it and never needs to.

Why seal a number that is only 1, 2, 3?

  • Counting stops working. With plain numbers, changing one digit in an address would walk through every restaurant, booking and order ever made. A sealed number cannot be incremented, guessed, or walked.
  • The seal is steady. The same record always presents the same token, because the token is derived from the number itself rather than rolled fresh each time. The app legitimately keeps hold of identifiers — the restaurant you are currently managing sits in the browser between visits — and a token that changed on every response would make that stored value worthless.
  • A forged token fails quietly. A token that does not open to a real number is treated as "no such record" — an ordinary not-found — never as a peek inside and never as a crash.

Identifiers that were never database numbers are not sealed: your membership ID (MCD-2026-XXXXXX), an order's public booking number, and a UPI handle keep travelling exactly as before. A cart line that names a dish variant (<dish>::<size>) seals only the dish half; the size is a name, not a number.

How the change was made

No record was created or destroyed; the existing ledger was renumbered in place.

  1. A full copy was taken first — every row of all 25 record kinds (2,041 rows), kept outside the database alongside the previous schema, so the old numbering can be restored exactly if this chapter ever has to be retracted.
  2. New numbers were assigned in ledger order: within each kind, the oldest record became 1, the next 2, and so on. (Nothing in the system depends on the order; it only has to be explainable, and "oldest first" is the explanation.)
  3. Every pointer was rewritten to match — restaurant → city, booking → restaurant/table/offer, order → restaurant/customer, and the references that hide inside other records: the dish ids inside past orders, and the "which record changed" pointer inside the audit history.
  4. The result was audited, not assumed: each kind came back with exactly the rows it left with, a sweep of all 36 record-to-record pointers found zero that point at nothing, and each list's counter was left standing just past its last number so the next record continues the sequence instead of colliding with it.

One thing operations must not lose

The sealing key lives with the other server secrets (ID_ENCRYPTION_KEY). The database does not depend on it — the plain numbers are the truth — but the tokens the apps are holding do. Losing or changing the key does not lose data; it simply means yesterday's tokens no longer open, and apps treat them as unknown records and ask afresh. Keep the key backed up with the rest of the deployment secrets, and never rotate it casually.

Ready to grow your restaurant?

Join 50+ restaurants on MyCityDeal and start receiving bookings today.