Agent instructions
OpenCloud build skill
---
name: opencloud-build-apps
description: Build, validate, deploy, verify, operate, and troubleshoot static web applications on OpenCloud through hosted MCP or the app-scoped CLI, with its versioned JavaScript SDK, bounded PostgreSQL data, central Auth, managed Files, private Realtime, Functions, app email, cron, safe telemetry, and backups. Use for live OpenCloud app work or for an honestly offline-validated bundle when no online execution path is available.
---
# Build apps on OpenCloud
Use this skill as the execution policy and <https://docs.opencloud.ai> as the
interface reference. When a usable terminal is available, use the public,
versioned OpenCloud CLI at <https://github.com/opencloud-ai/cli> even if hosted
OpenCloud MCP tools are also exposed. Reserve MCP for surfaces that cannot run
the CLI or call the API, such as mobile-only ChatGPT or Claude sessions. MCP
and the CLI use the same control-plane contract, server-side drafts, validator,
deployment executor, and verification operation. Do not inspect platform
internals to infer undocumented behavior.
## Select CLI, MCP, or offline mode
Inspect the execution surface first. When a terminal is available:
1. Install and verify the pinned CLI release below.
2. Run `opencloud auth status`. Reuse a healthy account login; otherwise run
`opencloud login` and let the user approve it in their browser.
3. Run `opencloud app list`. Select an existing app or create one with
`opencloud app create`.
4. From that app's source directory, run `opencloud app connect "$APP_ID"`.
5. Use the CLI for drafts, isolated development, deployment, verification, and
operations. Do not switch to MCP merely because both interfaces are
available.
When no usable terminal or direct API path exists and OpenCloud MCP tools are
present:
1. Call `get_opencloud_session`.
2. If it reports disconnected, call `connect_opencloud` for a returning user,
or continue with `start_onboarding` for first registration.
3. For first registration, ask once for the email if necessary and call
`start_onboarding` with the agreed project name and visibility.
4. A new email receives a provisional identity, first app, canonical HTTPS URL,
and 24-hour grant immediately. The MCP server retains the grant; never ask
for or expose its token. If the result is `provisional_ready` with
`verification.emailSent: false`, continue the build without retrying
onboarding and tell the owner that confirmation delivery is delayed.
5. Give the owner the returned `launchUrl` as the primary link while email
confirmation or deployment is pending. The confirmation message names the
project and explains that approval is required to continue using it. The
launch page signs the confirming browser in and opens the project when the
deployment is ready.
6. An existing email must approve the emailed request. Then call
`complete_onboarding` with the returned onboarding ID. Its private
completion token remains inside the MCP session.
7. Resolve the assigned app from the onboarding result or call `get_app`.
Call `get_app_starter` with every capability implied by the request: `auth`,
`data`, `files`, `functions`, `ai`, `email`, `realtime`, `telemetry`, and/or
`cron`.
Implement every returned `resolvedCapabilities` and `capabilityChecklist`
entry. Use its current minimal manifest, SDK pattern, external E2E
specification, and build invariants before authoring the first draft; call
it again if the product scope grows.
8. Use source-draft, dev-session, verification, and promotion tools for all
live work. Do not require the CLI as a second preflight.
## Understand how hosted MCP works
OpenCloud MCP is a stateful Streamable HTTP protocol, not a remote shell:
- Use `/mcp/build` for a native app-building agent. It exposes only onboarding,
assigned-app inspection, draft, isolated dev, promotion, deployment
evidence, and verification tools. Use `/mcp` for the full lifecycle on
supported no-terminal connector surfaces. Terminal-enabled Codex, Claude
Code, and similar agents use the CLI instead.
- Initialization returns an opaque `mcp-session-id`. Reuse it on subsequent
requests and deletion. A session belongs to the endpoint that created it;
do not move one session between `/mcp` and `/mcp/build`.
- The server retains only bounded onboarding state and a provisional grant
created in that session. OAuth bearer credentials remain request-scoped.
Neither credential is tool output, source content, or something to request
from the user.
- Tools are authoritative actions. `opencloud-skill` is a supplemental MCP
resource; some clients do not read resources automatically, so server
instructions and `get_app_starter` carry the essential workflow too.
- Draft files live on OpenCloud. `expectedRevision` prevents overwriting a
newer draft, and each `baseSha256` prevents overwriting a file that changed
since it was read. On conflict, call `get_draft` plus `list_files` or
`read_files` and rebase the intended change; never guess a revision/hash.
- `validate_draft` builds one immutable artifact from one source revision.
Read `sourceManifest` and `sourceFiles` as author inputs, `artifactFiles` as
packaged output, and every diagnostic `path`/`suggestedFix` before editing.
- A dev session materializes only a validated revision in an isolated
database. Each later edit requires validation and `apply_dev_revision`.
Synthetic data and explicit Function invocations produce evidence without
touching production.
- `verify_dev_session` runs strict Chromium TLS/runtime checks plus the
immutable `tests/opencloud.e2e.js` specification. Its receipt binds the
exact revision, artifact, migration digest, and E2E source hash; any later
edit invalidates the receipt. External tests run in independent child
sandboxes with fresh migration-replayed data, Storage, Functions, and users.
Up to five tests run concurrently by default; callers may request 1 through
10 without changing the test artifact.
- Promotion and production verification return durable operations. Poll the
matching `operation.id` with `get_operation` to a terminal state, then poll
the matching verification. A queued operation or preview is not done.
Recover from failures by preserving evidence and changing the smallest
boundary:
| Failure | Correct recovery |
| ------------------------------------ | ------------------------------------------------------------------------------------------------ |
| draft revision or file hash conflict | Re-read draft/files and reapply only the intended change. |
| manifest diagnostic | Fix the reported source path; use `get_app_starter` when the manifest or SDK pattern is missing. |
| development migration failure | Fix the named migration file/SQLSTATE, validate a new revision, and apply it. |
| Chromium certificate failure | Treat it as an environment TLS failure; never disable certificate validation. |
| expired/stale receipt | Reapply the exact current revision, repeat dev checks, and obtain a new receipt. |
| durable operation failure | Report the terminal failure and diagnostic; do not promote or claim completion. |
Pasting a prompt cannot install an MCP connector. If tools are absent, state
that OpenCloud must be enabled on a supported agent surface. Claude Code and
Cowork can install the OpenCloud plugin; Claude.ai, Claude Desktop, and Claude
mobile can use the hosted connector. ChatGPT plugins and arbitrary MCP tools
are not currently available directly in ChatGPT mobile chats; mobile can steer
an already configured desktop task through ChatGPT Remote.
For every terminal workflow, establish CLI execution capability before
credentials.
The pinned CLI release for this skill is `v3.1.0` and requires Node.js 22 and
npm 10 or newer. The installer downloads that exact release, verifies its
published checksum, and installs the `opencloud` command for the current user:
```bash
curl -fsSL https://docs.opencloud.ai/install.sh | bash
test "$(opencloud --cli-version)" = "3.1.0"
```
If the installer reports that its user-level binary directory is not on
`PATH`, apply the `export` command it prints before running the version check.
Do this CLI preflight before starting CLI login. If the
environment cannot execute a shell, Node.js 22/npm, HTTPS downloads, or the
verified CLI, it cannot deploy from the public CLI. If MCP is also unavailable,
work honestly offline.
Work offline only when both MCP and the verified CLI are unusable. Build and
locally validate a complete source tree, but do not claim live deployment or
verification.
## Connect with the CLI
Account login is per OS user; app connection is per source workspace:
```bash
opencloud auth status
opencloud login
opencloud app list
# Only when the requested app does not already exist:
opencloud app create --name "$PROJECT_NAME" --visibility private
opencloud app connect "$APP_ID"
opencloud doctor
```
- `opencloud login` creates a short-lived browser approval request, prints and
opens its HTTPS URL, and polls while the user signs in with a one-time email
link or configured password and explicitly approves the CLI. It does not
start a localhost callback or ask the user to paste a code, email link,
cookie, or token. Use `--no-browser` when the terminal cannot open a browser;
give the user the printed approval URL.
- Approval returns a 15-minute account access token and a rotating 30-day
refresh token. The account token may list, read, and create apps and connect
a workspace; it cannot deploy, configure an app, read secrets, or perform
owner-only actions.
- Secrets are stored under the OS credential service `ai.opencloud.cli` when a
keyring is available. Headless environments fall back to a per-user,
mode-`0600` file under `$XDG_CONFIG_HOME/opencloud/credentials` (normally
`~/.config/opencloud/credentials`) on Linux,
`~/Library/Application Support/OpenCloud/credentials` on macOS, or
`%APPDATA%\OpenCloud\credentials` on Windows. Never open, print, copy,
upload, summarize, or commit these credentials.
- `opencloud app connect` writes only a non-secret, ignored
`.opencloud/app.json` binding in the source tree. It stores a separate
24-hour app-scoped credential in the same protected credential backend. The
CLI renews that credential from the account login when it nears expiry.
- `opencloud logout` (or `opencloud auth logout`) revokes the refresh-token
family and all workspace credentials issued from it, clears the local
account credential, current workspace credential, and resolved onboarding
session, and retains non-secret workspace bindings for later reconnection.
`opencloud login --force` replaces an unusable stored login.
- `.opencloud/session.json` and `opencloud onboard` implement passwordless
first-project onboarding. `OPENCLOUD_API_URL` selects a non-default
installation, and `OPENCLOUD_TOKEN` supplies explicitly delegated authority.
Never inspect an onboarding session file or ask for a copied credential.
- OpenCloud chooses the DNS-safe title-based slug and random suffix for new
apps. Never ask the user to find an available domain.
- Treat `OPENCLOUD_EDGE_URL` only as an optional CLI transport adapter. Preserve
and report the canonical HTTPS app URL.
- Never invent IDs, URLs, credentials, secrets, operations, or results.
- Never print or commit tokens, passwords, cookies, `.env` contents, secret
values, or brokered access tokens.
Run the CLI from any directory:
```bash
opencloud <command>
```
Workspace and legacy-session discovery search parent directories, so commands
work from nested source folders. `opencloud doctor` prints a redacted view of
the CLI version, credential backend, app identity, endpoint reachability, and
deployed platform version. Pass absolute app-directory paths when more than one
app is present.
## Read only the docs you need
Read these first:
1. <https://docs.opencloud.ai/getting-started/>
2. <https://docs.opencloud.ai/getting-started/agents>
3. <https://docs.opencloud.ai/sdk/javascript/>
4. <https://docs.opencloud.ai/reference/manifest>
5. <https://docs.opencloud.ai/guides/development>
6. <https://docs.opencloud.ai/reference/verification>
7. <https://docs.opencloud.ai/guides/functions-cron>
Then read the capability page before implementing Auth, database, managed Files,
Realtime, Functions/cron, telemetry, or verification. Use
<https://docs.opencloud.ai/llms.txt> as the compact documentation index.
## Discover the assigned app
With MCP, use `list_apps` and `get_app`. Treat `get_app` as authoritative for
`appUrl`, `authUrl`, and `apiUrl`. Then call `get_app_starter` with the
assigned app ID, intended unique version, and complete capability list. Treat
its resolved checklist as required. Use its files as a current scaffold, not
permission to overwrite intentional existing source.
With the CLI:
```bash
opencloud app list
opencloud app get "$APP_ID"
opencloud app origin "$APP_ID"
```
Treat `app get` as authoritative for `appUrl`, `authUrl`, and `apiUrl`.
App-scoped agents cannot create apps, manage credentials, delete apps, or
restore backups unless their credential and user role explicitly allow it.
Provisional account grants can create multiple new apps during their 24-hour
verification window:
With MCP, call `create_app`. With the CLI:
```bash
opencloud app create \
--name "Another project" \
--visibility private
```
Use a private app when its UI or data requires a user. Let the edge redirect to
the central OpenCloud access page; do not build a second sign-in form.
## Change an existing app safely
For a change request, inspect and validate the existing bundle before editing.
Do not run `init`, replace the app ID, recreate the app, or rewrite an applied
migration. Preserve current behavior outside the request, append ordered
migrations for schema changes, update product tests and the required external
E2E specification, then deploy with a new version. Confirm the active
deployment before and after the change and leave the previous release available
for operator-authorized rollback. Preserve `runtime.sdk.version`
unless the request explicitly includes an SDK upgrade; an upgrade is a new
release and must pass the complete product and browser gates.
## Make an immediate artifact checkpoint
Create a real manifest and non-empty frontend in the first coherent file batch:
With MCP, call capability-aware `get_app_starter`, then `create_draft` and
inspect its files.
Use `apply_file_changes` with the current draft revision and per-file hashes.
Include `opencloud.yaml` plus a non-empty configured frontend in the first
coherent batch, and call `validate_draft`. Read source/artifact file lists and
diagnostic fixes from the result before continuing. Grow the app in small coherent revisions;
use `apply_dev_revision` after each later validation.
With the CLI:
```bash
opencloud init "$APP_DIR" \
--app-id "$APP_ID" \
--version "$VERSION"
opencloud artifact-check "$APP_DIR" \
--expect-app-id "$APP_ID" \
--max-files 4
opencloud validate "$APP_DIR"
```
Grow the product in small coherent batches. Re-run the checker after changing
manifest-reachable paths and validate after each runtime boundary.
## Author the deterministic bundle
Use this layout:
```text
app/
├── opencloud.yaml
├── frontend/
├── migrations/
├── functions/
└── AGENT_REPORT.md
```
Use this manifest pattern:
```yaml
schemaVersion: 2
appId: 6f9619ff-8b86-4e6e-a62a-889950f42d3e
version: 2026.07.28-1
frontend:
directory: frontend
spa: true
runtime:
sdk:
version: 2.0.0
files:
access: user
maxUploadBytes: 52428800
migrations:
- id: 0001_create_items
file: migrations/0001_create_items.sql
functions:
- name: summarize
entrypoint: functions/summarize/index.ts
access: user
- name: hourly-summary
entrypoint: functions/hourly-summary/index.ts
access: system
- name: receive-support
entrypoint: functions/receive-support/index.ts
access: system
email:
addresses:
- name: support
displayName: Example Support
function: receive-support
cron:
- name: hourly-summary
schedule: "0 * * * *"
function: hourly-summary
enabled: true
health:
path: /
secrets:
INTERNAL_SIGNING_KEY: generated
AI_API_KEY: required
ORGANIZATION_LABEL: optional
observability:
metrics:
- name: items_created
type: counter
unit: items
dimensions:
actor_type:
values: [member, admin]
- name: overdue_items
type: gauge
unit: items
```
Choose `files.access: app` only when authenticated members should share files;
keep the safer `user` default for per-user files. `files.maxUploadBytes` is an
integer from 1 byte through 100 MiB. Use a unique version for every deployment.
Pin the exact installed SDK version—never `latest` or a range. Keep migration
IDs ordered and append-only. Never write migration checksums; the CLI computes
them.
Only the canonical manifest, configured frontend tree, declared migrations,
and declared Function source trees enter the archive. Inspect the exact file
list printed by `validate`.
## Use the stable JavaScript SDK
Every frontend imports the deployment-pinned singleton from one stable path:
```js
import {
opencloud,
OpenCloudError,
OPEN_CLOUD_SDK_VERSION,
} from "/_opencloud/sdk.js";
```
Do not fetch `/_opencloud/config`, import an immutable version path, construct a
client, or read runtime credentials in application code. The edge maps the
stable module and `/_opencloud/sdk.d.ts` declarations to the exact `2.0.0`
artifact pinned by `runtime.sdk.version`. Inspect the active pin operationally
without printing runtime credentials:
```bash
opencloud app sdk-inspect "$APP_ID"
```
Use only these public methods:
| Interface | Methods |
| --- | --- |
| Version | `OPEN_CLOUD_SDK_VERSION` |
| App | `app.info()` |
| Auth | `auth.currentUser()`, `auth.requireUser()`, `auth.signInUrl()` |
| Data | `data.table(name).list/getById/create/createMany/updateById/deleteById` |
| Files | `files.upload/info/download/save/replace/remove/attach` |
| Functions | `functions.call(name, input?)`, `functions.stream(name, input?)` for `user`/`public` Functions |
| Realtime | `realtime.subscribe(topic, handler)`, `realtime.publish(topic, event, payload)` |
| Telemetry | `telemetry.summary/increment/gauge` |
| Client | `dispose()` |
The SDK returns parsed values, managed file metadata, or typed streams—not raw
HTTP responses. It owns config discovery, app identity, Function access mode,
bearer tokens, cookie forwarding, refresh, file routing, and Realtime protocol
state. Never decode JWTs, persist token material, call raw REST/Storage/Function
endpoints, or construct buckets and object paths.
Handle expected failures through the typed error contract:
```js
try {
await opencloud.data.table("items").create({ title });
} catch (error) {
if (error instanceof OpenCloudError) {
renderProblem(error.code, error.requestId, error.retryable);
} else {
throw error;
}
}
```
An `OpenCloudError` has `code`, `surface`, `status`, `requestId`, `retryable`,
and optional `details`. Show a safe product message and retain the request ID
for diagnostics. Do not parse response text or expose raw platform details.
The browser SDK deliberately exposes high-level data and managed Files
operations instead of request paths. Use `opencloud.data.table(...)` and
`opencloud.files` with opaque file IDs. Never construct REST paths, Storage
buckets, object names, owner prefixes, URLs, or authorization headers.
## Build data with RLS
Write unqualified DDL; OpenCloud selects the app schema.
For owner-isolated records:
```sql
create table items (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null default auth.uid(),
title text not null check (length(title) between 1 and 200),
created_at timestamptz not null default now()
);
create policy items_owner_access
on items for all
using (owner_id = auth.uid())
with check (owner_id = auth.uid());
```
For records shared by admitted app members, add a permissive business policy:
```sql
create policy items_member_access
on items for all
using (true)
with check (true);
```
OpenCloud forces RLS and combines business policies with a restrictive app
boundary. Deployments execute the complete history in a disposable constrained
schema on the pinned PostgreSQL runtime before touching live app data.
Read <https://docs.opencloud.ai/reference/sql> before using nontrivial SQL.
Use the bounded data API. It validates identifiers, owns authentication and
parses the result:
```js
const items = opencloud.data.table("items");
const rows = await items.list({
select: ["id", "title", "created_at"],
orderBy: { column: "created_at", direction: "desc" },
limit: 50,
});
const created = await items.create({ title: "Review evidence" });
await items.updateById(created.id, { title: "Reviewed evidence" });
```
The exact results are `list -> Row[]`, `getById -> Row | null`, `create ->
Row`, `createMany -> Row[]`, `updateById -> Row | null`, and `deleteById ->
boolean`. There is no `.rows` wrapper and no `insert`, `upsert`, or broad
mutation alias.
Reads use the current user when present and can follow deliberately public RLS.
Writes require a user. Mutate only through `updateById` and `deleteById`; never
build filters that can accidentally update or delete multiple rows.
## Use Files, Realtime, Functions, and telemetry
Upload a browser `Blob` or `File` directly. OpenCloud returns an opaque ID and
owns paths, authorization, size enforcement, names, content types, and
idempotency:
```js
const uploaded = await opencloud.files.upload({
data: file,
name: file.name,
contentType: file.type || "application/octet-stream",
onProgress: ({ percent }) => renderUploadProgress(percent),
});
```
`upload` and `replace` automatically retry one transient failure with the same
private idempotency key. Do not invent or manage a retry key in app code.
When a file belongs to a database record, prefer the compound helper. Create
the table with `file_id`, `file_name`, `file_type`, and `file_size` columns (or
provide an explicit `columns` mapping):
```js
const { file: storedFile, record } = await opencloud.files.attach({
data: file,
name: file.name,
table: "item_attachments",
values: { item_id: itemId },
});
```
`attach` reconciles an ambiguous metadata write and cleans up a definite
failure. If it throws `FILE_ATTACHMENT_INCOMPLETE`, retain the returned file
details for an explicit cleanup retry. Store the opaque ID; never store or
construct a bucket/object path, send Storage headers, or request S3
credentials. Use `download`, `save`, `replace`, and `remove` with the returned
file object or ID.
Subscribe to private Realtime topics with a logical purpose:
```js
const unsubscribe = await opencloud.realtime.subscribe("items", ({ event, payload }) => {
if (event === "changed") void reloadItems(payload);
});
void opencloud.realtime.publish("items", "changed", {
reason: "item-created",
}).catch((error) => console.warn("Realtime notification skipped", error));
```
Call `unsubscribe()` when the view unmounts and `opencloud.dispose()` when the
app tears down. Realtime is best-effort invalidation, not durable truth. Check
`app.info().capabilities.realtime` before subscribing, never await publication
from a primary CRUD action or verifier cleanup, persist through `opencloud.data`
first, and let recipients reload durable truth. Send identifiers, not secrets
or full records. Query current controls again after rendering replaces nodes.
Write Deno-compatible Functions with the first-party server boundary:
```ts
import { defineFunction, errors, schema } from "@opencloud/server";
export default defineFunction({
input: schema.object({ itemId: schema.uuid() }),
handler: async ({ input, user, data, secrets, log, requestId, environment }) => {
if (!user) {
throw errors.unauthorized("SIGN_IN_REQUIRED", "Please sign in");
}
const item = await data.table("items").getById(input.itemId, {
select: ["id", "title"],
});
if (!item) throw errors.notFound("ITEM_NOT_FOUND", "Item not found");
log.info("item loaded", { itemId: input.itemId });
return {
item,
requestId,
environment,
secretPresent: Boolean(secrets.get("AI_API_KEY")),
};
},
});
```
`defineFunction` accepts exactly `{ input, handler }`; `input` is already
parsed. Handler-only definitions and `input.json/parse/text` do not exist.
Use `ai.generateText` for text or `ai.generateObject` with a `schema` for a
validated object. Model selection, provider envelopes, idempotency, retries,
and timeouts remain private platform behavior.
When a Function reports its pinned SDK version, import
`OPEN_CLOUD_SDK_VERSION` from `@opencloud/server`; never hard-code the version
string.
The Function context contains exactly `input`, `user`, `data`, `files`, `ai`,
`email`, `secrets`, `log`, `requestId`, and `environment`. These are high-level,
invocation-scoped capabilities; raw database clients, buckets, provider keys,
and bearer tokens are not exposed. The outer platform gateway allocates a
request ID before module loading and catches imports, rejected promises,
timeouts, invalid responses, and platform-call failures. Unknown production
errors are generic; dev diagnostics are bounded and redacted.
Declare browser access as `user` or `public` in the manifest. Declare every
cron/platform-only Function as `system`; the browser SDK rejects it with
`FUNCTION_SYSTEM_ONLY`. Call a browser Function with
`opencloud.functions.call(name, input)` for parsed output or
`opencloud.functions.stream(name, input)` for a byte stream; the SDK selects
the correct auth mode from the manifest.
Declare app-owned identities under `email.addresses`. A receive-capable alias
references a Function with `access: system`; that same allocated address is
used for sending and receiving. Function code sends only from a declared alias
with `email.send(message, { idempotencyKey })`. Provider credentials and SMTP
are never exposed. Development Functions capture every outbound message and
support synthetic `.test` inbound injection through the email CLI commands or
MCP operations.
Development Functions remain dormant until an explicit
`invoke_dev_function` MCP call, `app dev invoke` CLI command, or deliberate
preview interaction calls them. Dev
Functions get the isolated dev schema, isolated values for secrets declared as
`generated`, no owner-provided production values, and no cron triggers. Before
verification, explicitly invoke every Function declared by
the exact active revision with safe dummy input and inspect
`list_dev_invocations` or `app dev requests`;
the latest invocation of each must succeed. Repeat these checks after any sync.
Declare each Function secret in the manifest with one mode:
- `generated`: OpenCloud creates and retains a strong production value
automatically and supplies an isolated synthetic value in development;
- `required`: the owner must enter a value before production; or
- `optional`: the owner may enter a value, but its absence never blocks a
release and `secrets.get(name)` returns `undefined`.
Do not call a setup command for `generated` secrets. Use `generate_secret` or
the following CLI command only when the user explicitly asks to rotate one:
```bash
opencloud secret rotate "$APP_ID" INTERNAL_SIGNING_KEY
```
For a `required` or `optional` owner-supplied value, call MCP
`create_secret_entry_link` or use:
```bash
opencloud secret configure "$APP_ID" AI_API_KEY
```
Give the returned URL to the user. Do not ask them to paste the value into the
agent conversation. Return only a presence flag, version marker, or one-way
digest for a secret.
An AI Function also needs the owner to select a Codex login for that app. If a
development invocation reports `app_ai_credential_not_selected`, give the
owner the existing non-secret launch URL once and resume after that explicit
consent action; never substitute or copy the coding agent's credential.
Use exact telemetry fields:
```js
const summary = await opencloud.telemetry.summary();
const rest = summary.activity.surfaces.rest;
const freshness = summary.activity.telemetry;
```
All six surfaces are always present. `usage` can be null. Treat unavailable,
missing, or truncated activity honestly; never label absence as healthy. Read
<https://docs.opencloud.ai/sdk/javascript/telemetry> for the exact response.
Define custom metrics only when they express a product or workflow signal the
platform cannot derive. Keep the catalog small and bounded. Never use user IDs,
emails, URLs, object keys, or arbitrary strings as dimensions.
```js
await opencloud.telemetry.increment("items_created", 1, {
dimensions: {
actor_type: "member",
},
});
await opencloud.telemetry.gauge("overdue_items", overdueCount);
```
The SDK creates the private idempotency key and safely retries one transient
write. Application code supplies only the metric value and bounded declared
dimensions. Browser measurements are product signals, not trusted security
evidence.
CLI `v3.1.0` provides `alert-rule` and `agent-feed` for configuring and
reading these signals. Do not bypass the protected credential store. Prefer the
Agent Feed over raw logs or metrics. Alerts
inform the agent; they do not authorize automatic rollback, deletion, or
shared-platform repair. See the telemetry reference for the exact contract.
## Validate, deploy, and verify the real UI
With MCP, call `validate_draft`, then:
1. `start_dev_session` for the exact validated revision;
2. `apply_dev_revision` after every later coherent validated change;
3. `request_dev_app` to inspect the preview and REST reads;
4. `mutate_dev_data` only for isolated dummy fixtures, using the high-level
`table`, `action`, `values`, and optional `id` fields. It authenticates as
synthetic user A and preserves that identity through browser verification;
never pass a REST path or user credential;
5. `invoke_dev_function` for each declared Function and
`list_dev_invocations` for correlated diagnostics;
6. for app email, inspect `list_dev_email_captures` and
`get_dev_email_capture`, then exercise every receive-capable alias with
`inject_dev_email` and a reserved `.test` sender;
7. `verify_dev_session` for the exact active revision;
8. `promote_dev_revision` for that receipt;
9. `get_operation` until the durable deployment is terminal;
10. `verify_app`, its operation, and `get_verification_run` until every required
production gate is terminal; and
11. `get_app` to confirm the active deployment and canonical HTTPS URL.
Make at most ten `verify_dev_session` attempts for one build. Use validation,
preview reads, and explicit Function evidence between attempts; after a tenth
failure, stop and report that structured result instead of looping.
With the CLI, run app-local syntax checks/tests/build plus:
```bash
opencloud validate "$APP_DIR"
```
Start the isolated development loop before changing production:
```bash
opencloud app dev start "$APP_DIR"
# after each coherent edit batch
opencloud app dev sync "$APP_DIR"
opencloud app dev request "$APP_DIR" /
```
The capability URL has a separate migration-replayed schema. Auth, data,
managed Files, and Functions are isolated from production; generated secrets
receive synthetic values. Owner-configured secrets, Realtime, runtime
telemetry, and cron are unavailable. For external E2E verification, each
`REQ-###` test gets a child sandbox with a fresh schema, managed Files
namespace, Function namespace, and three short-lived synthetic browser
sessions: owner A, admitted member B, and unrelated user C. The platform drops
those resources after the test even when the app's own UI cleanup fails.
Functions execute only when the CLI or a deliberate preview interaction calls
them.
Frontend-only syncs preserve dev data. A migration definition change resets
the dev schema and replays the complete ordered history. Treat every listed
unavailable capability as unavailable; never fall back to production.
Create isolated dummy fixtures without touching production:
```bash
opencloud app dev data "$APP_DIR" items create \
--values '{"title":"Preview item"}'
```
For an email-capable app, exercise its dev-only mailbox without contacting the
production provider:
```bash
opencloud app dev email inject "$APP_DIR" \
--to support --from customer@example.test \
--subject "Test request" --text "Please acknowledge this message."
opencloud app dev email list "$APP_DIR"
opencloud app dev email get "$APP_DIR" "$MESSAGE_ID"
```
Run the exact-revision verification and promote its receipt:
```bash
opencloud app dev verify "$APP_DIR"
opencloud app dev promote "$APP_DIR" \
--idempotency-key "$IDEMPOTENCY_KEY"
```
Any source, migration, or production-base change invalidates promotion. Direct
`deploy` remains a lower-level automation path but is not the default agent
workflow because it has no dev verification receipt.
`promote_dev_revision` (or CLI `app dev promote`) is the default completion
path. Follow the durable deployment, run authoritative feature-aware production
verification, report the live URL, and stop dev only after success. The copied
user prompt authorizes promotion of the exact verified receipt; do not pause
for another confirmation. Dev is an iteration environment, not a finished
result.
MCP `verify_app` (or CLI `app verify`) remains available as a standalone
durable release gate. It checks the recorded deployment artifact and pinned
OpenCloud SDK, canonical HTTPS health, Chromium diagnostics, and the same immutable
`tests/opencloud.e2e.js` hash used in dev. If a newer deployment became active,
verification refuses instead of following the moving pointer. CLI v3 provides
no partial local verification substitute.
Every new app must replace the starter's failing external spec with tests whose
titles begin with stable `REQ-###` IDs. Tests import exactly `test` and `expect`
from `@opencloud/test`, drive visible accessible controls, assert exact durable
create/reload/update/filter/navigation/delete outcomes where applicable, and
clean unique fixtures through the UI. Each test needs at least one trusted
action and one trusted assertion. The bounded runner supplies owner, admitted
member, second-owner-tab, and unrelated pages plus deterministic file fixtures;
it rejects skip/only, direct network/backend access, evaluate, routing, direct
navigation, and script injection. Test code runs in a blank network-isolated
controller and cannot read app globals, cookies, SDK clients, or response
bodies. The supplied `marker` is short and unique to the current REQ test
rather than shared with the other tests in the run. Use
`uniqueValue(prefix, maximumLength)` for bounded fields so the unique suffix
cannot be truncated, and `clickIfVisible(locator)` only for genuinely optional
cleanup controls.
Each REQ test starts with fresh browser contexts and an independent runtime
sandbox. The scheduler runs up to five tests concurrently by default; the
verification request may set `parallelism` from 1 through 10. Role names accept bounded
regular expressions for accessible names, label/text/test-ID/placeholder
queries, and `filter({ hasText })`; bounded scalar matchers may supplement query checks,
but scalar checks do not replace the required page or locator assertion. Scope
duplicate labels through their dialog, form, or card. In `finally`, close open
overlays and wait for cleanup state to converge before interacting with a page
that another user changed. If cleanup also fails, the verifier preserves the
first primary command error and adds bounded browser/network/status context.
`selectOption` accepts a raw value and the bounded Playwright `{ value }`,
`{ label }`, and `{ index }` forms.
Verification success receipts and structured failures include an attempt
report with revision/artifact/test hashes, requested parallelism, outcome,
failure classification, and phase timings. Per-test evidence separates sandbox
setup, browser execution, cleanup, and total time. If another request is still
verifying the same dev session, wait after `DEV_VERIFICATION_IN_PROGRESS`; the
platform preserves the active attempt instead of starting overlapping sandbox
cleanup.
The platform independently fails primary-flow HTTP 404/405, mobile horizontal
overflow, unnamed visible controls, and primary touch targets below 44 by 44
pixels. Private production verification retains the anonymous Auth redirect
check, then runs the external spec with short-lived admitted synthetic users
and removes their sessions, grants, and identities.
Run the same UI assertions in dev and production where possible. Managed Files
and Functions are available inside each disposable E2E sandbox but not on the
ordinary capability URL. Realtime and cron remain unavailable in dev and must
never fall back to production. Production E2E verification remains serial
against its candidate deployment. Realtime remains best-effort and cannot
block CRUD or cleanup; `opencloud.data` is the durable source of truth.
Inspect deployments, error logs, usage, and cron history after normal traffic.
## Run manifest-derived release verification
The schema 2 manifest and immutable `tests/opencloud.e2e.js` artifact are the
verification inputs. Use the existing `@opencloud/test` API rather than
creating another test DSL or a local partial-verification configuration.
```bash
opencloud app verify "$APP_ID"
```
The durable server operation checks active release state, exact runtime and SDK
metadata, canonical HTTPS health, Chromium diagnostics, and the required
external E2E specification. Use the isolated dev tools to exercise Data, Files, Auth,
and every declared Function before promotion; inspect cron history, logs, and
usage after normal production traffic.
## Recover only with explicit authorization
Create and list backups freely when requested. Do not roll back or restore
merely to test a deployment. Code rollback replaces runtime code/config but
does not reverse migrations; database restore can discard newer writes.
Never delete, archive, stop, roll back, or restore an app unless the user
clearly authorized that exact action.
## Report completion honestly
For online work, record canonical URLs, IDs, exact validation/UI/verifier/log/
usage/cron outcomes, deployment state, observed friction, product limitations,
and confirmation that the app remains active.
For offline work, report the artifact digest and exact remaining online steps.
An offline-valid bundle is useful progress, not a deployed app.