App manifest
schemaVersion: 3
appId: 6f9619ff-8b86-4e6e-a62a-889950f42d3e
frontend:
directory: frontend
spa: true
runtime:
sdk:
version: 2.2.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
- name: receive-slack-message
entrypoint: functions/receive-slack-message/index.ts
access: system
- name: receive-telegram-message
entrypoint: functions/receive-telegram-message/index.ts
access: system
- name: receive-asana-events
entrypoint: functions/receive-asana-events/index.ts
access: system
- name: process-report
entrypoint: functions/process-report/index.ts
access: system
queues:
- name: reports
function: process-report
concurrency: 2
maxAttempts: 3
retryDelaySeconds: 5
retryBackoff: true
timeoutSeconds: 120
email:
addresses:
- name: support
displayName: Example Support
function: receive-support
notifications:
webPush: true
icon: /icons/notification.png
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
integrations:
calendar:
provider: google-calendar
account: calling_user
cardinality: one
capabilities:
- calendar.events.read
bank:
provider: gocardless-bank-account-data
account: calling_user
cardinality: many
capabilities:
- bank.accounts.read
- bank.balances.read
- bank.transactions.read
team_chat:
provider: slack
account: app
cardinality: many
capabilities:
- slack.messages.send
- slack.messages.receive
events:
message:
function: receive-slack-message
support_chat:
provider: telegram
account: app
cardinality: many
capabilities:
- telegram.messages.send
- telegram.messages.receive
events:
message:
function: receive-telegram-message
work:
provider: asana
account: app
cardinality: one
capabilities:
- asana.tasks.read
- asana.tasks.update
- asana.assignees.read
- asana.assignees.write
- asana.sections.read
- asana.sections.move_tasks
- asana.custom_fields.read
- asana.custom_field_values.write
- asana.attachments.read
- asana.attachments.write
- asana.events.receive
events:
function: receive-asana-events
observability:
metrics:
- name: items_created
type: counter
unit: items
dimensions:
actor_type:
values: [member, admin]
- name: overdue_items
type: gauge
unit: items
alertRules:
- id: overdue-items
name: Overdue items detected
metric: overdue_items
aggregation: latest
operator: gte
threshold: 1
window: 5m
minimumSamples: 1
severity: warning
enabled: trueFields
| Field | Rules |
|---|---|
schemaVersion | Use 3 for new manifests; schema 2 remains readable for immutable legacy artifacts |
appId | Assigned UUID; must match the deploy target |
frontend.directory | Relative directory containing index.html |
frontend.spa | Enables index.html fallback |
runtime.sdk.version | Exact installed SDK version pinned to this deployment |
data.search | Optional named PostgreSQL searches; nonempty declarations require SDK 2.4.0 |
files.access | Optional managed Files capability; user (default) or app |
files.maxUploadBytes | 1 byte through 100 MiB; defaults to 50 MiB |
migrations | Ordered, append-only IDs and relative SQL files |
functions | Unique logical names, entrypoints, and user/public/system access |
routes | Schema 3 only; up to 100 named asset aliases or Function routes; see App routes |
queues | Up to 50 unique background queues targeting declared system Functions |
cron | Unique names, valid UTC schedules, declared system Function targets |
email.addresses | Up to 25 unique aliases; optional inbound function must be a declared system Function |
notifications.webPush | Optional Web Push capability; when present its value must be true |
notifications.icon | Optional same-origin default notification icon path; omitted uses the OpenCloud logo |
health.path | Same-origin path beginning with / |
secrets | Map of uppercase names to generated, required, or optional |
integrations | Named provider slots with an account mode, one/many cardinality, and bounded capabilities |
integrations.*.events | Optional Slack/Telegram event handlers; message.function must name a system Function |
integrations | Named provider slots with an account mode, one/many cardinality, bounded capabilities, and optional provider events |
observability.metrics | Optional deployment-pinned counter/gauge catalog; at most 20 metrics |
observability.alertRules | Optional deployment-pinned fixed-threshold rules; at most 20 rules, each referencing a declared metric |
Schema 3 has no top-level release version. The publisher assigns a bounded app-name-YYYYMMDD-N release name while holding the app deployment lock and stores it separately on the deployment row. runtime.sdk.version is unrelated: it remains in the manifest because the exact SDK is part of the immutable artifact contract.
Routes are optional. Each has a unique id, an absolute path, and exactly one target: asset relative to frontend.directory, or function naming a declared user/public Function. Assets default to methods: [GET, HEAD]; Function methods must be explicit. Access defaults to inherit; only an exact literal asset alias may set access: public, including in a private app. Function routes require SDK 2.3.0. See the routing guide for optional parameters, wildcards, precedence, query input, and reserved paths. The pinned public CLI 3.10.3 validates and bundles routed schema-3 manifests. Custom domains are app settings, not manifest fields.
Schema-2 artifacts keep their embedded version so their verified bytes never change, but OpenCloud does not use that legacy field to name a newly submitted ordinary-app deployment. This lets an unchanged schema-2 source manifest be part of a valid update while the publisher still allocates a fresh database version.
An email alias without function is sending-only. Adding function makes it receive-capable, and OpenCloud uses the same allocated address for outbound From and inbound delivery. Function code selects the alias, never an arbitrary email address or provider credential.
notifications.webPush requires runtime.sdk.version: 2.1.0 or later. Apps pinned to 2.0.0 retain their original runtime-config shape and cannot declare Web Push. notifications.icon is the app-wide default. A same-origin icon supplied to an individual notifications.send call overrides it; if neither is supplied, the managed OpenCloud logo is used. Web app manifest icons remain a separate browser-installation concern. iOS and iPadOS currently ignore notification payload icon overrides and show that installed app icon; an apple-touch-icon takes precedence over manifest-declared icons when both are present.
Queue names use lowercase kebab-case. concurrency is 1–20 and defaults to 1; maxAttempts is 1–10 and defaults to 3; retryDelaySeconds is 1–3600 and defaults to 5; retryBackoff defaults to true; and timeoutSeconds is 1–900 and defaults to 900. maxAttempts includes the initial delivery. Every queue target must be a declared Function with access: system.
Custom metric names use lowercase snake_case. A metric can declare at most three dimensions, and each dimension must list at most 20 allowed values. Alert-rule IDs use lowercase kebab-case. Counter rules support sum and rate; gauge rules support latest, min, max, and avg. Rules are validated with the artifact and become active atomically with the deployment, before the new release can receive traffic. Operational rules created through the API or CLI remain separate. A same-ID operational rule overrides the active manifest definition until that override is deleted; list responses expose which origin is effective. Arbitrary strings, IDs, emails, URLs, and object keys are not valid dimensions. Counters support non-negative increments; gauges record a current value.
Do not write migration checksums. The CLI computes SHA-256 values and emits the canonical opencloud.json inside the archive.
Use an exact SDK version, never latest or a range. Publishing a newer SDK does not change an existing deployment. Frontends import the stable /_opencloud/sdk.js module; they do not derive a versioned path from the manifest.
Schema 2 intentionally rejects runtime.javascriptSdk, storage, and Function verifyJwt, and requiredSecrets. Replace them with runtime.sdk, files, Function access, and the declarative secrets map; do not keep both old and new fields. OpenCloud provisions generated values automatically, injects isolated synthetic values in development, blocks production when a required owner value is missing, and allows optional values to remain absent.
Only manifest-reachable files deploy. Reports, briefs, verifier specifications, and local tests remain outside the artifact.
Google Workspace providers are google-calendar, google-drive, google-sheets, google-docs, and google-slides. Slot names use lowercase snake_case. account: app resolves an owner-configured binding; account: calling_user resolves a separate binding for the signed-in caller. cardinality: one replaces the prior binding, while many permits multiple bindings and requires the Function to select an opaque binding ID.
Provider-scoped capabilities are calendar.events.read and calendar.events.create; drive.files.read and drive.files.write; sheets.spreadsheets.read and sheets.spreadsheets.write; docs.documents.read and docs.documents.write; and slides.presentations.read and slides.presentations.write. Calendar binds an exact calendar. The other providers bind a reusable Google account and accept bounded resource IDs through their typed Function clients.
Read-only Google reporting providers are google-analytics with analytics.reports.read, google-search-console with search.performance.read, and google-ads with ads.reporting.read. They require runtime SDK 2.2.0 or later, must use account: app, and bind one exact GA4 property, verified Search Console site, or directly accessible non-manager Ads customer. Functions use integrations.use(slot).call(operation, input) with the bounded analytics.reports.run, searchConsole.performance.query, or googleAds.campaigns.report operation; raw provider requests, GAQL, tokens, and unbound resource IDs are not part of the contract.
The Asana provider is asana. Every Asana binding selects one exact project. Its capabilities are asana.tasks.read, asana.tasks.create, asana.tasks.update, asana.assignees.read, asana.assignees.write, asana.sections.read, asana.sections.move_tasks, asana.custom_fields.read, asana.custom_field_values.write, asana.attachments.read, asana.attachments.write, asana.stories.read, asana.comments.write, and asana.events.receive. Functions use only the typed integrations.asana(slot) client. asana.tasks.create, asana.tasks.update, asana.assignees.write, asana.sections.move_tasks, and asana.custom_field_values.write also require asana.tasks.read because their methods return normalized current task state.
An Asana events.function must name a declared system Function. It requires asana.events.receive, asana.tasks.read, and account: app. Conversely, asana.events.receive requires events.function. Other providers currently reject events. Asana sections are project groupings (list headings or board columns), not independently connected resources.
The HubSpot CRM provider is hubspot-crm and must use account: app. Available capabilities are crm.contacts.read, crm.contacts.write, crm.companies.read, crm.companies.write, crm.deals.read, crm.deals.write, crm.owners.read, crm.pipelines.read, crm.notes.write, and crm.associations.write. Association writes must also declare the write capability for each source and target record type. Functions use integrations.crm(slot) for bounded contact, company, deal, owner, pipeline, note, and default-association actions. HubSpot slots do not expose deletes, raw provider requests, provider tokens, inbound events, or account: calling_user.
The open-banking provider is gocardless-bank-account-data. Its capabilities are bank.accounts.read, bank.balances.read, and bank.transactions.read. It binds exact accounts chosen through human, bank-hosted consent and exposes only bounded typed methods through integrations.bankAccountData(slot).
The technical provider behind Wise transactions is wise-balance-webhook, with the payments.received.reconcile capability. It must use account: app and binds exactly one owner-created Wise Account deposit events webhook. Functions use only integrations.receivedPayments(slot).bindings/reconcile. No Wise credential, raw transfer ID, or raw transaction feed enters an app.
The Slack provider is slack, with slack.messages.send and slack.messages.receive. It must use account: app. Receiving requires events.message.function, and that target must be a declared system Function. A human connects the reusable workspace, invites @OpenCloud, and binds exact channels in App Integrations. The per-binding mention or all_messages trigger mode is a human choice and does not appear in the manifest. Functions send through integrations.slack(slot).sendMessage; bot tokens, raw connection IDs, and arbitrary channel selection never enter the app.
The Telegram provider is telegram, with telegram.messages.send and telegram.messages.receive. It must use account: app. Receiving requires events.message.function, and that target must be a declared system Function. The app owner pairs an exact private chat or group with a one-time link, selects an owned paired chat in App Integrations, and chooses the per-binding directed or all_messages trigger mode. cardinality: one selects one chat at a time; many can bind several. Private chats do not require bot administration. Directed groups keep the bot as a member; all-message groups grant the bot minimal administrator access in that exact group while privacy mode stays enabled. Functions send through integrations.telegram(slot).sendMessage; bot tokens, raw chat IDs, and arbitrary chat selection never enter the app.
PostgreSQL search declarations
The unpublished SDK 2.4/platform candidate supports named Function searches:
runtime:
sdk:
version: 2.4.0
data:
search:
- name: document-chunks
table: document_chunks
select: [id, document_id, content]
filterColumns: [document_id]
fullText:
column: search_text
language: english
vector:
column: embedding
dimensions: 128
metric: cosine
model: potion-128-v1Declare at most 16 unique names and at least one of fullText or vector for each. SQL identifiers are unqualified lowercase identifiers of at most 63 bytes. select has at most 16 unique columns and must include id; filterColumns defaults to [] and allows at most eight unique scalar equality columns. Full-text columns must already be tsvector; language is english or simple. Vector columns must already be vector(n), with declared dimensions 1–2000, metric cosine and a nonempty model identity of at most 128 characters.
App migrations own columns, embeddings and indexes. Provisioning checks the declared base table, supported column types, primary key and forced RLS before activation. Changing a model requires re-embedding stored content; the model label checks query compatibility but cannot attest to stored vector provenance. Absent or empty data.search preserves older SDK compatibility. See Migration SQL and Function search for queries and ranking.
