Develop safely before production
OpenCloud development sessions let an agent see and exercise the exact app it is editing without deploying it over the production release.
Each session has:
- a stable, unguessable preview URL;
- an isolated PostgreSQL schema built by replaying the app's canonical migrations;
- immutable frontend and Function revisions;
- correlated browser, HTTP, and Function diagnostics; and
- a verification receipt bound to the exact artifact, migrations, and production base deployment.
The preview cannot read production data or secrets. Auth, Storage, Realtime, and cron are unavailable. Development Functions remain dormant until an explicit CLI command or deliberate preview interaction invokes them.
Start the loop
From the app source directory:
"$OPENCLOUD_CLI" validate "$APP_DIR"
"$OPENCLOUD_CLI" app dev start "$APP_DIR"start creates a source draft, synchronizes only changed files, runs the authoritative canonical validator, applies migrations to a dev-only schema, and prints the capability URL. It stores only IDs and hashes in .opencloud/dev.json; no credential is copied into that file.
Edit locally, then update the same preview:
"$OPENCLOUD_CLI" app dev sync "$APP_DIR"
"$OPENCLOUD_CLI" app dev request "$APP_DIR" /
"$OPENCLOUD_CLI" app dev status "$APP_DIR"Frontend-only changes retain dev data. When the ordered migration definition changes, OpenCloud drops the dev schema and replays the complete migration history from empty. This makes migration drift visible without touching production. Add dummy records through the preview UI or the dev-only fixture command; do not put test fixtures in production migrations:
"$OPENCLOUD_CLI" app dev data "$APP_DIR" /rest/v1/events \
--method POST --body '[{"name":"Preview workshop","capacity":12}]'The browser runtime config says environment: "dev" and lists unavailable capabilities. Preview responses use Cache-Control: no-store.
Test Functions explicitly
Use the first-party server library in every new Function:
import { defineFunction, httpError } from "@opencloud/server";
defineFunction(async ({ input, db, requestId, environment, log }) => {
const value = await input.json<{ eventId?: string }>();
if (!value.eventId) {
throw httpError(400, "EVENT_ID_REQUIRED", "eventId is required");
}
const rows = await db.from("events").select("id,capacity", {
filters: { id: value.eventId },
});
log.info("capacity checked", { eventId: value.eventId });
return { rows, requestId, environment };
});@opencloud/server supplies environment-bound database, Auth, Storage, secret, input, and logging helpers. The platform's outer runtime boundary allocates a request ID before loading user modules and catches import errors, rejected promises, timeouts, invalid responses, and platform-call failures. Production returns a safe generic message for unknown errors. Development returns bounded diagnostics with secret-like values redacted.
Invoke a dev Function only when its side effects are intended:
"$OPENCLOUD_CLI" app dev invoke "$APP_DIR" capacity \
--body '{"eventId":"00000000-0000-4000-8000-000000000001"}'
"$OPENCLOUD_CLI" app dev requests "$APP_DIR"The invocation gets the dev schema and an empty secret set. It cannot inherit production secrets or cron triggers. Its response, duration, request ID, and redacted error are recorded for the session. Before verification, explicitly invoke every Function declared by the active revision with safe dummy input; the latest invocation of each must succeed. A source sync creates a new immutable revision, so repeat these invocations after every sync that you intend to verify.
The preview frontend may call the normal browser SDK client.functions.invoke(...) or invokePublic(...). OpenCloud routes that deliberate call to the current dev Function revision and records it as browser-dev; merely loading the preview never boots a Function.
Declare the primary browser flow
The verification gate fails on page exceptions, console errors, failed same-origin requests, and server 5xx responses. Expected application 4xx responses such as OUT_OF_STOCK may be asserted by the contract. Interactive apps should require a bounded primary-flow check:
globalThis.__opencloudVerify = async ({ client, config, session }) => {
document.querySelector("[data-create-event]")?.click();
const form = document.querySelector("[data-event-form]");
if (!form) throw new Error("event form did not render");
return {
passed: true,
checks: ["opened event form", `matched ${config.appId}`],
coverage: ["view-transition", "state-assertion"],
};
};Then run:
"$OPENCLOUD_CLI" app dev verify "$APP_DIR"A successful receipt is invalidated by any later source change, migration change, or production deployment.
Promote the exact verified revision
"$OPENCLOUD_CLI" app dev promote "$APP_DIR" \
--idempotency-key "$IDEMPOTENCY_KEY"Promotion does not rebuild from the working directory. It deploys the exact validated draft revision named by the unexpired receipt. If production changed after the dev session started, or the draft changed after verification, promotion refuses and requires a fresh verification path.
By default the promote command follows the deployment, runs the same feature-aware contract against production, prints the live HTTPS URL, and stops dev only after every gate passes. On failure it leaves dev intact for another iteration. Use app dev receipts or app dev evidence to inspect exact-revision evidence after cleanup.
Session lifecycle
Sessions expire after 24 hours. The worker removes expired schemas, frontend revisions, and Function links in bounded batches. Stop a finished session earlier to release those resources immediately.
Current limitations are explicit: there is no production-data clone, synthetic user session, Storage sandbox, Realtime sandbox, or automatic Function boot. These are unavailable capabilities, not silent fallbacks to production.