← Back to home
● Technical brief · v1.0

Built to stay inside your trust boundary — and prove it.

A reference for the engineering, IT, and security teams evaluating Mise. It opens with the security posture and the controls behind it, then walks through tenant isolation, architecture, the data model, integrations, deployment topology, evaluations, and SLAs. Everything in this document is contractable.


1. What Mise is

Mise is a tenant-isolated reasoning service for multi-unit restaurant franchisees. It ingests POS, labor, inventory, financials, and food-safety signals from the systems your stores already run, layers your brand-standard documents on top as a private knowledge base, and answers your above-store leaders' questions with grounded citations.

Mise is read-mostly. The only writes it performs are inside its own tenant database — alerts, snoozes, assignments, and the audit log of who saw what and when. It does not write back into your POS, your scheduling system, or your ERP.

Trust boundary, in one line. Mise reads from your systems through credentials your team grants, stores the results in a Postgres database dedicated to your tenant, and surfaces them to your team through chat and a web UI. It never proxies writes into vendor systems on your behalf.

2. Security posture

Security is the load-bearing property of Mise — every other section of this brief assumes the controls described here are in place. Your data stays inside your trust boundary, your credentials stay encrypted, and the path between Claude and your systems is audited end-to-end.

Encryption

  • In transit: TLS 1.2+ on every external connection. Caddy terminates with HSTS preload, OCSP stapling, and modern cipher suites only. HTTP traffic is rejected, not redirected.
  • At rest — database: The Postgres data volume is encrypted at the host level. Backups inherit the same encryption.
  • At rest — secrets: Every OAuth client secret and refresh token is encrypted with AES-256-GCM using a per-record nonce. The data-encryption keys are wrapped by a master key held in the host's secret store and never written to the database. Secrets are decrypted in memory only when an outbound API call needs them, and never logged.
  • Document blobs: Uploaded manuals and photos sit in object storage with server-side encryption and signed-URL retrieval. URLs expire in minutes, not hours.

Authentication & access control

  • User auth: Email + password with bcrypt (cost 12) and JWT-issued sessions today. SAML/SCIM provisioning is on the roadmap and gets built against your IdP for the first enterprise customer that needs it.
  • Session JWTs: Short-lived (15 min) access tokens with refresh-token rotation. Tokens are bound to your org_id on issue and re-validated on every request.
  • Role-based access: Roles are scoped to your org. A user belongs to exactly one tenant, and the role gate sits in front of every controller — not just the ones a developer remembered to protect.
  • Vendor credentials: Every integration is BYOA (bring your own app). The OAuth client lives in your vendor portal under your control. Revoking it kills Mise's access immediately, and Mise re-confirms token validity on every poll.

Auditability

  • Append-only audit log: Every authenticated query, every export, every settings mutation, every alert action lands in audit_log. The table has no UPDATE or DELETE permission for the application role; rows can only be inserted. 7-year retention.
  • Structured request logging: SLF4J → JSON to stdout, with org_id, request_id, and user_id on every line. A Logback filter scrubs known sensitive field names (password, token, secret, ssn, card) as a backstop against accidental leakage.
  • Reasoning trace: Every answer is paired with its retrieval set, the SQL templates it executed, and the slot values it used. Your team can review exactly what data was touched to produce any given answer.

Application security

  • Dependency hygiene: GitHub Dependabot + mvn org.owasp:dependency-check run on every CI build. Critical CVEs block the build.
  • Base image refresh: The runtime container is rebuilt weekly from the latest patched base image, even with no code changes.
  • Static analysis: SpotBugs and ArchUnit rules run in CI. ArchUnit enforces the tenant-isolation invariants described in section 3 — a build with a query that bypasses org_id fails before merge.
  • Penetration testing: Independent third-party pen-test scheduled annually starting at the first enterprise customer. Findings are remediated and the report is shared with your security team under NDA.
  • Secret management: No secrets in code, no secrets in git. Production secrets live in the host's secret store and are surfaced to the application as environment variables at boot.

Operational security

  • Single-tenant deployments: Your data lives on your application instance, with your encryption keys, on your dedicated database. There is no multi-tenant SaaS pool mixing customer data.
  • Backups: Nightly Postgres base backup + WAL archive every 5 minutes. RPO ≤ 5 minutes, RTO ≤ 30 minutes. Backups are encrypted, off-site, and tested with a quarterly restore drill.
  • Disaster recovery: Documented runbook to restore a tenant from cold backup to live in under 60 minutes.
  • Incident response: Confirmed customer-impacting incidents are reported to your security contact within 24 hours, with a written post-mortem within 14 days.
  • Sub-processor list: Hetzner (hosting), Cloudflare (edge), Anthropic (LLM, configurable per-tenant), Sentry (error tracking). Mise notifies your security contact in advance of any sub-processor change.

Data handling commitments

  • No cross-tenant analytics. Your data is not used to train, fine-tune, or improve any model that another tenant's queries touch.
  • No model training on your data. Anthropic API calls run with X-Anthropic-Privacy: do-not-train equivalents enabled per the provider's enterprise privacy terms.
  • Customer-controlled deletion. A signed deletion request triggers a documented purge across the database, blob storage, and all backups within 30 days (90 days for cold backups).
  • One-click export. Stores, alerts, audit history, and uploaded documents export as a portable bundle (CSV + PDF + original filenames) on demand and on contract termination.
  • DPA on file. Mise signs a standard Data Processing Agreement before production access. Custom-clause negotiation is supported for enterprise contracts.
  • Compliance roadmap. SOC 2 Type I within 12 months of first paying customer, Type II within 24 months. The control matrix is shareable under NDA today.

3. Tenant isolation — the non-negotiable

Tenant isolation is enforced at three independent layers. Each layer is sufficient on its own for many threat models; together they create a defense-in-depth that fails closed.

  1. JWT & TenantContext. Every authenticated request carries an org_id in its JWT. An auth filter writes it to a request-scoped TenantContext before any controller runs. Code outside the filter cannot see anonymous or cross-tenant requests.
  2. Repository discipline. Every JPA repository method takes org_id as a parameter, drawn from TenantContext. Plain @Query without an org_id clause is forbidden, and a CI ArchUnit rule fails the build if one slips in.
  3. Postgres row-level security. The application's runtime DB role has RLS enabled. Multi-tenant tables carry USING (org_id = current_setting('app.current_org')::uuid) as a database-level net beneath the application — even a compromised application role cannot read across tenants.

Vector retrieval has the same rule: the retriever refuses to run without an org_id filter, and after retrieval every returned chunk's org_id is re-validated against the request context before it reaches the synthesizer. A document chunk from another tenant cannot reach your prompt.

Why three layers, not one. Each layer fails in a different way. The application layer catches developer mistakes. RLS catches a compromised application role. Repository discipline catches both, in CI, before code ships. Removing any single layer would still leave a defensible system — keeping all three is the cheap part of the design.

4. Architecture

Mise is a single Spring Boot service backed by Postgres + pgvector and Redis. There is no Kafka, no Kubernetes, no service mesh. Boring on purpose: fewer moving parts means a smaller attack surface and a shorter incident MTTR.

Read path

A question enters through chat or the web UI. The router classifies it as either a metric question (handled by the NL→SQL plane) or a knowledge question (handled by RAG). Most real questions cross both, so the synthesizer composes results from each side and emits an answer with inline citations.

HTTP request
  → AuthFilter (JWT → TenantContext.org_id)
  → Router (metric? knowledge? mixed?)
  → SQL plane          RAG plane
    ─ template registry  ─ vector search (pgvector, org-scoped)
    ─ slot validation    ─ BM25 + RRF fusion
    ─ Postgres execute   ─ bge reranker
  → Synthesizer (LLM call w/ tool results + retrieved chunks)
  → SSE stream → client

Write path

Ingestion is one-way. Webhooks (Toast, Jolt) and pollers (7shifts, Square, Clover, Shopify, Lightspeed, QuickBooks, Deputy) land raw events, which a normalization stage maps into provider-agnostic warehouse tables. Documents follow a separate path: PDF/DOCX upload → chunking → embedding → indexed in pgvector.

Components

Application

Java 21 · Spring Boot 3

Spring Web MVC + SSE for streaming answers. Tenant context is set on every request from a JWT-derived org_id; repositories refuse to run without it.

Storage

Postgres 16 + pgvector

Operational tables, document chunks, and 1024-d embeddings live in one database. Row-level security enforced at the DB role; the application layer holds belt + suspenders.

Cache & queues

Redis 7

Soft holds during ingestion, rate limits, async jobs. No durable message queue — recovery is built around replaying from the source of truth.

LLM

Anthropic (managed) or self-hosted

Reasoning calls go through a single LlmClient interface. Production code never references a specific provider directly. Self-host target: Qwen 2.5 32B-Instruct on Ollama for tenants that require it.

5. Data model

Operational data lives in provider-agnostic warehouse tables — sales_daily, labor_daily, inventory_snapshots, financials_monthly, food_safety_events. Each row carries org_id, store_id, the date, the source (square, clover, toast, …), and the normalized metric. The SQL plane reads from these, never from raw provider tables.

Documents are split into chunks (≈800 tokens, 120-token overlap) and indexed with both a 1024-d embedding and a BM25 column. Each chunk stores its org_id, document_id, page_number, and a stable citation_anchor so the UI can deep-link.

TableHoldsTenant keyRetention
storesStore records, addresses, brand, GMorg_idLifetime of contract
sales_dailyNet sales, tickets, daypart splitsorg_id + RLS5y rolling
labor_dailyHours, OT, scheduled vs actualorg_id + RLS5y rolling
document_chunksEmbedded prose from your manualsorg_id + RLSUntil document deleted
alertsGenerated by rule engine; in-memory overlay for snooze/ack/assignorg_id + RLS2y rolling
audit_logAuth, queries, mutations, exportsorg_id + RLS, append-only7y

6. The reasoning plane

Mise does not let the model write SQL freely. Free-form NL→SQL is the kind of feature that demos well and breaks in production — wrong joins, missing tenant filters, queries that lock a table during peak. Instead, the router picks a named template from a versioned registry (in docs/sql-templates/) and fills typed slots.

Slot values are validated against types and an allowlist before the query runs. A store_id slot must resolve to a store in the caller's org; a date_range slot must be bounded; a metric slot must be one of the registered metrics. Anything else is rejected before SQL is touched.

// Example: "How did Aurora compare to plan last week?"
template_id: store_vs_plan_weekly
slots:
  store_id: 7e3c-... (validated: belongs to org_id)
  iso_week: 2026-W16 (validated: ≤ today)
  metric:   net_sales (validated: in registry)

RAG retrieval runs in parallel: vector search (pgvector cosine), BM25 (Postgres tsvector), reciprocal-rank fusion, then a bge-reranker pass to drop chunks that don't actually answer the question. A distance floor (currently 0.55) keeps low-relevance chunks out, and the citation list is filtered post-hoc to only the chunks the LLM actually referenced in its answer.

7. Integrations

All integrations are BYOA — bring your own app. Your team registers a developer app in the vendor's portal and pastes the client ID and secret into Mise's settings. Tokens are encrypted at rest with AES-256-GCM, keyed off a master key held in the host's secret store, and decrypted in memory only at request time.

VendorDomainMechanismScopes
SquarePOS / paymentsOAuth 2.0Read-only: payments, orders, locations
CloverPOSOAuth 2.0Read-only: orders, payments, employees
ToastPOSAPI key + webhookRead-only: orders, checks, sales summary
ShopifyPOS / e-commOAuth 2.0Read-only: orders, locations, inventory
LightspeedPOSOAuth 2.0Read-only: sales, items, locations
7shiftsLaborAPI key (poll)Read-only: shifts, time punches
DeputyLaborOAuth 2.0Read-only: roster, timesheets
QuickBooks (Intuit)FinancialsOAuth 2.0Read-only: P&L, COGS, payroll
JoltFood safetyWebhookRead-only: tasks, photos, temp logs

Every connection records the org, store, granting user, granted scopes, and last-refreshed timestamp. Revoking the app in the vendor's portal kills Mise's access immediately; token validity is re-confirmed on every poll, with failures surfaced in your settings page within minutes.

8. Deployment topology

Each customer is a single-tenant deployment by default — your data, your application instance, your encryption keys. The reference target is a Hetzner CCX23 (4 vCPU, 16GB) with Caddy + systemd; a single VM is sufficient through ~60 stores. Beyond that, the deployment scales vertically to CCX33 / CCX43, then splits read replicas. There is no multi-tenant SaaS pool that mixes customer data.

┌─────────────────────────────┐
│  Caddy (TLS, HSTS, HTTP/3)  │
└──────────────┬──────────────┘
               │ :8080
┌──────────────▼──────────────┐
│   Mise (Spring Boot, JVM)   │
└──────────────┬──────────────┘
               │
   ┌───────────┴───────────┐
┌──▼───┐               ┌───▼───┐
│ pg16 │               │ redis │
│+pgvec│               └───────┘
└──────┘
   │
┌──▼───────┐
│ R2 blobs │  (uploaded docs, photos)
└──────────┘

Deploys flow through GitHub Actions → SCP → systemctl reload. Migrations run via Flyway on startup; schema is forward-only. A bad migration is rolled forward with a compensating migration, never backward — so the tenant database never sits in an indeterminate state.

9. Evaluation & quality

The reasoning plane has a versioned eval suite (docs/eval/cases.json). Every change to a SQL template, a prompt, or the retrieval pipeline runs against the suite in CI. New features add 1–2 cases. Cases come in three flavors:

  • Routing — does the router pick the right plane (metric / knowledge / mixed)?
  • SQL templates — does the slot-filler choose the right template and pass type-valid slots?
  • RAG groundedness — does the synthesized answer cite chunks that actually contain the supporting evidence?

Groundedness is scored by a separate LLM judge. Three numbers are tracked on the internal dashboard: citation precision (cited chunks that are actually relevant), citation recall (relevant chunks that were cited), and refusal rate on out-of-scope questions. A regression on any of the three blocks the deploy.

10. SLAs & ops posture

  • Availability: 99.5% monthly for v1, measured from a synthetic check that hits the answer endpoint every 60 seconds.
  • Latency target: p95 < 4s for typical questions (cached snapshot + small RAG pass).
  • On-call: One engineer on rotation, paged via PagerDuty. Sentry catches errors; UptimeRobot catches outages.
  • Status page: status.mise.to — incidents posted within 15 minutes of detection.
  • Data export: One-click export of your stores, alerts, audit history, and uploaded documents. CSV + PDF for tabular data, original filename for documents.
  • Termination: 30-day deletion window after termination, 90 days for backups. A final export is delivered before the data is purged.

11. What Mise does not do

The most useful part of a brief is what's not inside the box. Mise does not:

  • Write back into your POS, scheduling, or ERP. (No order edits, no shift adjustments, no journal entries.)
  • Auto-resolve alerts. The model surfaces; humans decide.
  • Run cross-tenant analytics. Your data is isolated and never used to train a model that any other customer's queries touch.
  • Replace your BI stack. Mise answers narrow operational questions well; it is not a self-serve dashboard builder.
  • Speak to your guests. No CRM features, no marketing automation, no loyalty.

Engineering, IT, and security teams reviewing Mise can request the full security packet — DPA template, sub-processor list, control matrix, pen-test summary, and a sandbox connected to a synthetic POS — by booking a working session: calendly.com/max-mise/intro-call-for-mise.