---
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, PostgreSQL/REST, central Auth, Storage, 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.
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` for the current minimal manifest, SDK pattern,
   interaction hook, and build invariants before authoring the first draft.
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 `v1.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)" = "1.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 legacy
  session, and retains non-secret workspace bindings for later reconnection.
  `opencloud login --force` replaces an unusable stored login.
- Legacy `.opencloud/session.json`, `opencloud onboard`,
  `OPENCLOUD_API_URL`, and `OPENCLOUD_TOKEN` remain compatibility paths, not
  the default flow. Do not inspect a legacy session file or ask for a copied
  connection pack.
- 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, Storage,
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 and intended unique version. 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.javascriptSdk.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 `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/
├── opencloud.verify.yaml
└── AGENT_REPORT.md
```

Use this manifest pattern:

```yaml
schemaVersion: 1
appId: 6f9619ff-8b86-4e6e-a62a-889950f42d3e
version: 2026.07.28-1
frontend:
  directory: frontend
  spa: true
runtime:
  javascriptSdk:
    version: 0.2.2
storage:
  authorization: owner-prefix
migrations:
  - id: 0001_create_items
    file: migrations/0001_create_items.sql
functions:
  - name: summarize
    entrypoint: functions/summarize/index.ts
    verifyJwt: true
cron:
  - name: hourly-summary
    schedule: "0 * * * *"
    function: summarize
    enabled: true
health:
  path: /
requiredSecrets:
  - AI_API_KEY
observability:
  metrics:
    - name: items_created
      type: counter
      unit: items
      dimensions:
        actor_type:
          values: [member, admin]
    - name: overdue_items
      type: gauge
      unit: items
```

Choose `storage.authorization: app` for a deliberately shared namespace or
`owner-prefix` for per-user files. Use a unique version for every deployment.
Pin an exact installed SDK version—never `latest` or a range. The CLI resolves
an omitted pin once while building an older author manifest, but generated
manifests should be explicit. 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 exact JavaScript SDK

Read the module path from runtime config:

```js
const runtime = await fetch("/_opencloud/config").then((response) =>
  response.json(),
);
const { createOpenCloudClient } = await import(runtime.javascriptSdk.module);
const opencloud = createOpenCloudClient();
```

The current exact import is:

```js
import { createOpenCloudClient } from "/_opencloud/sdk/js/v0.2.2/index.js";
```

Prefer runtime discovery in generators. Verify
`runtime.javascriptSdk.version`, `module`, and `types`; do not guess a latest
path. Runtime discovery returns the SDK pinned to the active deployment, so a
platform SDK release cannot move an existing app. TypeScript declarations are
served beside the module. Inspect the active pin without printing runtime
credentials:

```bash
opencloud app sdk-inspect "$APP_ID"
```

Use only these public methods:

| Interface | Methods                                                                   |
| --------- | ------------------------------------------------------------------------- |
| Client    | `config()`, `session()`, `dispose()`                                      |
| REST      | `rest.request(path, init?)`                                               |
| Storage   | `storage.request(path, init?)`                                            |
| Functions | `functions.invoke(name, init?)`, `invokePublic(name, init?)`              |
| Realtime  | `realtime.channel(name, options?)`                                        |
| Channel   | `connect()`, `broadcast()`, `onBroadcast()`, `onStateChange()`, `close()` |
| Telemetry | `telemetry.summary()`, `telemetry.increment()`, `telemetry.gauge()`       |

There is no `functions.request`, `channel.on`, or `channel.subscribe`.

The SDK owns app identity headers, bearer tokens, cookie forwarding, and
refresh. The safe session exposes only user/profile and expiry metadata. Never
decode JWTs or persist token material.

## 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 SDK for REST:

```js
const response = await opencloud.rest.request(
  "items?select=id,title,created_at&order=created_at.desc",
);
if (!response.ok) throw new Error(await response.text());
```

Authenticated mode is the default. Use `{ auth: "anonymous" }` only for a
deliberately public RLS read.

## Use Storage, Realtime, Functions, and telemetry

For `owner-prefix`, start each decoded object name with the exact
`session.userId`, encode segments independently, and persist object metadata in
an RLS-protected table. Never request S3 credentials or use platform buckets.

Create private Realtime channels with a logical purpose:

```js
const channel = opencloud.realtime.channel("items");
channel.onBroadcast(({ event, payload }) => {
  if (event === "changed") void reloadItems(payload);
});
await channel.connect();
await channel.broadcast("changed", { reason: "item-created" });
```

Close channels on teardown. Send identifiers, not secrets or full records.

SDK `0.2.1` uses receiver-safe Realtime timers. Do not rebind browser globals.
Only a deliberately frozen `0.2.0` import needs its documented compatibility
shim; prefer the version selected by runtime config.

Write Deno-compatible Functions with the first-party server boundary:

```ts
import { defineFunction, httpError } from "@opencloud/server";

defineFunction(async ({ input, db, secrets, log, requestId, environment }) => {
  const body = await input.json<{ itemId?: string }>();
  if (!body.itemId) {
    throw httpError(400, "ITEM_ID_REQUIRED", "itemId is required");
  }
  const items = await db.from("items").select("id,title", {
    filters: { id: body.itemId },
  });
  log.info("item loaded", { itemId: body.itemId });
  return {
    items,
    requestId,
    environment,
    secretPresent: Boolean(secrets.get("AI_API_KEY")),
  };
});
```

`@opencloud/server` binds database, Auth, Storage, secrets, input, and logs to
the current app environment. 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.

Use browser `functions.invoke` for `verifyJwt: true` and `invokePublic` for
`verifyJwt: false`. 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 and no production secrets or
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 secret names in the manifest and provision values separately. For a
value OpenCloud may generate, call the MCP `generate_secret` tool or use:

```bash
opencloud secret generate "$APP_ID" INTERNAL_SIGNING_KEY
```

For a user-supplied provider key, 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.

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",
  },
  idempotencyKey: `item-created:${item.id}`,
});
await opencloud.telemetry.gauge("overdue_items", overdueCount);
```

Use an idempotency key for counter increments that may be retried. Browser
measurements are product signals, not trusted security evidence.

CLI `v1.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;
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, Storage, 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 an explicit compatibility 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. `app smoke` and `app verify-ui` are local diagnostics,
not substitutes.

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 ({ client, config, session }) => {
  document.querySelector("[data-open-workspace]")?.click();
  const ready = document.querySelector("[data-workspace-ready]");
  if (!ready) throw new Error("workspace did not render");
  return {
    passed: true,
    checks: ["opened workspace", `matched app ${config.appId}`],
    coverage: ["view-transition", "state-assertion"],
  };
};
```

The harness supplies the first-party client, exact runtime config, and safe
session. 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 or CLI flag 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-aware two-user verification

Use only the pre-provisioned verification user variables; never print them.
Place `opencloud.verify.yaml` beside the app manifest:

```yaml
schemaVersion: 1
data:
  mode: owner
  table: items
  ownerColumn: owner_id
  markerColumn: title
  insert: {}
storage:
  objectPrefix: opencloud-verify/items
realtime:
  topic: items
function:
  name: summarize
  secretName: OPENCLOUD_VERIFY_SECRET
  digestField: secretDigest
  presentField: secretPresent
cron:
  name: hourly-summary
```

Use `data.mode: owner` for isolation and forged-owner denial or `shared` when
both admitted users should see both fixtures. The verifier rejects unknown
fields and derives Storage authorization from `opencloud.yaml`. It validates
Function, secret, and enabled cron names against the manifest before live work.

```bash
opencloud verify "$APP_ID" "$APP_DIR/opencloud.verify.yaml"
```

The verifier checks sessions, the declared data mode, manifest-derived Storage,
private Realtime, Function Auth and secret rotation, a deterministically
triggered cron invocation, logs, and usage. It cleans its fixtures.

## 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.
