DocumentsProcurement and compliance

Data residency

2 min readSource data-residency.mdsha256 c674bb77ebf55815…

The operator processes and stores everything it receives in the EU, and says so in a signed record you can check. What it receives is commitments, signatures and governance data. Trade values never reach it, because the raw-data wall refuses them.

Where things are#

DataWhere it is storedWhere it is processed
Trade values, quantities, bands, corridor secrets, documents, customer identitiesInside each member bank only. Never sent to the operator, enforced by the raw-data wall.Inside the bank (the bander, which has no network)
Commitments, signatures, governance events, the verdict ledger, webhook stateSupabase Postgres, AWS eu-central-1 (Frankfurt)A Cloudflare Durable Object restricted to the EU jurisdiction
This site's static filesCloudflare PagesCloudflare's network

How processing is pinned#

  • Every request goes to one object. The Worker at the edge forwards each API request to a single Durable Object created with Cloudflare's eu jurisdiction. Cloudflare guarantees that such an object runs and persists only in EU data centres.
  • The edge does not read the request. It forwards the body unread; only the rate limit and the size check happen there.
  • The object checks its own jurisdiction. It reads the jurisdiction from its own id at runtime. It does not rely on configuration saying so.
  • It fails closed. If the EU object cannot be reached, the request is answered 503 unavailable and nothing is processed anywhere else.

Check it yourself#

GET /residency returns a signed mirror.residency record:

{
  "processing": { "platform": "cloudflare durable object", "jurisdiction": "eu", "evidence": "runtime", "observed_colo": "FRA" },
  "storage":    { "platform": "supabase postgres on aws", "region": "eu-central-1 (Frankfurt)", "jurisdiction": "eu", "evidence": "declared" },
  "edge":       { "colo": "MXP", "body_read_at_edge": false, "note": "..." },
  "commitment": "Processing and storage of everything this operator receives are in the EU jurisdiction; ...",
  "not_covered": ["the first edge hop ...", "..."]
}

Verify it against /jwks like any other record. Then run the fail-closed check, which ships in the verification kit and in every SDK:

MirrorKit.residency.meets(record, "eu")   // { ok: true, reasons: [] }
  • Processing passes only if the jurisdiction was observed at runtime. A jurisdiction that is merely declared does not pass.
  • Storage must be in the same jurisdiction.
  • Anything missing, unknown or malformed fails, with the reason.

A Node host, or the Supabase Edge Function, reports its processing as declared or not pinned, and so it does not meet eu.

GET /health also says, in plain words, whether processing is pinned.

What the commitment does not cover#

The record lists these itself, under not_covered:

  • The first edge hop. The Cloudflare data centre that first receives a request can be anywhere. TLS ends there, and the request is forwarded unread. Pinning that hop too needs Cloudflare's Regional Services, a commercial product this rail does not use.
  • Durable Object ids. Cloudflare logs them outside the jurisdiction for billing and debugging.
  • Storage is declared, not observed. The operator cannot ask its database where it lives. The region is in the sub-processor register.
  • Anything a member sends elsewhere. A member's own systems, and the bander, are outside the commitment.

Commitments#

  • The EU commitment can be a contract term: Schedule C. It is a draft for counsel, which no bank has signed.
  • A member's own data never leaves the member. That is a property of the design, not a residency promise that could change.
  • Other jurisdictions. The same mechanism supports us and fedramp objects. No such deployment exists, and none is offered until one does.

What the operator can possibly hold is the refusal list.