# The Double Blind — verifiable privacy

Witbitz's core privacy claim: for an app built on it, **neither the platform operator nor the app's own owner can
read the users' data**. And — the part that makes it a *property* not a promise — that claim is meant to be
**checkable from the code**, not taken on faith. This doc states what holds today, and what is north-star.

> See also: [async Spaces](./async-spaces.md) (the sealed ledger + decrypt-once render) and
> [the Space link is the auth](./room-link-auth.md) (the keys live in the link).

---

## 1. Ciphertext at rest → the platform is blind

A Space's data is a snapshot **sealed under the room key `mk`** and stored as ciphertext; the platform never holds
`mk` (`agent/envelope.mjs` `seal`/`open`; the key is committed only as `commit(mk)`, [room-link-auth](./room-link-auth.md)).
At rest, to the operator, a Space is opaque bytes — storage and transport are **content-blind**.

There is **no operator recovery key.** Every record is sealed to the **room key alone** — the platform writes no
support or recovery recipient of any kind, so there is nothing readable-by-the-operator to read at rest. The double
blind is the only configuration that runs in production.

## 2. The decrypt-once render → the one plaintext door

Plaintext exists in exactly one place: a **transient turn function** that opens the ledger with a *present*
member's `mk`, runs the declared program (the model), re-seals, and **drops `mk`** ([async Spaces §3](./async-spaces.md)).
The key reaches compute only from a member's client, per active window — never from storage. So the operator can
act on the data only *while a member is present and only by running the declared program*, and learns nothing at
rest. The AI is the door where plaintext opens — and that door can be closed further (on-device / customer-owned
weights / an attested box) as a higher tier.

## 3. Backendless → the app owner is blind too

The second blind is the one nobody else offers. A Witbitz app is a **static page with no backend of its own** that
connects *only* to the audited Witbitz API. So the app's *developer/owner* has no backend through which to read its
users either: the users' content is sealed to a key the owner doesn't hold, and the app has nowhere else to send
it. Data is encrypted **from** the owner, not merely **to** them.

To be exact — "backendless" is a claim about the *app*, not the platform. **Witbitz is still a backend** (one
shared, constrained, inspectable platform). What's removed is each developer's *own, opaque, app-specific* server.
So the trust surface isn't smaller in a hand-wavy "no infrastructure" sense — it's **consolidated**: one auditable
platform to check, instead of N unknown app backends. That consolidation is exactly what makes the footprint
declarable (§4).

## 4. Verifiable → check the code, not the operator

Because the app is a static page with a declared egress (it can only talk to the Witbitz API), its **entire data
footprint is inspectable and declarable**. The north-star is that Witbitz **derives and signs the true data
footprint from ground truth** — a pinned build hash + a locked egress allowlist — so the app's privacy page is
*structurally grounded*: the bytes certified are the bytes you run, and "what data goes where" is machine-derived,
not marketing copy. You verify the code + a signature, not a "trust us" policy.

**What you can check *today* vs the north-star.** That signed certificate is designed, not issued — so full
"check the footprint" verifiability isn't here yet. But the **enforcement** layer already is: **[Verify it yourself](./verify.md)**
walks you through reproducing the admission gate in your own terminal (token-less read/write → `403`). It checks that
the gate *enforces*, not that the operator is blind at rest — that honest boundary is the point. As the certificate
and attested tier ship, their checks join that same page.

## 5. Status — what holds today vs north-star

Being precise, because this is exactly where a claim can overreach:

| Property | Status |
|---|---|
| Ciphertext at rest (sealed ledger, platform holds no key) | **shipped** — live in async Spaces |
| No operator recovery key (sealed to the room key alone) | **shipped** — content-blind at rest, verified in prod |
| Decrypt-once render, `mk` from the client only, dropped after | **shipped** |
| Backendless static app (no backend of its own) | **shipped** — the Spaces app is a static page |
| Egress-lock (CSP `connect-src`/`script-src` allowlist) | **shipped** as report-only (a block-mode flip is the next step) |
| Client E2EE primitive (the sealed-blob envelope) | **shipped** |
| Signed privacy **certificate** (derive + sign the true footprint) | **designed** — north-star, not yet issued |
| Attested/on-device inference tier (operator provably blind even for big models) | **designed** — a higher tier |

So today: the platform is content-blind at rest and the app is backendless — real, shipped guarantees. The
*signed, independently-verifiable certificate* that turns "we can't read you" into "you can check we can't" is the
in-progress north-star (two of its blocks — egress-lock report-only and the E2EE primitive — have shipped).

**Roadmap honesty.** Closing the render's *"in use"* half **verifiably** — the certificate first, the attested tier
after — is the privacy claim's **next milestone, and it should lead the Companion**. The Companion adds notifications
+ device-side key custody + local decrypt for the *peer* path; it does nothing for the server render, which is
exactly where the load-bearing conditional ("runs the declared program") lives. So the Companion ships for its own
value, not as evidence the double blind holds.

## 6. The moat — honestly

The moat is **not** "incumbents *can't* do this." They can: Apple's Private Cloud Compute ships attested confidential
inference today, and a competent team could match everything Witbitz has *shipped* in a quarter. Two things are
actually defensible, and only one of them is finished:

- **Neutral + backendless (shipped).** A vendor cloud reads *to itself* by default; a Witbitz app has no backend of
  its own and isn't bound to one provider's stack. The trust surface is **consolidated to one auditable platform, not
  removed** — the honest claim, *not* "no backend" (Witbitz **is** the backend, singular and inspectable). That's
  awkward for an incumbent whose business *is* the readable backend to adopt — but it isn't something they're *barred*
  from.
- **The verifiability layer (unbuilt — the real moat).** The **signed certificate** (derive + sign the render's true
  footprint) and the **attested-inference tier** (operator blind even in use) are what turn *trusted in use* into
  *checkable in use*. Both are designed, not shipped. Until they are, verifiable privacy is a **direction we're
  building toward**, not a finished barrier — and we say so rather than imply the line is already uncrossable.

## 7. Code map

| Concern | Where |
|---|---|
| At-rest ledger seal (**symmetric, under `mk` only**) + key commitment | `agent/envelope.mjs` (`seal`, `open`, `commit`, `newRoomKey`); `agent/sessionStore.mjs` `encodeMemory` — one recipient, `mk`. **No operator/beta/admin recipient in prod.** |
| Recipient sealing (**ECDH `sealTo`/`openBox`**) — seals to a **member/agent** public key, *never an operator* | scoped agent reads (`agent/spaceScopedView.mjs`), Bridge epoch keys (`agent/bridgeMembership.mjs`), the collection-blob — a **separate** mechanism from the at-rest ledger above |
| Sealed ledger + decrypt-once render | `agent/sessionStore.mjs`, `agent/asyncTurn.mjs` |
| Content-blind poll (etag over ciphertext) | `agent/spacePoll.mjs` |
| Egress-lock CSP (report-only) | the app deploy (`gen-csp.mjs` pattern; per-app) |
| Certification design | repo `docs/privacy-certification.md` (north-star) |
