# 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.