Architecture: what runs, and how to check it
Math Challenge runs entirely on Cloudflare. This page separates what is running today from what is only designed, and gives the command that proves each claim.
What is running today
math-challenge-web— A Worker running Astro with Static Assets, serving the seven locales and the routes behind them.math-challenge-ingest— A second Worker with no public route at all. It can only be reached through a service binding from the first one.math-challenge-db— A D1 database in the WNAM region. The jurisdiction is fixed when the database is created and cannot be changed afterwards — a door that closed on the day it was created, and that is written down rather than discovered later.- Two KV namespaces: short-lived session tokens, and feature flags with the content-catalogue cache.
- Two R2 buckets: media, and the cold archive that data-subject exports will be written to.
- Analytics Engine, bound and reserved for attempt telemetry and field performance. Nothing writes to it yet: one of these datasets only comes into existence the first time a Worker writes to it.
Everything else in the inventory is designed and not created. It is listed further down, by name, because a page that only shows the parts that exist is a brochure.
HTTP/3, QUIC and 0-RTT
HTTP/3 over QUIC is on for the zone, and so is 0-RTT resumption, which lets a returning visitor send the first request without paying for a fresh handshake. Neither is claimed from a documentation page: the two lines below were read straight from production, by the same check that runs after each deployment. A switch someone turns off in a dashboard leaves no trace in the repository — nothing in the code would notice.
alt-svc: h3=":443"; ma=86400
TLS 1.3 · Max Early Data: 14336 What this does not fix: HTTP/3 speeds up transport, not work. A heavy JavaScript bundle blocks the main thread over QUIC exactly as it does over TCP. That is why the weight budget further down matters more than the protocol does.
Native RPC between Workers
The two Workers talk to each other through a service binding, as typed method calls rather than HTTP requests. Cloudflare describes what that costs:
When you use Service Bindings, there is zero overhead or added latency. By default, both Workers run on the same thread of the same Cloudflare server.
RPC to another Worker (over a Service Binding) usually does not even cross a network. In fact, the other Worker usually runs in the very same thread as the caller, reducing latency to zero.
An RPC that does not cross a network cannot be beaten by one that does, however efficient its serialization. The whole path — first Worker, second Worker, D1 — answers on a public health endpoint, so it can be checked instead of believed.
Why not gRPC
gRPC was asked for by name. The research ruled it out on three independent facts, each sufficient on its own:
- Workers cannot make outbound gRPC calls. The runtime has no HTTP/2 bidirectional streaming, and an open issue in Cloudflare's own runtime repository tracks it.
- The browser does not speak gRPC. The web client implements a different protocol, falls back to HTTP/1.1 — which cancels out some of the advantages — and supports neither client-streaming nor bidirectional calls.
- HTTP trailers, which gRPC needs in order to carry status, have limited support at Cloudflare's edge proxy. Cloudflare documented that itself.
What was left is not a consolation prize. Adopting gRPC here would have meant adopting the previous generation with more work for less. The request and the evidence that overrode it are both in the public decision log — that is the point of keeping one.
Zero third-party requests
No font CDN, no analytics script, no embedded frame, no external stylesheet. Not one request leaves for a host that is not ours. This is checked against the HTML that production actually serves, not against our intentions.
Raleway is self-hosted as a variable font, in two files: latin and latin-ext. The second one is not optional — without it the ñ, ç, ã, õ and ü of five of our locales fall back to another typeface, precisely on the characters that make them those locales. And the reason that outweighs performance: loading a font from a third party hands that third party the address and the browser signature of every visitor, on the public pages of a children's product.
Inference runs inside the network
The 23 adversarial auditors already run on Workers AI, and the tutor is going there too: the inference stays on Cloudflare instead of on an external API. The call happens inside the Worker, with no external round trip, and the reference device is a low-end Android phone on slow mobile data, where latency costs as much as price does.
What that gives up is written down rather than glossed over. Workers AI's JSON mode is best-effort, so schema validation is ours to do. A verdict that fails validation counts as a failed auditor, never as a clean one — the cheap failure is a re-run, the expensive one is a violation that nobody caught.
Attempts never go to D1
D1 caps at 10 GB per database. One row per attempt would reach that wall by design mistake rather than by growth: the first hard limit of this architecture is hit by getting it wrong, not by succeeding. Raw attempts go to Analytics Engine; D1 keeps accounts, content metadata and roll-ups. A deterministic auditor blocks the commit that would forget it.
We measure ourselves, and never a child
Field performance is measured by us and never by a third party: an inline script of roughly 600 bytes reporting to an endpoint of ours, which writes to Analytics Engine — never to D1 and never to an outside service. Nothing in it identifies a person, and it is never loaded on a child's screen: the endpoint refuses those bands as well, so both ends fail closed. One auditor guards the code; the production check guards the other half, because the platform can inject a beacon of its own at zone level and that leaves no file to inspect.
What the pages weigh
Heaviest page: 9.5 KB gzipped, which is how it travels. All client JavaScript across the site: 2.6 KB. All CSS: 1.5 KB. The budget that blocks a commit is 12 KB per page and 60 KB for all client JavaScript together.
The performance budget, and what we do not claim yet
The budget is INP under 150 ms, LCP under 2.5 s, CLS under 0.1. 150 and not the usual 200, because this is a high-interaction game: a child taps many times per session and every tap is measured. That is the exact profile where INP breaks.
Said plainly: those are budgets, not results. No field measurement has run long enough for the auditor that enforces the thresholds to have anything to enforce. Until then this page claims no performance result at all.
The auditor fleet
8 deterministic auditors run on every commit, in milliseconds, and block it. 23 adversarial auditors backed by language models run before a pull request is opened, instructed to find the violation rather than to approve. 39 are planned in total; 8 are still waiting for the phase that enables them, and are listed as waiting rather than quietly dropped.
Two rules make them useful instead of noisy. Every auditor has to cite the decision or the research document it enforces — one that cannot point at a real identifier is opining, and its verdict does not block. And overriding an auditor requires writing down why, in a file committed alongside the change that needed the override.
What does not exist yet
The inventory is written before the objects exist, so that whoever creates one adds their line to the log in the same pull request. A resource created and not documented is a resource nobody will be able to delete a year from now. These are designed and not created:
- Durable Objects for leagues, classrooms and the per-child adaptive model — one object per league and per classroom, never one global object.
- Queues and Workflows for asynchronous scoring and for generating explanations.
- A Vectorize index for retrieval over the curated hint bank, scoped to curated content and never to per-child embeddings.
- The item bank itself, which is the product rather than a dataset.
Who operates this, and who provides it
Ignia operates the infrastructure. Every object named on this page lives in a Cloudflare account that Ignia runs — operating Cloudflare accounts, for its own products and for its clients, is part of what Ignia does. Math Challenge is an Ignia project, sponsored by Ignia.
Cloudflare provides the platform. Workers, D1, KV, R2, Analytics Engine and the network they run on are Cloudflare products, operated by Cloudflare. Ignia operates; Cloudflare provides. Two verbs, two companies, kept apart on purpose — and the reason to keep them apart is the same one this whole page is built on: the reader of an architecture page is the one who checks.
dig +short NS math.kilowatto.com
alberto.ns.cloudflare.com.
colette.ns.cloudflare.com. The other half cannot be looked up anywhere, and we say so. That this domain is served by Cloudflare, anyone can confirm. That Ignia operates the account is our own statement: there is no public register you could check it against. Saying that plainly is worth more than dressing it up, because one unverifiable claim is enough to make a reader stop verifying the rest.
What this page deliberately does not claim: that Ignia is a Cloudflare partner. We name no partner tier and show no badge, because there is no public directory where you could verify one. On a page whose entire promise is “and here is how to check it”, a claim that cannot be checked costs more than it is worth.
Ignia, in its own words: ignia.cloud
How to check any of this
- the HTTP/3 announcement in the response headers
curl -sI https://math.kilowatto.com/en/ | grep alt-svc- 0-RTT: the TLS 1.3 session ticket and its early-data allowance
openssl s_client -connect math.kilowatto.com:443 -tls1_3 -sess_out s.pem openssl sess_id -in s.pem -text | grep "Max Early Data"- the RPC path: first Worker, second Worker, D1
curl -s https://math.kilowatto.com/api/health- the weights quoted above, from the repository
node audits/bundle-budget.mjs
Sources
- About Service bindings — Cloudflare Workers docs
- We've added JavaScript-native RPC to Cloudflare Workers
- Support HTTP/2 bidirectional streaming (gRPC) in Workers/Durable Objects — cloudflare/workerd issue #6455
- gRPC Web (PROTOCOL-WEB) — gRPC core documentation
- Road to gRPC — Cloudflare blog
- HTTP/3 (with QUIC) — Cloudflare Speed docs
- D1 Platform limits — Cloudflare docs
- JSON Mode — Cloudflare Workers AI docs
Math Challenge is a project by Ignia, and it runs on Cloudflare. Those are two claims and they are kept apart on purpose: saying that Ignia provides the stack would be disproved by a single DNS lookup, and the reader of an architecture page is exactly the person who would run one. ignia.cloud