# vault1984 — Commercial Onboarding Architecture

*Last updated: March 2026*

---

## Overview

The commercial onboarding flow connects Stripe payments to vault provisioning without violating zero-knowledge principles. The account system knows billing metadata — who paid, how many vaults they have, when they expire. It never touches vault contents, keys, or encrypted data.

Self-hosted users are unaffected. The commercial code path only activates when `vault_meta` exists in a vault's SQLite.

---

## Vault Filename Scheme

The vault filename is derived from the PRF master secret:

```
Master (32 bytes) from PRF
  L3 = bytes[0..32]   (full 32 bytes)
  L2 = bytes[0..16]   (first 16 bytes)
  L1 = bytes[0..8]    (first 8 bytes)
  filename = bytes[0..4] → base64url (no padding) = 6 chars
```

Filename format: `vault1984-AbCdEf` (prefix + 6 base64url chars, no extension).

- 4 bytes → 2^32 ≈ 4 billion unique vaults
- Reveals only 4 of the 8 L1 bytes — not enough to derive any key
- Deterministic: same hardware key always produces the same filename

---

## The Account System

A new service. Go + SQLite. Runs on HQ (Zürich). Handles billing, not security.

### What it stores

```
accounts:
  email              TEXT PRIMARY KEY
  stripe_customer_id TEXT
  created_at         TEXT

vaults:
  vault_id           TEXT  -- the "AbCdEf" part
  account_email      TEXT  -- FK to accounts
  region             TEXT  -- which POP
  created_at         TEXT
```

### What it does NOT store

- Encryption keys (L1, L2, L3)
- Vault contents or metadata
- Session state
- Anything that touches the zero-knowledge guarantee

### API

```
POST /checkout              Stripe Checkout session → redirect to Stripe
POST /webhook/stripe        Stripe confirms payment → create account
GET  /vault/{id}/status     Is this vault still paid? (called by POP on expiry)
POST /vault/create          POP registers new vault against account
POST /vault/{id}/delete     POP notifies vault deleted, frees capacity
```

---

## Consumer Flow ($12/year)

### Purchase

1. User visits vault1984.com → enters email → redirected to Stripe Checkout
2. Stripe collects payment ($12/year)
3. Stripe webhook → account system creates account (email + Stripe customer ID)
4. User shown: "Pick your region" → selects a POP

### Vault Creation

5. User lands on POP (e.g. `eu.vault1984.com`) → enters email
6. Registers passkey → WebAuthn PRF fires → 32-byte master derived
7. POP takes first 4 bytes → base64url → vault filename `vault1984-AbCdEf`
8. POP calls account system: `POST /vault/create` with email + vault_id
9. Account system checks: does this account have capacity? (consumer = max 1 vault)
10. Yes → records vault, responds with `expires_at`
11. POP creates SQLite file `vault1984-AbCdEf` with `vault_meta` row (account_email, expires_at)
12. Vault is live. User is unlocked. Registration = unlocked.

### Every Request (hosted vault)

1. Bearer token carries L1 (8 bytes)
2. POP derives filename from first 4 bytes → opens SQLite
3. Checks `vault_meta.expires_at` — if past, calls home (see Expiry below)
4. If valid → process request as normal

### Expiry & Renewal

- No per-request call to HQ. The vault checks its local `expires_at`.
- When `expires_at` is reached, the POP calls `GET /vault/{id}/status` on HQ.
- HQ checks Stripe subscription status → responds with new `expires_at` or "expired."
- Renewed → POP updates `expires_at` locally, proceed.
- Not renewed → 402 Payment Required. Vault data is intact but inaccessible.

### Vault Deletion

- Initiated through account management on vault1984.com (not the vault UI).
- Account site → strong auth → user deletes vault.
- Account system calls POP → vault SQLite deleted → vault record removed from account → capacity freed.
- 30-day money-back guarantee, no questions asked.

---

## Self-Hosted (Free, Elastic License)

No `vault_meta` table. No expiry check. No call home. No account system interaction. The vault binary works exactly as it does today. The commercial code path is inert.

---

## Account Management (vault1984.com)

Central control plane for billing and vault lifecycle. Separate from any vault UI.

- View account, payment status, invoices
- See owned vaults (vault ID + region)
- Delete a vault
- Manage Stripe subscription (cancel, update payment method)
- Strong auth required (passkey — same hardware key the user already owns)

---

## Hosted POP Architecture

The vault binary is the same binary self-hosters run. It has no management API, no delete endpoint, no inbound control surface. This is by design — self-hosters must never be exposed to hosted infrastructure concerns.

Hosted POPs run two processes:

```
vault1984        — port 1984, public internet
                   Dumb encrypted storage engine. Identical to self-hosted binary.

vault1984-mgmt   — Tailscale network only, no public interface
                   Management sidecar. Handles commands from HQ.
```

### Why Two Processes

The vault binary cannot have a delete endpoint. If it did, every self-hosted instance would have one too. Separating management into a sidecar means:

- The vault binary stays unchanged for self-hosters
- The management surface is only reachable via Tailscale (not the public internet)
- Self-hosters never install, run, or know about the sidecar

### Tailscale for Management

Tailscale provides the secure channel between HQ and POP sidecars:

- **Identity-based ACLs:** Only HQ's account system can reach `vault1984-mgmt` on any POP
- **Zero config on new POPs:** `tailscale up` with an auth key, done
- **No public exposure:** The sidecar binds only to the Tailscale interface
- **POPs can't reach each other's mgmt:** ACL policy enforces HQ-only access

```
Tailscale ACL (simplified):

  HQ account-system  →  vault1984-mgmt on any POP    ✅
  POP mgmt           →  POP mgmt                      ✗
  Anything else       →  vault1984-mgmt                ✗
```

### Management Sidecar Responsibilities

The sidecar is a small Go binary with a narrow API:

```
POST /vault/{id}/delete       Delete a vault SQLite file
POST /vault/{id}/extend       Update expires_at in vault_meta
GET  /vault/{id}/exists       Confirm a vault file exists
GET  /health                  Sidecar is running
```

It has filesystem access to the vault directory. It reads/writes `vault_meta` and can delete vault files. It never opens or decrypts vault contents.

---

## POP ↔ HQ Communication

| When | Direction | Channel | What |
|------|-----------|---------|------|
| Vault creation | POP → HQ | Tailscale | Validate account, register vault |
| Vault expiry | POP → HQ | Tailscale | Check renewal status |
| Vault deletion | HQ → POP | Tailscale (via mgmt sidecar) | Delete vault file |
| Expiry update | HQ → POP | Tailscale (via mgmt sidecar) | Extend `expires_at` |

No runtime dependency on HQ. POPs operate independently except at creation and expiry boundaries. All management traffic flows over Tailscale — never the public internet.

---

## Open Questions

- **Account management auth:** Passkey on vault1984.com — same authenticator or separate registration?
- **Grace period on expiry:** How many days past `expires_at` before 402? Immediate, or a buffer (e.g. 7 days)?
- **Deletion confirmation:** Sidecar deletes file → responds to HQ → HQ removes vault record. What if the file delete fails? Retry? Tombstone?
- **Future: second copy (read replica):** Noted as planned. Architecture supports it — vault_id is deterministic, same file can exist on two POPs. Sync model TBD.
