How it’s built

Why nothing here is real, by construction

A demo that anyone may change, and that a recruiter may open on a borrowed laptop, has to be safe for the visitor and for the next one. This is how every sandbox is kept apart, why the bakery holds no personal data because it has nowhere to put any, and what that costs: a bakery in which nothing is real, the customers who open campaigns included.

A bakery handed to a stranger

A visitor signs in, opens the console and changes the price of the cardamom bun. Another visitor, at the same minute, cancels every order on the board. A third types a phone number into an order note. None of them should see the others’ work, and nobody should be left holding something that names a person. The bakery is a business system, and business systems are full of customers’ names, addresses and notes (docs/adr/0005), so a demo that works like one has to decide where each of those goes.

What a simpler design does, and where it breaks

The shared demo is the usual first design: one bakery, one database, every visitor in it. Anything one person types is shown to the next, so it needs moderation, and anything personal that lands in it stays until someone finds it. The second design keeps one shared set of tables and adds a tenant column to every row, with a filter on every query. One query without its filter shows every visitor everything, and a wipe has to find the rows by that column. No lab page shows this one yet. The lab’s index is where the lab explains the family of techniques around it, and the site’s rules for every demo, read on 2026-11-06, are what the bakery keeps: made-up data, no free text at first, and nothing that outlives the visit.

A name that carries its sandbox

The bakery has no shared table to filter. A sandbox is a set of Durable Objects, each with its own SQLite database, and every object’s name is built by the Worker from the sandbox id in the signed session or token: catalog:<sandbox>, schedule:<sandbox>:<date>, order:<sandbox>:<ulid> (docs/architecture.md, “Object naming”). A client never names an object. Cloudflare’s namespace documentation, read on 2026-11-06, addresses an object by an id derived from its name, so a name with another sandbox’s id is another object. A path that names another sandbox gets the 404 of a name that exists nowhere, and each object stores its sandbox id when it is born and refuses a mismatch.

Born only through the Sandbox. The Sandbox object keeps a registry of its children (workers/api/src/objects/sandbox.rs). A child is written into the registry before it is first written, in the same request, and the Sandbox hands it a Birth (child.rs): its name, the sandbox id and the clock. An object with no meta row refuses every command with 410, so a guessed name or a request after the wipe summons nothing. That is why the wipe has one list to work from, and it cannot miss an object the sandbox created.

Made up, and the same every time

crates/seed builds the whole starting state from the sandbox’s seed and birth date with a seeded ChaCha20 generator: 60 customers, 6 venues, 64 delivery places, 90 days of closed orders, 12 open orders and 8 catering events (crates/seed/src/lib.rs). Two sandboxes from one seed and date are identical byte for byte, so a bug report can name its seed. A customer is a made-up word pair, a plant or a weather word with a landform or a craft, such as Fennel Talus (words.rs). A delivery address is not an address at all: p1 to p64, mapped onto the public places of the map by index. No free text is seeded.

No personal data is an absence of places to put it

The bakery does not promise to be careful with personal data. It has no column for it. The Account object’s tables hold an id, a kind, dates, the current sandbox, the ids of revoked sessions, and a demo account’s generated username and password hash (account.rs). Sign-in stores the id as a keyed hash: HMAC-SHA256 of the provider’s number for the account, with a key held as a Worker secret (docs/adr/0006). Google is asked for the openid scope alone and GitHub for no scope, and if a provider ever sends an email, a name or a picture, the callback drops them before anything is stored or logged.

Mail has nowhere to go. The bakery has no address to send from or to, so every message is a row in the sandbox’s Inbox, whose recipient is a customer id and not an address. The Recipient type has private fields and no conversion from a string, and two compile_fail tests show that code naming an address does not build (crates/app/src/ports.rs, docs/security.md).

Two free-text fields, checked as written. The order note is 140 characters and the catering brief 500 (crates/domain/src/free_text.rs). Both are normalised to NFC and then refused, with a rule such as brief-email or brief-phone and never the text, when they hold an email shape, 7 or more digits, 13 to 19 digits or a URL. The consumer removes both from every event headed for the CRM or the Inbox. Logs carry ids, counts and slugs, and a request’s address only as HMAC(daily salt, IP); the salt is made in memory and never written, because Cloudflare, as read on 2026-11-06, can restore deleted storage for 30 days.

The wipe, from the one list

At 10:00 UTC a cron asks Control to stamp each sandbox due, and Control wipes them 50 a run from its own alarm (wipe.rs, control.rs). Each Sandbox wipes its registered children, deletes its alarm and runs deleteAll(); Billing first cancels, voids and deletes the Stripe objects it created, and each run ends in the wipe data point. Every object’s own 36-hour backstop covers a wipe that fails. Deleted is not erased: Cloudflare’s documentation says recovery reaches back 30 days, and nothing here calls it.

What it costs

Try it

Open the console: it shows your sandbox’s id, its seed and when it will be wiped, and Wipe now ends it on the spot. Read the privacy page, then the answer to GET /v1/session, which is the whole of what the bakery knows about you. In the catering form, type a made-up email into the brief and read the refusal: it names the rule and does not repeat what you typed.

Sources

Back to the sandbox principle on How it’s built, to no personal data, to the decisions that chose them, ADR 0005 and ADR 0006, or to the lab.