Reference
App contract
The complete app contract, enforced by the MCP server, the translator pipeline, and the invariant checker. A violation surfaced at publish time is a bug in Ellivate — not in your code.
This page is the formal specification. For a narrative explanation, see The Ellivate contract. For framework-specific walkthroughs, see the App shapes section of the nav.
§1 — Identity
Ellivate is a shared-shell platform. Viewers arrive already authenticated. Apps never run their own sign-in flow.
Invariants
- No third-party auth libraries:
@supabase/ssr(auth use only — DB client is fine),next-auth,firebase/auth,@clerk/*,@auth0/*,lucia-auth,magic-sdk, hand-rolled JWT for session cookies. - No
/login,/signup, or/auth/callbackroutes. - Viewer identity is read from one of:
ellivate_viewer_tokencookie (server components, server actions)X-Ellivate-Viewer-Tokenheader (server-to-server / Python frameworks)window.ELLIVATE_VIEWER_TOKEN(browser, mobile WebView)- URL fragment
#__ellivate_auth=...on first page load
- User shape:
{ id, email, username, verified }. - Sign-out is a redirect to
/, not a third- partysignOut()call. - Python apps (Flask / FastAPI) get an auth-gate middleware injected at publish time. The middleware 401s any request without a valid viewer cookie. Bypassed when
App.requiresAuth = false. - Python apps enforce per-viewer scope dispatch via a
contextvars.ContextVar. The middleware stashes the token after verify; the SDK reads the contextvar on every KV call. - The handoff cookie must carry the
Partitionedattribute (CHIPS). Chrome blocks unpartitioned third-party cookies on subsequent iframe requests. App.isPublicandApp.requiresAuthare orthogonal. All four combinations valid; never conflate.
§2 — Persistence
All user-generated data goes through Ellivate's KV SDK.
Invariants
- No
localStorage,sessionStorage,IndexedDB,Dexie,localForagefor user data. - No
AsyncStorage,redux-persist,zustand/persist,pinia-plugin-persistedstate. - No filesystem writes for user data:
fs.writeFile,sqlite3.connect,shelve.open,pickle.dump,sqlalchemyfor app state. - No cookies for app state.
- Keys are strings; values are JSON-serializable.
- Scope (
PERSONAL/SHARED/PUBLIC) is classifier-picked by default. Explicit prefixes (personal:,shared:,public:) override. - Scope enforcement never runs on server lane. Client lane with no cookie means SHARED/PERSONAL reads 401 (returned as null for GETs, thrown for writes); it does NOT silently dispatch to app-wide storage.
- MODULE_SDK treats 401 on GETs as "no data" (returns null); 401 on writes throws.
- If the app genuinely needs a relational DB, the translator emits a loud warning and the gate asks the user to upgrade. No silent sqlite fallback.
§3 — SDK integration
The Ellivate SDK is a local file at publish time, not a registry package.
Invariants
ellivate-client.jsexists at project root whenever any JS/TS file references it.ellivate-client.d.tsships alongside — strict TypeScript apps need the declaration.ellivate_client.pyexists at project root whenever any Python file references it.ellivate-client.global.jsandellivate-config.jsexist at project root ANDpublic/for vanilla HTML apps.- JS/TS imports use relative paths:
"../ellivate-client". Bare specifier"ellivate-client"is forbidden. - Python imports are absolute:
import ellivate. ellivate-clientis NOT inpackage.jsondependencies.ellivate_clientis NOT inrequirements.txt.package-lock.jsonis in sync withpackage.json.- SDK
fetchcalls in MODULE_SDK usecache: "no-store". - SDK degrades gracefully when env vars missing — returns null, warns once, never throws.
- MODULE_SDK sends
X-Ellivate-Caller-Lane: clientwhenever there's per-request viewer context (browser, Next.js server components, explicit token). Server lane is the narrow fallback for non-Next Node with no token. - Ellivate-injected routes use a URL path starting with a letter — never
_. App Router treats underscore-prefixed folders as private (silent 404). The handoff is at/ellivate-handoff. - Redirects emitted by Ellivate routes must derive external origin from
x-forwarded-host+x-forwarded-proto.new URL(request.url).originyields the container's internal address.
§4 — Deploy environment
Invariants
- App listens on
0.0.0.0, notlocalhostor127.0.0.1. - Port is read from
PORTenv var. - Runtime env vars Ellivate provides:
ELLIVATE_DATA_URL,ELLIVATE_DATA_KEY,ELLIVATE_APP_ID. Python additionally getsELLIVATE_VIEWER_TOKENas a server-lane fallback (primary per-request viewer comes from the handoff cookie). - No user-facing env vars required for a normal publish. If the app declares one, the gate pauses and asks the user to fill it in.
- No user-writable paths outside the project directory.
§5 — Out of scope (explicit refusals)
Publish fails loudly with a Class 1 blocker and a user-facing reason.
- React Native / Expo apps.
- Electron / native desktop apps.
- Code that requires GPU, dedicated hardware, or a physical display.
- Code that reads or writes paths outside the project directory.
- Playwright apps that hardcode a Chrome channel or require a persistent user profile.
Amending the contract
A new invariant lands with three pieces in parallel:
- A server-side enforcement — translator rewrite, gate check, or invariant validator.
- An MCP-side steer — new tool or updated tool output.
- At least one E2E fixture that fails if the invariant breaks.
Removing a rule (a shape we now support) requires updating the same three pieces. Amendment log lives in git.
What's next
- Narrative walkthrough of what each section means.
- JavaScript SDK / Python SDK.