---
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, 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`, `realtime`, `telemetry`, and/or `cron`.
   Implement every returned `resolvedCapabilities` and `capabilityChecklist`
   entry; 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 app's
  interaction contract. Its receipt binds the exact revision, artifact, and
  migration digest; any later edit invalidates the receipt.
- 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 `v2.0.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)" = "2.0.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 browser
interaction contract, 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
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.

## 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);
});
await opencloud.realtime.publish("items", "changed", {
  reason: "item-created",
});
```

Call `unsubscribe()` when the view unmounts and `opencloud.dispose()` when the
app tears down. Send identifiers, not secrets or full records.

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.

The Function context contains exactly `input`, `user`, `data`, `files`, `ai`,
`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. 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 generate "$APP_ID" INTERNAL_SIGNING_KEY
```

For a `required` or `optional` owner-supplied value, call MCP
`create_secret_entry_link` or use:

```bash
opencloud secret entry-link "$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 `v2.0.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. `verify_dev_session` for the exact active revision;
7. `promote_dev_revision` for that receipt;
8. `get_operation` until the durable deployment is terminal;
9. `verify_app`, its operation, and `get_verification_run` until every required
   production gate is terminal; and
10. `get_app` to confirm the active deployment and canonical HTTPS URL.

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 and no production
data, secrets, Auth, managed Files, Realtime, or cron. Functions only execute 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" /rest/v1/items \
  --method POST --body '[{"title":"Preview item"}]'
```

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
active release state, the exact artifact and deployment-pinned SDK, canonical
HTTPS health, Chromium diagnostics, and the same app-declared primary-flow
contract used in dev. CLI v2 intentionally provides no partial local
verification substitute.

For a product-specific gate, mark the contract as required:

```html
<meta name="opencloud-ui-contract" content="required" />
```

Then expose a bounded browser function before the page finishes booting:

```js
globalThis.__opencloudVerify = async ({ opencloud, app, user }) => {
  document.querySelector("[data-open-workspace]")?.click();
  const ready = document.querySelector("[data-workspace-ready]");
  if (!ready) throw new Error("workspace did not render");
  const refreshedApp = await opencloud.app.info();
  return {
    passed: refreshedApp.id === app.id,
    checks: [
      "opened workspace",
      `matched app ${app.id}`,
      user ? "loaded verification user" : "rendered signed-out state",
    ],
    coverage: ["view-transition", "state-assertion"],
  };
};
```

The harness supplies the stable `opencloud` singleton, safe app information,
and the safe current user or `null`; it never exposes raw config, credentials,
or session material. It accepts 1–20 short check names, applies a 10-second timeout, and
requires both `view-transition` and `state-assertion` coverage when the meta
contract requires interactions. It still fails on browser/network
diagnostics produced during the interactions.

Inspect deployments, error logs, usage, and cron history after normal traffic.

## Run manifest-derived release verification

The schema 2 manifest and app-declared browser interaction hook are the only
verification inputs. Do not create a second verification 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
interaction contract. 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.
