# SafeApp: Agent-Native Full-Stack Platform > SafeApp is a file-free, git-free application platform where **policy derives placement**. > Logic is written in safescript; SafeApp statically decides what runs on the client vs. the server, > generates the RPC boundary between them, and proves no secret ever reaches an unauthorized host. > For full safescript syntax and language specification, see: https://safescript.cc/llms.txt --- ## ⚡ Quickstart for Agents (Build a Live Database App in 3 Steps) ### Step 1: Create the App ```http POST /api/v1/apps Authorization: Bearer Content-Type: application/json { "id": "my-crm", "name": "My CRM", "title": "Lead Management Dashboard" } ``` *Returns `{ "ok": true, "previewUrl": "/preview/my-crm/main" }`.* ### Step 2: Insert Real Data into the Database (Data REST API) Push data directly into the app's provisioned realtime relational database (InstantDB). No boilerplate required: ```http POST /api/v1/apps/my-crm/data/leads Authorization: Bearer Content-Type: application/json { "items": [ { "company": "Wix", "contactName": "Dana", "status": "New", "phone": "050-1234567" }, { "company": "Amdocs", "contactName": "Michal", "status": "Contacted", "phone": "052-7654321" } ] } ``` *You can query data anytime with `GET /api/v1/apps/my-crm/data/leads` (supports query filters e.g. `?status=New`).* ### Step 3: Define the UI (`render` Only) No `initialState()` needed — the database IS the state! The UI queries the database directly with `dbQuery`: ```http POST /api/v1/apps/my-crm/functions Authorization: Bearer Content-Type: application/json { "updates": { "render": "(): { tag: string } => { leads = dbQuery({ namespace: \"leads\" }).items return { tag: \"card\", title: \"Leads Dashboard\", children: [ { tag: \"table\", props: { columns: [\"company\", \"contactName\", \"status\", \"phone\"], rows: leads } } ] } }" }, "summary": "Display leads from database in a table" } ``` *Live SSR & client SPA immediately available at `https://safeapp.prompt2bot.com/preview/my-crm/main`!* --- ## 1. Core Principles - **No Files, No Git:** Applications are not files on disk or git repositories. They are collections of discrete, named functions stored in an entity store. Agents manipulate code via structured REST API calls (`getFunction`, `updateFunctions`, `createBranch`, `mergeBranch`). - **Policy Derives Placement:** Developers and agents do not manually decide what runs on the client vs. the server. The compiler analyzes the static data-flow DAG: - Nodes that read secrets (`readSecret`) or access protected resources are placed on the **server**. - Presentation and UI nodes (`render`, view constructors) are placed on the **client**. - The boundary between them is automatically converted into an RPC bridge (`/rpc`). - **Secrets on Client as a Permission:** If code places a secret on the client side or exposes a secret to the client bundle, this is strictly a permission: only tokens with `can_access_secrets: true` can compile such code. Unprivileged tokens are blocked. - **No Email Required:** Anyone can create an admin account without an email. The account instantly receives a Master Admin Token capable of managing apps and issuing scoped agent tokens. - **Auto-Merge Policy Gate:** Merging a branch into `main` requires zero human review if the signature diff introduces no new secret-to-host flows or unallowlisted network sinks. --- ## 2. Safescript Foundation SafeApp application logic is written in [safescript](https://github.com/uriva/safescript), a non-Turing-complete language with decidable compile-time data-flow signatures. For language details, see: - Safescript Reference: https://safescript.cc/llms.txt - GitHub: https://github.com/uriva/safescript Key Language Constraints: - Built-in operations take a single object argument and return an object: - `stringConcat({ parts: ["hello", " ", name] }).result` - `jsonStringify({ value: data }).text` - `jsonParse({ text: rawJson }).value` - `arrayAppend({ array: list, element: item }).array` - No unbounded loops. Use functional combinators with named functions: - `map(fn, array)` - `filter(fn, array)` - `reduce(fn, initialAccumulator, array)` - Conditionals use ternary expressions: `condition ? valueIfTrue : valueIfFalse`. --- ## 3. The Application Contract In SafeApp, the database IS the state. There is no double mechanism. Every SafeApp application requires only one function: 1. `render()` (or `render(state)`): Returns a **View ADT** describing the user interface. It queries the database directly with `dbQuery({ namespace: "..." })`. Optional lifecycle functions: 2. `update(state, action, payload)`: Handles dispatched actions (clicks/submits) and mutates data via `dbTransact({ namespace, id, patch })`. 3. `initialState()`: Optional transient UI state (e.g. active tab or search input). If omitted, state defaults to `{}`. ### View ADT Structure ``` { tag: "div", // Standard HTML tag or Semantic Component attrs: { "class": "...", style: "..." }, // String attributes on: { "click": "actionName" }, // Event to action name text: "Content text", // Text node children: [ ...View... ], // Child view nodes html: "..." // Raw unescaped HTML string (or rawHtml) } ``` ### Raw HTML Support & `` Tag When rendering rich markup, forms, SVGs, or custom widgets, you do **not** need to decompose trees into deep AST dictionaries: 1. **Raw HTML Element:** ```safescript { tag: "raw_html", html: "
...
" } // Or shorthand: { html: "Active Lead" } ``` 2. **Container with Raw Inner HTML:** ```safescript { tag: "div", html: "" } ``` 3. **Escaped HTML Detection in Preview:** If a raw string containing HTML tags (e.g. `"
...
"`) is passed as a child string without the `raw_html` / `html` key, SafeApp's preview engine detects it, logs a warning in the console with remediation instructions, and escapes it safely to prevent broken layouts. ### Built-in Semantic UI Component Primitives SafeApp includes standard, beautifully styled dark-mode component primitives out of the box so you don't need to write repetitive CSS: - **Card (`tag: "card"`):** ```safescript { tag: "card", title: "Pipeline Overview", subtitle: "Active enterprise leads", children: [ ... ] } ``` - **Badge (`tag: "badge"`):** Pill badge supporting variants (`success`, `warning`, `danger`, `info`, `neutral`): ```safescript { tag: "badge", attrs: { variant: "success" }, text: "Qualified" } ``` - **Metric Stat (`tag: "stat"`):** ```safescript { tag: "stat", label: "Monthly ARR", value: "$48,500" } ``` - **Data Table (`tag: "table"`):** Supports declarative zero-boilerplate table generation via `columns` and `rows` (either at root, `props`, or `attrs`). No manual `map`, ``, or `` required: ```safescript { tag: "table", props: { columns: ["Name", "Phone", "Status"], rows: state.leads } } ``` - `columns`: Array of strings `["Name", "Phone", "Status"]` or column objects `[{ key: "email", label: "Email" }, ...]`. - `rows`: Array of objects (keys match columns automatically via exact, camelCase, or case-insensitive matching) or array of arrays. - Cells can be strings, numbers, or full VDOM components (e.g. `{ tag: "badge", attrs: { variant: "success" }, text: "Active" }` or buttons). - Standard HTML ``/`` children are also supported for custom layouts. ### Hybrid VDOM + HTML & Nested Array Support - **Recursive Mixed Children:** `children` accepts mixed arrays containing strings, numbers, VDOM nodes, `{ html: "..." }`, and sub-arrays returned from `map(renderFn, items)`. Nested arrays automatically flatten. - **VDOM in Raw HTML:** Passing a VDOM node or array of nodes to the `html` key (e.g. `{ html: myVdomNode }`) automatically converts it to HTML without string concatenation errors. - **Type Safety in `stringConcat`:** In Safescript, `stringConcat({ parts })` strictly validates that all elements in `parts` are strings. Attempting to concatenate objects throws a descriptive `TypeError: stringConcat expects string parts, got object at index i`, preventing silent `[object Object]` bugs in UI generation. Event Handling & Direct Safescript Function Bindings: - **Direct Safescript Function Invocation:** Any `on: { click: "fnName", change: "fnName", input: "fnName", submit: "fnName" }` (or HTML `data-on-click="fnName"`, `data-on-change="fnName"`, `data-on-input="fnName"`, `data-on-submit="fnName"`, or inline `onclick="fnName('arg')"`) can call a named Safescript function `fnName` directly (both client functions and server RPC functions), automatically updating `state` and re-rendering the UI while preserving input focus! - If `fnName` is not a standalone function, it falls back to `update(state, action, payload)`. - `data-on-click="action"` / `on: { click: "action" }`: Passes `data-payload` (parsed as JSON if valid, or string). - `data-on-change="action"` / `on: { change: "action" }`: Passes `el.value` (or `el.checked` for checkboxes). - `data-on-input="action"` / `on: { input: "action" }`: Passes `el.value` on live keystrokes while preserving input cursor focus across re-renders. - `data-on-submit="action"` / `on: { submit: "action" }`: If the `
` has named inputs (``), serializes all fields via `FormData` into an object `{ company: "...", ... }`; otherwise passes the single input's string value. --- ## 4. REST API Reference Base URL: `http://localhost:/api/v1` Authentication: Pass bearer token via `Authorization: Bearer ` or `x-safeapp-token: ` or cookie `safeapp_token`. ### Authentication & Accounts (No Email Required) - `POST /api/v1/auth/guest` - 1-click instant sandbox creation. Issues a scoped guest session and admin token with zero setup. - Response: `{ "ok": true, "account": { "id": "guest-...", "isGuest": true, ... }, "token": "sa_..." }` - `POST /api/v1/auth/signup` - Body: `{ "username": "agent-lead", "name": "Lead Agent", "password": "optional" }` - Response: `{ "ok": true, "account": { "id": "agent-lead", ... }, "adminToken": "sa_admin_..." }` - `POST /api/v1/auth/login` - Body: `{ "username": "agent-lead", "password": "..." }` or `{ "token": "sa_..." }` - Response: `{ "ok": true, "account": { ... }, "adminToken": "..." }` - `GET /api/v1/auth/me` - Returns current account profile, guest status, and token capabilities. ### Token Management - `POST /api/v1/tokens` - Generates a new admin or non-admin scoped token. - Body: ```json { "name": "Frontend Styler Agent", "appId": "todo-app", // or "*" for all apps "isAdmin": false, "capabilities": { "can_read_code": true, "can_update_functions": true, "can_create_branches": true, "can_merge_branches": false, "can_access_secrets": false, "can_add_hosts": false, "can_manage_secrets": false, "can_manage_tokens": false } } ``` - Response: `{ "ok": true, "token": { "token": "sa_...", ... } }` - `GET /api/v1/tokens` - Lists all tokens issued under the caller's account. ### Application Management - `POST /api/v1/apps` - Body: `{ "id": "my-service", "name": "My Service", "title": "My Service UI", "isPrivate": false, "template": "todo" }` - Set `"isPrivate": true` to activate the Turnkey Private App Gate (hosted login for unauthenticated visitors). - Response: `{ "ok": true, "app": { ... }, "adminToken": "sa_...", "previewUrl": "/preview/my-service/main" }` - `GET /api/v1/apps` - Lists applications for the authenticated account. - `GET /api/v1/apps/:appId` - Returns application metadata, branches, secrets, and token list. - `PATCH /api/v1/apps/:appId` - Updates app metadata (e.g. `{ "isPrivate": true, "title": "New Title" }`). - Returns application metadata, branches, secrets, and token list. ### Data REST API (Insert & Query Database Directly) Directly seed, insert, update, and query records in the app's provisioned database via standard HTTP without writing boilerplate code: - `POST /api/v1/apps/:appId/data/:namespace` - Push real records directly into the app's database. - Body: `{ "items": [ { ... } ] }` or raw array `[ { ... } ]` or `{ "id": "...", "patch": { ... } }` or single object `{ ... }`. - Response: `{ "ok": true, "insertedCount": N, "items": [ ... ] }`. - `GET /api/v1/apps/:appId/data/:namespace` - Query records from the namespace. Optional query parameters filter matching fields: e.g. `?status=New`. - Response: `{ "ok": true, "count": N, "items": [ ... ] }`. ### Branch & Function Operations (Zero Files) - `POST /api/v1/apps/:appId/functions` (or `/api/v1/apps/:appId/branches/:branch/functions`) - Updates discrete named functions and creates an immutable revision (defaults to `main` branch). - To delete obsolete functions, set their value to `null` (or `""`) in `updates`, or pass `"deleteFunctions": ["fn1", "fn2"]`. - Body: ```json { "updates": { "render": "(): { tag: string } => { leads = dbQuery({ namespace: \"leads\" }).items; return { tag: \"card\", title: \"Leads\", children: [ { tag: \"table\", props: { columns: [\"name\", \"status\"], rows: leads } } ] } }" }, "summary": "Implement realtime leads dashboard" } ``` - Response: `{ "ok": true, "revisionId": "...", "signature": { ... }, "placement": { ... } }` - `GET /api/v1/apps/:appId/functions` (or `/api/v1/apps/:appId/branches/:branch/functions`) - Returns active functions, static signature, and placement plan. - `POST /api/v1/apps/:appId/branches` - Body: `{ "name": "feat-dark-mode", "fromBranch": "main" }` - Response: `{ "ok": true, "branch": { ... }, "previewUrl": "/preview/:appId/feat-dark-mode" }` ### Unit Testing via Built-in Safescript Test Framework - `POST /api/v1/apps/:appId/branches/:branch/test` (or `GET`) - Executes unit tests in the branch environment with full execution context. - Safescript identifies zero-argument functions (especially `test*` and `test_*`) as tests. - Optional Body: - `{ "tests": ["testCalculateScore"] }` — Runs only specific test functions. - `{ "functions": { "adHocTest": "(): boolean => { ... }" } }` — Executes ephemeral test functions against the branch without persisting them. - Response: ```json { "ok": true, "allPassed": true, "passed": 2, "failed": 0, "total": 2, "results": [ { "name": "testCalculateScore", "status": "passed", "durationMs": 1 }, { "name": "testStringEquality", "status": "passed", "durationMs": 0 } ] } ``` ### Policy-Gated Branch Merging - `POST /api/v1/apps/:appId/branches/:branch/merge` - Body: `{ "targetBranch": "main", "skipTests": false }` - Behavior: - **Automated Test Run:** All tests on the branch are executed before merge. If any test throws or assertion fails, merge is rejected with status `422` (`tests_failed`). - **Policy Diff Check:** If signature diff introduces no unallowlisted secrets or network hosts: `{ "ok": true, "merged": true }`. - If policy expanded without permission: `{ "ok": false, "status": "blocked", "reason": "..." }`. ### Infrastructure Primitives Allocation Primitives (Realtime Relational Database, Deno KV, GCP Cloud Tasks) are automatically allocated upon app creation (`POST /api/v1/apps`). - `POST /api/v1/apps/:appId/resources/database` - Allocates a dedicated realtime relational database instance at `https://api.instantdb.uriv.me`. - Body: `{ "endpoint": "https://api.instantdb.uriv.me", "instantAppId": "" }` - Automatically vaults the admin token into `INSTANT_ADMIN_TOKEN` with host `api.instantdb.uriv.me`. - Enables `dbQuery({ namespace, where })` and `dbTransact({ namespace, id, patch })`. - `POST /api/v1/apps/:appId/resources/kv` - Partitions zero-latency Deno KV storage for the app. - Body: `{ "prefix": "safeapp:my-app" }` - Enables `kvGet({ key })`, `kvSet({ key, value, expireInSeconds? })`, `kvDelete({ key })`. - `POST /api/v1/apps/:appId/resources/queue` - Allocates a background task queue backed by GCP Cloud Tasks (Project: `supergreen-450411`). - Body: `{ "queueName": "worker-queue", "maxConcurrentDispatches": 10 }` - Enables `queueEnqueue({ queue, taskName, payload, delaySeconds? })`. - Consumer webhook automatically dispatches to the app's `handleQueueTask(taskName, payload)` function. - `GET /api/v1/apps/:appId/resources` - Lists all active allocated primitives for the app. - `GET /api/v1/apps/:appId/resources/database/rules` (or `/perms`) - Inspects current InstantDB permission rules. - `POST /api/v1/apps/:appId/resources/database/rules` (or `/perms`) - Modifies InstantDB permissions. Syncs directly to InstantDB `/dash/apps/:instantAppId/rules`. - Body: `{ "rules": { "leads": { "allow": { "view": "auth.id != null", "create": "auth.id != null", "update": "auth.id != null", "delete": "auth.id != null" } } } }` - `GET /api/v1/apps/:appId/resources/database/schema` - Inspects current InstantDB database schema. - `POST /api/v1/apps/:appId/resources/database/schema` - Modifies database schema. - Body: `{ "schema": { "leads": { "name": "string", "status": "string", "ownerId": "string" } } }` --- ## 5. Primitives Usage in Application Code SafeApp provides builtin operations in the execution context for server and client functions: ### 1. Realtime Relational Database - **Query records (`dbQuery`):** ```ts // Retrieves items in a namespace matching filter criteria items = dbQuery({ namespace: "leads", where: { status: "Demo Sent" } }).items ``` - **Mutate records (`dbTransact`):** ```ts // Creates or updates a record by ID with an atomic patch result = dbTransact({ namespace: "leads", id: "lead_123", patch: { status: "Closed Polite", notes: "Owner not interested" } }) ``` ### 2. Zero-Latency Key-Value Store (`kvGet`, `kvSet`, `kvDelete`) - Partitioned automatically per app and branch (`safeapp::`): ```ts // Read value val = kvGet({ key: "session_counter" }).value // Write value with optional TTL expiration in seconds kvSet({ key: "session_counter", value: 42, expireInSeconds: 3600 }) // Delete key kvDelete({ key: "session_counter" }) ``` ### 3. Background Task Queue (`queueEnqueue` + `handleQueueTask`) - **Enqueue background job:** ```ts // Schedules a task via GCP Cloud Tasks queueEnqueue({ queue: "worker-queue", taskName: "sendEmailOutreach", payload: { leadId: "lead_123", email: "client@example.com" }, delaySeconds: 120 }) ``` - **Task Worker Handler (`handleQueueTask`):** Every app can define a top-level function `handleQueueTask(taskName, payload)`. When Cloud Tasks dispatches the webhook to `/api/v1/apps/:appId/branches/:branch/queue/receive`, SafeApp executes `handleQueueTask` automatically with vaulted secrets injected. --- ## 6. Authentication Specification SafeApp implements a dual-tier authentication architecture: **Platform Auth** (for developers & agents) and **App Auth** (for end-users of deployed apps). ### A. Platform Authentication (SafeApp Dashboard & Agent Access) 1. **Developer Email Magic Code:** - On the homepage (`/`), developers sign in using email magic code. - `POST /api/v1/auth/instant-session` synchronizes the session and issues a cookie `safeapp_token`. 2. **Multi-Tenant Isolation:** - When calling `GET /api/v1/apps`, unauthenticated callers receive `[]`. - Authenticated users receive **only** applications where they are the owner (`ownerEmail`) or a designated administrator (`admins`). 3. **CLI / Agent Tokens:** - Agents authenticate using bearer tokens: `Authorization: Bearer `. - Master admin tokens (`isAdmin: true`, `appId: "*"`) can manage all apps across the platform. 4. **Granting App Admin Access to Other Users:** - App owners can designate other email users as administrators: ```bash POST /api/v1/apps/:appId/admins { "email": "collaborator@company.com" } ``` - The invited email user will immediately see the app on their dashboard upon signing in with their email. - To inspect admins: `GET /api/v1/apps/:appId/admins`. - To revoke access: `DELETE /api/v1/apps/:appId/admins?email=collaborator@company.com`. ### B. App-Specific Authentication (End-Users of Deployed Apps) Every app runs against its own isolated database instance (never touching parent SafeApp data). 1. **End-User Magic Code Login:** - `POST /api/v1/apps/:appId/auth/magic-code` with `{ "email": "customer@example.com" }` sends a 6-digit code. - `POST /api/v1/apps/:appId/auth/verify` with `{ "email": "customer@example.com", "code": "123456" }` validates the code and returns `{ ok: true, user, token }`. 2. **Client-Side Auth State (`ctx.auth`):** - The compiled browser runtime bundle injects `_ctx.auth` directly: - `_ctx.auth.getUser()` — Returns current authenticated app user `{ id, email }`. - `_ctx.auth.getToken()` — Returns the persistent session token. - `_ctx.auth.sendMagicCode(email)` — Sends code to user. - `_ctx.auth.signInWithMagicCode(email, code)` — Logs in user. - `_ctx.auth.signOut()` — Logs out and clears local session. 3. **Authenticated Server RPC Calls:** - Browser RPC shims automatically attach `x-app-auth-token: `. - The RPC handler (`/api/v1/apps/:appId/branches/:branch/rpc`) verifies the token against the app's `INSTANT_ADMIN_TOKEN`. - Server functions receive `ctx.auth.user = { id, email }`. 4. **Agent Direct Token Minting (Headless / Zero Email):** Agents building, automating, or testing apps can mint valid authenticated app tokens directly without an email inbox or verification codes: ```bash POST /api/v1/apps/:appId/auth/token Authorization: Bearer { "email": "agent@system.local" } ``` Response: ```json { "ok": true, "token": "", "user": { "id": "...", "email": "agent@system.local" }, "headerExample": { "x-app-auth-token": "" } } ``` The agent can immediately execute protected RPC calls or interact with the app using `x-app-auth-token: `. ### C. CRITICAL: InstantDB Permissions, Admin Token Bypass & Preview Gate Architecture 1. **Public Apps vs. Turnkey Private App Gate:** - **Public Web Apps (Default, `isPrivate: false`):** `/preview/:appId/:branch` is publicly accessible for end-users. Access control is enforced within the application logic. - **Turnkey Private App Gate (`isPrivate: true`):** For internal CRMs, dashboards, and agent services, set `"isPrivate": true` on the app (`POST /api/v1/apps` or `PATCH /api/v1/apps/:appId`). - When `isPrivate: true`, SafeApp's preview gateway automatically renders a sleek, turnkey hosted login page for unauthenticated visitors. - Users sign in with email magic codes or authorized agent bearer tokens (`?auth_token=...`). - SSR never executes unauthenticated, completely preventing data leaks without requiring you to implement any login VDOM or auth state handling in Safescript! - If the app has designated `admins` (`POST /api/v1/apps/:appId/admins`), the gateway automatically restricts access to those emails. 2. **The Admin Token Bypass (Why Data Leaked in Incognito on Public Apps):** - Server functions and SSR (`renderSSR`) run with the app's `INSTANT_ADMIN_TOKEN`. - **InstantDB Admin tokens bypass InstantDB permission rules completely.** - If a public app's `initialState` or a server function queries the database without checking `ctx.auth.user`, InstantDB returns all records because it executes as Admin. - SSR then embeds those records into static HTML and `__SAFEAPP_STATE__`, making them visible to anyone in incognito or via curl! - InstantDB permission rules (e.g. `auth.id != null`) protect **client queries**, NOT unauthenticated server queries running with admin privileges. 3. **How to Protect Application Data on Public Apps:** - **`initialState` must start unauthenticated:** ```safescript def initialState(input, ctx) return { "user": ctx.auth.user, "leads": [], "view": "login" } end ``` - **`render` must render the Login Screen when logged out:** ```safescript def render(input, ctx) if input.state.user == null return renderLoginView(input.state) end return renderDashboard(input.state) end ``` - **Server RPC functions must verify authentication before querying:** ```safescript def getLeads(input, ctx) if ctx.auth.user == null return { "error": "Unauthorized", "items": [] } end return ctx.dbQuery("leads", {}) end ``` - **Load data on client after authentication:** When the user signs in (or when an agent opens `?auth_token=...`), the client runtime dispatches an action calling the server `getLeads` function. 4. **Modifying InstantDB Permissions (Rules) & Schema:** - Rules control access for client queries and scoped user tokens. - **Syntax (CEL Expressions):** ```json { "leads": { "allow": { "view": "auth.id != null", "create": "auth.id != null", "update": "auth.id in data.ref('owner.id')", "delete": "auth.id in data.ref('owner.id')" } } } ``` - **Update via API:** `POST /api/v1/apps/:appId/resources/database/rules` with `{ "rules": { ... } }` - **Update via CLI (if working with local files):** Set `INSTANT_APP_ID` and `INSTANT_ADMIN_TOKEN` in `.env`, then run: `npx instant-cli push perms --yes` (uses `instant.perms.ts`) `npx instant-cli push schema --yes` (uses `instant.schema.ts`) --- ## 7. Vaulted Secrets - `POST /api/v1/apps/:appId/secrets` - Body: `{ "name": "STRIPE_KEY", "allowedHosts": ["api.stripe.com"], "value": "sk_live_..." }` - Secrets are stored encrypted/vaulted server-side and never sent in client bundles. - `GET /api/v1/apps/:appId/secrets` - Lists secret names and allowed hosts (values are always masked). --- ## 8. Testing & Self-Verification Loop Safescript includes a built-in testing framework where zero-argument functions are treated as unit tests using `assert({ condition, message? })`. For language-level test syntax, assertions, and pure operation testing, refer directly to the Safescript reference: 👉 **https://safescript.cc/llms.txt** SafeApp extends this by running tests directly in the application's live execution context (with vaulted secrets, InstantDB, Deno KV, and Cloud Tasks available): ### A. Writing Tests on a Branch Include test functions alongside your app functions when calling `POST /api/v1/apps/:appId/branches/:branch/functions`. By convention, name test functions with a `test` or `test_` prefix: ```safescript def testLeadScoreCalculation() res = calculateScore(21) assert({ condition: res == 42, message: "Score should be doubled" }) return true end def testStringEquality() assert({ condition: "safeapp" != "unsafe" }) return true end ``` ### B. Executing Tests via REST API Autonomous agents use this endpoint as their **self-verification loop** before merging: - **Endpoint:** `POST /api/v1/apps/:appId/branches/:branch/test` (or `GET`) - **Headers:** `Authorization: Bearer ` - **Optional Request Body:** ```json { "tests": ["testLeadScoreCalculation"], // Run specific tests only "functions": { // Ephemeral test functions (run without persisting to branch) "adHocTest": "(): boolean => { assert({ condition: 2 + 2 == 4 }); return true; }" } } ``` - **Response:** ```json { "ok": true, "allPassed": true, "passed": 2, "failed": 0, "total": 2, "results": [ { "name": "testLeadScoreCalculation", "status": "passed", "durationMs": 1 }, { "name": "testStringEquality", "status": "passed", "durationMs": 0 } ] } ``` If an assertion fails, the result shows `"status": "failed"` and the error message thrown by `assert`. ### C. Automated Test Enforcement on Merge When an agent calls `POST /api/v1/apps/:appId/branches/:branch/merge`, SafeApp automatically discovers and runs all tests on the source branch: - If all tests pass, the merge proceeds to check the policy diff gate. - If any test fails, the merge is **rejected** with HTTP 422: ```json { "ok": false, "status": "tests_failed", "error": "Merge rejected: Test 'testLeadScoreCalculation' failed: Assertion failed: Score should be doubled", "failedTest": "testLeadScoreCalculation" } ``` - To bypass automated testing during merge (e.g. emergency hotfix), pass `{ "skipTests": true }`. --- ## 9. In-Process Function Execution & Server RPC - `POST /api/v1/apps/:appId/branches/:branch/run` - Executes a single function in-process on the server. - Body: `{ "functionName": "calculateScore", "args": { "val": 21 } }` - `POST /api/v1/apps/:appId/branches/:branch/rpc` - Invoked automatically by client bundle RPC shims to execute server-placed functions with secrets injected. --- ## 10. Live App Serving & Previews - **SSR Page (HTML):** `GET /preview/:appId/:branch` - Server renders `initialState` and `render` into crawlable, SEO-friendly HTML with inlined hydration state. - If `isPrivate: true` and caller is unauthenticated, serves the Turnkey Hosted Login Page (HTTP 401). - Passing `?auth_token=` sets session cookie and auto-hydrates the preview. - **Client Bundle (JS):** `GET /preview/:appId/:branch/app.js` - Compiles client-placed functions into standard browser ES modules with automatic RPC shims for server functions.