Case Study · Documentation
Surface.
An AI document vault for Ontario lawyers. Upload legal documents, ask questions in plain English, and get answers with verbatim citations pointing to the exact source paragraph - plus e-signing, PDF tools, and client intake, all on Canadian servers.
At a glance
Platform
Electron desktop (macOS + Windows)
Backend
Supabase edge functions
Retrieval
Cohere embed → pgvector → Claude
Data residency
ca-central-1 · Montreal
Overview
A thin client over a Canadian backend
Surface is a cross-platform Electron desktop app. The client itself is deliberately thin - a sandboxed React renderer talking to the main process only through a preload context bridge. Every piece of heavy or sensitive work (document ingestion, embeddings, RAG, e-signing, billing) runs in Supabase edge functions, so no API secret ever ships inside the app.
A lawyer signs in, connects their document and email accounts, and works inside matters. Questions are answered from the vault with citations, from live email context, or both - a Haiku router decides. The result is a tool that feels like chat but is auditable, scoped per matter, and compliant with the Law Society of Ontario's residency rules.
Architecture
Desktop shell → Supabase edge
Desktop client · Electron (macOS + Windows)
Main process
window · OS keychain
Preload bridge
context isolation
React renderer
HashRouter SPA
Backend · Supabase · ca-central-1 (Montreal)
Edge functions
secrets stay server-side
Cohere · Claude
embed · answer
Postgres 17 + pgvector
Storage · Auth
The renderer runs with context isolation on and Node integration off. The main process holds the OAuth token and fetches raw mail; the edge functions hold the secrets that must never leave the server.
Retrieval
The RAG pipeline
Ingest
process-document extracts text via Claude native document input, chunks it, embeds each chunk with Cohere, and stores chunks + pgvector embeddings in Postgres.
Route
route-intent, a tiny Claude Haiku classifier, reads recent turns and routes the question to vault, live, both, or chat so follow-ups resolve correctly.
Retrieve
query-document embeds the question with Cohere, runs a pgvector search scoped to the caller’s org (from the JWT), and pulls the most relevant chunks.
Answer
Claude composes the answer with verified verbatim citations, injecting per-chat short-term history plus the matter’s long-term ai_context summary.
Conversation memory
Two layers, injected without touching the cached system prefix so prompt caching stays intact: short-term is the last ~5 raw turns; long-term is matters.ai_context, a running ≤300-word summary refreshed every ~10 messages. A brand-new chat still remembers earlier work in the same matter, with no bleed between clients.
Backend
Edge functions
process-documentServer-side ingestion: extract, chunk, embed, store.
query-documentRAG answer with org-scoped pgvector search + citations.
route-intentHaiku classifier → vault / live / both / chat.
sync-emails / search-emails30-day mail cache, embedded and searched server-side.
sign-session / sign-submitPublic signing flow for getsurface.ca/sign/[token].
generate-signed-pdfpdf-lib stamps signatures + Certificate of Completion (SHA-256).
stripe-webhookSource of truth for subscription state.
oauth-tokenProxies Google OAuth so the client never holds the secret.
Trust
Security & compliance
Canadian data residency
All documents and data live in a Supabase project in AWS ca-central-1 (Montreal). The only text that leaves Canada is the excerpts sent to Cohere and Anthropic at query time.
Per-matter isolation
Row-level scoping and audit logging are enforced at the database level for LSO compliance. Retrieval is scoped to the caller’s org via their JWT, never the request body.
Secrets never ship
The desktop client is a thin shell. API keys for Cohere, Claude, Stripe and OAuth live only in edge functions. OAuth session tokens are stored in the OS keychain, never on our servers.
Hardened Electron
Context isolation on, Node integration off, ASAR with hardened Fuses (cookie encryption, RunAsNode disabled). macOS builds are Developer-ID signed + notarized with auto-update.
Built with