# Superapp — App Standards & Contract

> The rules every app must satisfy to be hosted on the platform. This is the reference you build apps against.
> Companion: [requirements.md](requirements.md) — the platform spec. Sections here cite it as `req §N`.
> The platform's **validation gate** (req §16.2) enforces most of these mechanically on manifest registration; a failing check blocks deploy.

---

## 1. Purpose & Principles

An app is **first-party, trusted, and built on the mandated runtime** (req §3.3). To be hosted, it must:
1. **Declare, don't assume.** Everything the platform needs to wire an app is in the manifest (§3). No hidden coupling.
2. **Consume the SDK, don't reinvent.** Auth, API calls, telemetry, theming, i18n, lookups, errors, and inter-app comms come from `@stryde/superapp-sdk` (req §7). Apps that re-implement these fail review.
3. **Be a good tenant of the shell.** Don't crash it, don't grab the URL, don't bundle the framework, stay within budget.
4. **Localize and theme via the platform.** No hardcoded strings, colors, spacing, or `left/right`.
5. **Emit codes, not prose.** Errors are codes; messages are localized centrally.
6. **Be observable.** Propagate trace context and the mandatory attributes.

The checklist in §16 is the gate. If your app passes §16, it deploys.

## 2. App Anatomy

An app is a directory under `apps/<app_key>/`:
```
apps/<app_key>/
  manifest.json     ← the contract (§3)
  frontend/         ← micro-frontend module (optional if backend-only; platform renders skin UI)
    index.ts        ← exports the mount entry (§6.1)
  service/          ← backend microservice(s) (optional if frontend-only)
    Dockerfile
    app/            ← FastAPI baseline
  i18n/             ← per-app message catalogs + locale-map seeds (§8)
  lookups/          ← per-app lookup seeds (§9)
  errors/           ← per-app error-code catalog (§10)
```
- **Frontend-only** apps: backend section omitted; all data via other apps' APIs or the platform.
- **Backend-only / headless** apps: frontend omitted; the platform renders a **skin UI** from the manifest + API contract (req §1).
- `app_key`: lowercase, `^[a-z][a-z0-9_]{2,39}$`, globally unique on the platform. It namespaces everything (permissions, events, lookups, error codes, i18n, routes, API path).

## 3. The Manifest (`manifest.json`)

The single source of wiring. Validated on registration (§16). Immutable once deployed — changes create a new manifest version (req §16.4).

### 3.1 Full schema
```jsonc
{
  "app_key": "invoicing",                 // §2 rules; unique; immutable
  "name": { "en": "Invoicing", "ar": "..." },   // locale-map (req §15.2)
  "description": { "en": "...", "ar": "..." },
  "version": "1.4.0",                      // semver; required
  "runtime": "expo-ts | fastapi-py",       // declared runtimes used (req §3.3)
  "icon": "cdn://.../icon.svg",

  "frontend": {                            // omit for headless apps
    "bundle": "cdn://bundles/invoicing/1.4.0/index.js",  // set by publish step
    "mount": "default",                    // export name of the mount entry (§6.1)
    "route": "/invoicing",                 // route subtree the shell grants (§6.2)
    "nav": {                               // nav entry; omit to hide from nav
      "label_key": "invoicing.nav.title",  // i18n key (§8), NOT a literal string
      "icon": "receipt",
      "order": 30,
      "permission": "app.invoicing.view"   // gate visibility (§7)
    },
    "budget": { "max_bundle_kb": 250 }     // performance budget (req §6.4, §6.4 below)
  },

  "backends": [                            // zero or more
    {
      "service_key": "invoicing-api",
      "image": "registry/.../invoicing-api:1.4.0",  // set by publish step
      "api_base": "/api/apps/invoicing",   // stable gateway path (req §8.1)
      "replicas": { "min": 1, "max": 5 },  // scaling bounds (gateway hides count, req §8.2)
      "health": { "liveness": "/healthz", "readiness": "/readyz" },  // required (§5.1)
      "env": ["DATABASE_URL", "DB_SCHEMA"],          // declared config (§5.4)
      "secrets": ["INVOICING_SIGNING_KEY"],          // declared secrets (req §21.3)
      "rate_limit": { "rps": 50, "burst": 100 }
    }
  ],

  "permissions": [                         // permissions this app DEFINES (req §10.2)
    { "key": "app.invoicing.view",   "name": { "en": "View invoicing", "ar": "..." } },
    { "key": "app.invoicing.invoice.create", "name": { "en": "Create invoice", "ar": "..." } }
  ],

  "events": {                              // inter-app comms (req §11)
    "emits":   [ { "key": "invoicing.invoice.paid", "version": 1, "schema": "events/invoice_paid.v1.json" } ],
    "consumes":[ { "key": "crm.customer.created", "version": 1, "from": "crm" } ]
  },

  "lookups": "lookups/seed.json",          // per-app lookup seeds (§9); namespaced
  "i18n": { "catalogs": "i18n/", "locales": ["en", "ar"] },   // §8
  "errors": "errors/catalog.json",         // per-app error codes + messages (§10)

  "capabilities": {                        // declared, enforced (req §21.4)
    "platform_apis": ["lookups", "notifications", "bus"],
    "consumes_apps": ["crm"],
    "egress": ["https://api.stripe.com"]
  },

  "owner": "ousama",
  "slo": { "availability": 0.99, "latency_p95_ms": 400, "error_rate": 0.01 }  // req §12.5
}
```

### 3.2 Locale-map fields
Any user-facing text in the manifest (`name`, `description`, permission/event `name`) is a **locale-map** object `{ "<locale>": "..." }` (req §15.2), never a bare string. `nav.label_key` is the exception — it's an **i18n key**, resolved at render (§8).

## 4. Manifest Validation Rules (enforced — req §16.2)

The platform rejects a manifest if any fails:
1. Schema-valid against §3; `app_key` matches §2 and is unique; `version` is semver.
2. Every namespaced key (`permissions[].key`, `events.*.key`, lookup group keys, error codes, i18n keys) is correctly prefixed with `app_key` (or `platform`/`app.{app_key}` per the relevant section) — no foreign or unprefixed namespaces, no collisions with existing apps.
3. `events.consumes[].from` references an app that exists and actually `emits` that event at a compatible version.
4. Every declared **error code** (§10) and every **i18n key** referenced (incl. `nav.label_key`, permission names) has catalog entries for **all active locales** (req §15.1).
5. Each backend declares `health.liveness` + `health.readiness`; `api_base` equals `/api/apps/{app_key}` (req §8.1).
6. Frontend `mount` export exists and conforms to the mount contract (§6); bundle is within `budget.max_bundle_kb` (§6.4).
7. Every declared `env`/`secrets` entry resolves in the target environment's config; undeclared env/secret usage is a fail.
8. `capabilities` only reference platform APIs that exist, apps that exist, and egress that policy allows; the app must not access anything it didn't declare.
9. Backend image passes provenance/scan checks (req §21.2).
10. Required OTel attributes are wired (§11).
11. **Lookups usage** — no hardcoded enums for anything that should be reference data; app lookup groups are namespaced + seeded; FK columns named `*_lookup_id` (§9, §16).
12. **Logging/observability** — OTel initialized; structured logging through the SDK/platform logger (no raw `print`/`console.log` as the primary log path); mandatory attributes present; trace context propagated (§11, §16).
13. **Locale** — no hardcoded user-facing strings; all keys + DB content have full active-locale coverage; content uses locale-maps; layouts are RTL-safe (§8, §16).
14. **UI heritage** — frontend uses **design-system components + theme tokens only**; no ad-hoc visual primitives, no hardcoded color/spacing/typography (§6.5, §16).
15. **Strict SDK usage** — platform concerns (auth, API calls, telemetry, i18n, lookups, errors, inter-app bus, navigation) go **only** through `@stryde/superapp-sdk`; the app does not re-implement or bypass them; framework + SDK + design-system are externals, never bundled (§1.2, §6.3, §16).

## 5. Backend / Microservice Rules

Baseline runtime: Python 3.12 / FastAPI (mirror `llm-manager`). Other runtimes out of scope (req §3.3).

### 5.1 Required endpoints
- `GET /healthz` — **liveness**: process is up. Cheap, no dependencies.
- `GET /readyz` — **readiness**: dependencies (DB, Redis) reachable; returns 503 until ready. Used to gate rollout/routing (req §16.3).
- A versioned API under `api_base` (`/api/apps/{app_key}/v1/...`).

### 5.2 Auth
- Trust **only the internal access token** injected by the gateway (req §8.3/§9.2). Never call Clerk directly.
- Enforce permissions via a dependency that checks token scopes against the action's required permission (req §9.3). Declared permissions in the manifest must match what the code enforces.
- Service-to-service calls use platform-issued service credentials (req §9.4); never embed another app's secrets.

### 5.3 Error envelope (codes, not strings — req §13)
- On error, return the envelope:
  ```json
  { "error": { "code": "APP.INVOICING.INVOICE_NOT_FOUND", "params": { "id": 42 }, "details": {} } }
  ```
- `code` is from the app's declared catalog (§10). **Do not** put a localized/translated message as the source of truth. An English `message` MAY be included for logs only.
- Use a shared error type (mirror `llm-manager`'s `LlmManagerError`) that takes `(code, params, status_code, details)`.

### 5.4 Config & data
- Config via pydantic-settings from env; **every secret/env must be declared** in the manifest (§3) or it won't be injected (mirror `llm-manager`'s explicit-field rule).
- DB: Postgres, **schema-per-service** via `DB_SCHEMA` on `MetaData(schema=...)` (req §23). Async only (SQLAlchemy 2.x + asyncpg); no sync DB calls.
- Soft-delete + Timestamp + AuditUser mixins on persisted models (req §23). Alembic migration for every model change.

### 5.5 Observability
- Initialize the OTel SDK; export to the platform Collector (req §12.2).
- Propagate incoming trace context; attach the mandatory attributes (§11).
- Auto-write audit rows for state-changing operations (adopt `llm-manager`'s auto-audit CRUD; mask secrets).

## 6. Frontend / Micro-Frontend Rules

### 6.1 Mount contract
The frontend exports a mount entry (named per `frontend.mount`) that the shell renders inside its route subtree, receiving an injected SDK context:
```ts
// apps/<app_key>/frontend/index.ts
import type { AppMount, SdkContext } from '@stryde/superapp-sdk';

const mount: AppMount = ({ sdk, route, params }: {
  sdk: SdkContext;   // auth, api, telemetry, theme, i18n, lookups, bus, errors
  route: string;     // the subtree route the app was mounted at
  params: Record<string, string>;
}) => {
  return <InvoicingRoot sdk={sdk} route={route} params={params} />;
};
export default mount;   // matches manifest frontend.mount = "default"
```
- The component must render under both React Native and React Native Web.
- Wrap nothing above itself in providers the shell already supplies (theme/i18n/auth come from `sdk`).

### 6.2 Routing & URL ownership
- The **shell owns the URL** (req §6.3). The app gets the subtree under `frontend.route`.
- Navigate via `sdk.nav` only — never manipulate history/`window.location` directly. Cross-app navigation via `sdk.nav.navigateToApp(...)` (req §11.2).
- **The shell owns the top navigation.** An app must **not** render its own top bar / app header / brand strip — that's the superapp nav's job, and a second one is duplicative chrome. Apps lay out with **side menus only** (e.g. a left sidebar); brand, theme toggle, and app-switching all live in the shell nav. (Apps that ship their own top nav for standalone use must hide it when mounted.)

### 6.3 No framework bundling
- Framework runtime, the SDK, and the design system are **shared singletons provided by the shell** (req §6.4). Mark them as peer/externals; do **not** bundle your own copy. Validation rejects bundles that duplicate them.

### 6.4 Performance budget
- Declare `frontend.budget.max_bundle_kb`. The built bundle (excluding shared singletons) must stay under it; the gate measures and rejects overflow (req §6.4).
- Lazy-load heavy sub-features; the mount entry should be light.

### 6.5 Theming
- **Adopt the platform design language.** Declare `frontend.design_system` = the current platform skin id (`"stryde@2"`) **and** list both SDK sheets in `frontend.stylesheets`; the gate rejects apps that don't (`GATE.UI.DESIGN_SYSTEM`). The app links both — at build time (after your own styles); the skin is also injected by the shell into the app's iframe at runtime:
  - **`/sdk/stryde.css`** — the *skin*: brand accents, the unified font, the ambient glow, and the `.stryde-*` component classes resolved from **hex** tokens (so they render on the shell/wiki/launcher).
  - **`/sdk/stryde-tokens.css`** — the canonical *surface palette* in HSL-channel form (`--bg`, `--bg-elev`, `--ink*`, `--brand-*`, glow alphas) that a Tailwind app reads as `hsl(var(--bg))`, **plus the app-usable `.stryde-*` and `.stryde-side*` classes** (mirrors of the skin's components, resolved from `hsl(var(--…))` so they work *inside* apps). This is the **single source of truth** for the warm-beige light / deep-purple dark canvas + the components. It is **unlayered**, so it overrides an app's own `@layer base` token block AND the skin's hex `.stryde-*` (loaded later) — a new Tailwind app links both sheets and inherits the corrected superapp palette + working components for free. (Two files because `--bg` is consumed as hex on the shell/wiki but as HSL channels in apps; one file can't define both — `hsl(#F5F2EC)` is invalid.) Keep any app-local `:root`/`.dark` fallback in sync; edit the SDK file for platform-wide changes.
- **Use the token NAMES.** Map your Tailwind utilities to the SDK token names consumed as HSL channels — `bg: hsl(var(--bg) / <alpha>)`, `ink`, `line`, `brand-violet`, … (see `apps/_starter/frontend/tailwind.config.ts`). This is the one hard requirement: same names, HSL channels, no hardcoded hex — or the SDK palette can't reach your utilities.
- **Build with the canonical components — plug-and-play.** Build your surface with the `.stryde-*` classes (they re-theme automatically) and you look like the superapp with **zero per-app CSS** — the contract for a correctly-built app. Catalog (work in apps via `stryde-tokens.css`):
  - `.stryde-card` / `.stryde-panel` — glass surfaces (18px radius, hairline border, hover lift).
  - `.stryde-hero` (+ `.stryde-grad` for the violet→azure gradient word) / `.stryde-eyebrow` / `.stryde-lead` / `.stryde-section` — page headers & rhythm.
  - `.stryde-btn` + `.stryde-btn-primary` (gradient) / `.stryde-btn-ghost` — buttons.
  - `.stryde-input`, `.stryde-pill`, `.stryde-table`, `.stryde-note` — controls & data.
  - `.stryde-side` / `.stryde-side-brand` / `.stryde-side-section` / `.stryde-side-item`(`.active`) / `.stryde-side-icon` — the **side menu** (the only app nav allowed, §6.2).
  **Scaffold:** copy `apps/_starter/` to start 1:1 with the homepage. **Proof:** `/sdk/starter.html` links only the SDK and renders this whole catalog — if it matches the homepage, your app will too. **Inherited apps** built before the SDK must be migrated onto these (or token-aligned Tailwind), not diverge.
- Use **design-system components and theme tokens only** (req §18.3). No hardcoded colors, spacing, radius, typography. Apps must re-theme automatically when the admin changes the theme — hardcoded styling fails review.
- Use **CSS logical properties / direction-aware primitives** — never hardcoded `left`/`right`/`marginLeft` etc. (req §15.6).

### 6.6 Resilience
- Don't throw past your subtree; the shell's error boundary will catch and degrade, but apps should handle their own recoverable errors and surface them via the SDK error UI (§10, req §13).

## 7. RBAC / Permission Declaration

- Declare every permission the app defines in `manifest.permissions`, keyed `app.{app_key}.{resource}.{action}` (req §10.2). Platform permissions use `platform.{area}.{action}`.
- Gate UI via `sdk.auth.can('app.invoicing.invoice.create')`; gate backend endpoints via the scope-checking dependency (§5.2). The two must agree with the manifest.
- Never invent permission strings at runtime; only declared ones are grantable.

## 8. Internationalization Rules

- **No hardcoded user-facing strings** anywhere (frontend or manifest nav). Use `sdk.i18n.t(key, params)` with keys namespaced `{app_key}.*`. Ship catalogs under `i18n/` per locale.
- Translatable **DB content** uses the **locale-map** storage (`{ "en": ..., "ar": ... }`, req §15.2) — never `_ar` twin columns. Select with the platform fallback helper (req §15.3).
- Provide catalog entries for **all active locales** for every key (gate checks this, §4.4).
- Use `Intl`-based locale-aware formatting (dates/numbers/currency) via the SDK; don't format manually.
- Build layouts with logical properties so RTL (Arabic) works with no per-locale branching (req §15.6).

## 9. Lookups Rules

- Use **platform lookups** for reference data/enums; don't hardcode enums in code (adopt `llm-manager`'s lookups-as-type-system, req §14).
- App-specific groups are namespaced `{app_key}.{group}`; platform/system groups are unprefixed and `is_system` (req §14.2.5).
- Ship seeds in `lookups/` as idempotent upserts; resolve at runtime via the SDK/`require_lookup` (raises if unseeded — mirror `llm-manager`).
- FK columns referencing a lookup are named `*_lookup_id` (req §14.1).
- Translatable lookup `name`/`description` use locale-maps (§8).

## 10. Error-Code Rules

- Every failure maps to a declared code in `errors/catalog.json`, formatted `APP.{APP_KEY}.{NAME}` (uppercase; platform codes are `PLATFORM.{AREA}.{NAME}`) (req §13.3).
- The catalog provides, per code, a **message template per active locale** (with `{param}` interpolation) — the client localizes; the backend never emits the translated string as truth (req §13.1).
- Codes are unique platform-wide and validated to have full locale coverage (§4.4).
- Surface errors in the UI via the SDK error primitives, which resolve code→message for the current locale (req §13.4).

## 11. Observability Rules

- Initialize OTel via the SDK (frontend) / platform setup (backend); export to the Collector (req §12.2).
- **Propagate trace context** across the shell→gateway→backend boundary (the SDK API client and FastAPI middleware do this; don't break it).
- Every span/log/metric must carry the mandatory attributes — `user_id`, `session_id`, `app_id`, `org_id`, `team_id`, `trace_id`, `release/version` (req §12.3). The SDK attaches these automatically; don't strip them.
- Name spans meaningfully (operation, not URL with IDs); record errors on spans with the error `code`.
- Emit the metrics needed for the declared SLO (req §12.5).

## 12. Inter-App Communication Rules

- Declare all `emits`/`consumes` in the manifest with versioned, schema'd events (req §11.1); event keys are `{app_key}.{event}`.
- Publish/subscribe via `sdk.bus` only; payloads must validate against the declared schema.
- To consume another app's event, declare it in `events.consumes` and in `capabilities.consumes_apps`; the gate verifies the producer exists and emits a compatible version (§4.3).
- Cross-app navigation via `sdk.nav.navigateToApp` (req §11.2). No shared mutable globals — share via events or backend APIs (req §11.3).

## 13. Security Rules

- **No secrets in the frontend bundle or in git manifests.** Declare secrets (§3); the platform injects them server-side (req §21.3).
- Backends run as sandboxed containers (gVisor) — don't assume host access; declare all egress in `capabilities.egress` (req §21.1/§21.4).
- Validate and authorize every backend request against the internal token (§5.2); never trust client-supplied identity/role data.
- Web frontends must comply with the platform CSP; no inline eval of remote code.
- Pin dependencies; images must pass scanning/SBOM (req §21.2).

## 14. Naming & Conventions

- `app_key`: `^[a-z][a-z0-9_]{2,39}$`. Used as the namespace prefix everywhere.
- Permissions: `app.{app_key}.{resource}.{action}` / `platform.{area}.{action}`.
- Events: `{app_key}.{entity}.{event}` (+ integer `version`).
- Lookup groups: `{app_key}.{group}` (app) / `{group}` (system).
- Error codes: `APP.{APP_KEY}.{NAME}` / `PLATFORM.{AREA}.{NAME}`.
- i18n keys: `{app_key}.{area}.{name}` / `platform.{area}.{name}`.
- API: `/api/apps/{app_key}/v{n}/...`.
- DB: schema-per-service; `*_lookup_id` FK naming; soft-delete/timestamp/audit mixins.
- Backend lint/types: ruff (`E,F,I,B,UP,N,SIM,RUF`) + mypy (mirror `llm-manager`).

## 15. Versioning & Deprecation

- Apps are **semver**-versioned; the manifest is versioned and **immutable once deployed** (req §16.4). Changes ship as a new version.
- Bump **major** on breaking changes to your API, emitted-event schemas, or permission keys; consumers depend on declared versions (§12).
- Deprecate via lifecycle state `deprecated` (req §5.2) before `archived`; announce via the deprecation policy. Don't remove an emitted event consumers depend on without a major bump + migration window.

## 16. The Validation Checklist (the deploy gate)

An app deploys only when **all** pass (mechanically enforced — req §16.2):

**Manifest**
- [ ] Schema-valid; `app_key` valid + unique; `version` semver; runtimes declared.
- [ ] All namespaced keys correctly prefixed; no collisions (§4.2).
- [ ] Locale-map fields well-formed; `nav.label_key` is a key, not a literal.

**Strict SDK usage (platform-concern compliance)**
- [ ] Auth, API calls, telemetry, i18n, lookups, errors, bus, and navigation go **only** through `@stryde/superapp-sdk` — none re-implemented or bypassed (§1.2).
- [ ] Framework, SDK, and design-system are externals (peer), **not bundled** (§6.3).

**Lookups**
- [ ] No hardcoded enums for reference data; uses platform lookups (§9).
- [ ] App lookup groups namespaced `{app_key}.*` and seeded; `*_lookup_id` FK naming.

**i18n & errors (locale)**
- [ ] Every i18n key + error code has entries for **all active locales** (§4.4).
- [ ] No hardcoded user-facing strings in frontend/manifest; DB content uses locale-maps.
- [ ] Layout RTL-safe (logical properties; no hardcoded left/right).

**Logging / observability**
- [ ] OTel initialized; trace context propagated; mandatory attributes present (§11).
- [ ] Structured logging via SDK/platform logger; no `print`/`console.log` as primary log path.

**UI heritage**
- [ ] Uses the `.stryde-*` components + theme tokens only; no ad-hoc primitives or hardcoded styling (§6.5).
- [ ] Tailwind utilities map to the SDK token names as HSL channels (`bg-bg`, `text-ink`, …); no hardcoded hex (§6.5).
- [ ] App navigation is a `.stryde-side*` side menu — no top nav (the shell owns it, §6.2).
- [ ] `frontend.design_system` = the current skin id (`"stryde@2"`) **and** `frontend.stylesheets`
      links BOTH `/sdk/stryde.css` and `/sdk/stryde-tokens.css` — the app adopts the palette + the
      app-usable components, loaded last, so it reads as the superapp (`GATE.UI.DESIGN_SYSTEM`, §6.5).

**Backend**
- [ ] `/healthz` + `/readyz` present; `api_base` = `/api/apps/{app_key}`.
- [ ] Trusts only the internal token; enforces declared permissions.
- [ ] Returns the code-based error envelope (§5.3).
- [ ] All `env`/`secrets` declared and resolvable; none undeclared in use.
- [ ] OTel initialized; trace context propagated; mandatory attributes present.
- [ ] Schema-per-service; async DB; migrations present.

**Frontend**
- [ ] Mount entry exists + conforms (§6.1); renders on RN + RNW.
- [ ] Does not bundle shared singletons; within `max_bundle_kb` budget.
- [ ] Uses SDK nav (shell owns URL); theme tokens only; logical properties (RTL-safe).
- [ ] `frontend.mount` is `"default"` — the federation loader normalizes every app
      to the `./mount` expose and reads its default export (`GATE.FRONTEND.MOUNT_NAME`).
- [ ] **In federation mode** (req §5.4 Phase 2): `frontend.bundle` is set to the
      remote's `mf-manifest.json` URL (https, or http for localhost in dev). An
      enabled frontend with no reachable remote is rejected
      (`GATE.FRONTEND.NO_BUNDLE` / `GATE.FRONTEND.BUNDLE_URL`). The published
      remote's built JS (excluding shared singletons) must stay within
      `max_bundle_kb` — enforced by the publish step, which has the artifact.

**RBAC / comms / capabilities**
- [ ] Permissions declared = enforced.
- [ ] `emits`/`consumes` declared, versioned, schema'd; consumed producers exist (§4.3).
- [ ] `capabilities` declared and not exceeded (platform APIs, consumed apps, egress).

**Security / supply chain**
- [ ] No secrets in bundle/git; image scanned + SBOM; deps pinned.

Pass → the app can be deployed, enabled, and (later) rolled back from the admin console (req §17.1).
