Concepts

The Ellivate contract

Ellivate is opinionated: it picks the database, the auth, the session format. If your code fits, publishing is a single verb. If it doesn't, we fail loudly and tell you exactly what to change — never silently misbehave.

Why there's a contract

Ellivate is a *shared-shell platform*, not a hosting provider. Every app runs inside the same identity system, the same persistence layer, and the same mobile super-app experience. That shared shell is what lets a user open Ellivate on their phone and see every app their circle has shipped, without logging into each one separately.

For that to work, apps have to agree on a few things. We call that agreement the contract. It's enforced in three places in parallel:

  • The MCP server teaches your LLM the conventions while the code is being written.
  • The publish pipeline rewrites non-conforming code into conforming code where it can (storage calls, missing SDK files, host bindings).
  • Invariant checks reject publishes that still don't fit, with exact file+line pointers to the violation.

§1 — Identity

Ellivate handles sign-in. Your app never does.

What the contract says

  • No third-party auth libraries: next-auth, @clerk/*, @auth0/*, firebase/auth, Flask-Login, hand-rolled JWT cookies for sessions — all rejected.
  • No /login, /signup, or /auth/callback routes. Sign-in happens in the Ellivate shell, before the user ever reaches your app.
  • Viewer identity is read from a single source, depending on your runtime:
    • ellivate_viewer_token cookie (Next.js server components, Flask, FastAPI)
    • X-Ellivate-Viewer-Token header (server-to-server)
    • window.ELLIVATE_VIEWER_TOKEN (browser, mobile WebView)
    • URL fragment #__ellivate_auth=... on first page load (for vanilla HTML)
  • The viewer object your code sees: { id, email, username, verified }.

Public vs gated apps

Two independent axes control access:

FlagControls
isPublicWhether the app shows up on your public profile / discovery pages.
requiresAuthWhether visitors must be signed in to reach any route.

All four combinations are legal: private+gated (default), public+gated (Notion-workspace pattern — listed on your profile, but visitors have to sign in), public+open (a demo anyone can try), or private+open (rare — "obscure but unauthenticated").

§2 — Persistence

All user-generated data goes through Ellivate's cloud KV. Filesystem and browser-local state don't survive the platform's natural boundaries (redeploys, device switches, session expiry).

What the contract says

  • No localStorage, sessionStorage,IndexedDB, AsyncStorage, or persistence plugins (redux-persist, zustand/persist, pinia-plugin-persistedstate) for user data.
  • No filesystem writes for user data: fs.writeFile, sqlite3.connect, shelve.open, pickle.dump, sqlalchemy for app state.
  • Keys are plain strings; values are anything JSON-serializable.
  • Scope is picked by the classifier at publish time, or by explicit key prefix:
    • personal: — scoped to the viewer. Each user has their own copy.
    • shared: — scoped to the share group. Everyone with access sees the same data.
    • public: — readable by anonymous visitors on public apps.
  • If your app genuinely needs a relational database (joins, transactions, full-text search), the classifier emits a loud warning and publish pauses. We don't smuggle sqlite into a container that gets wiped on redeploy.

§3 — SDK integration

The Ellivate SDK is a local file written into your project at publish time. It is NOT an npm or pip package — installing it from a registry would just 404.

What the contract says

  • JavaScript / TypeScript: ellivate-client.js and ellivate-client.d.ts land at your project root. Import with a relative path — import { ellivate } from "../ellivate-client". Bare specifier "ellivate-client" won't work (npm would try to resolve from the registry).
  • Python: ellivate_client.py lands at project root. Import with import ellivate.
  • Vanilla HTML: ellivate-client.global.js and ellivate-config.js land at project root AND public/. Include via <script src="ellivate-client.global.js">.
  • The SDK degrades gracefully when the env vars are missing (local dev outside the container) — returns null, warns once, never throws.

§4 — Deploy environment

Ellivate runs your app in a Railway container with a fixed set of env vars and a specific network binding.

What the contract says

  • Bind to 0.0.0.0, not localhost or 127.0.0.1. Container networking requires the shared interface.
  • Read the port from the PORT env var; framework defaults only as fallback.
  • Env vars Ellivate provides at runtime: ELLIVATE_DATA_URL, ELLIVATE_DATA_KEY, ELLIVATE_APP_ID. You never set these yourself.
  • No user-writable paths outside the project directory.
  • If your app declares a secret it needs (an API key for a third-party service), the publish-gate pauses and asks you to fill it in via the dashboard. No env var is ever silently defaulted to empty.

§5 — Out of scope (explicit refusals)

A few app shapes can't run on Ellivate today. Publish fails with a clear, specific error — never a silent no-op:

  • React Native / Expo apps. Ellivate's mobile shell is a WebView, not a native host.
  • Electron / native desktop apps. Same reason.
  • GPU / dedicated hardware / physical display requirements.
  • Code that reads or writes paths outside the project directory.
  • Playwright apps that hardcode a Chrome channel or require a persistent user profile. Use Ellivate's cloud browser runtime instead.

How the contract evolves

Every new invariant lands in three places together: a server translator or gate, an MCP tool update, and a fixture in the end-to-end test harness. Adding a rule without all three lets the contract drift from reality — which means a publish can "pass" while the app misbehaves in production. Loud failures over silent ones is the first rule of the platform.

Where to go next