Application execution
An application is a calimero:app component: a WASM component, built with
calimero-sdk, that defines a state machine: its data types and the methods
that change them. An execution (a run) is one call of one method. On a
cluster, only the app group’s leader runs calls; the other replicas apply
what the run produced (Replication).
The node runs components on calimero-app-engine-wasmtime through the host core
calimero-app-host (crates/node/src/node/component.rs,
crates/node/src/component.rs), preparing each bundle once and caching the
compiled artifact by its hash. A WASM core module (an SDK 0.12 app, which ran on
wasmer) is refused at upload (400 UNSUPPORTED_BUNDLE); an app of that kind
that a data directory still holds is listed and can be deleted, and anything
else on it answers 410 UNSUPPORTED_ENGINE.
One run
Section titled “One run”A run reads the app’s state as an ordered, immutable version (StateView);
its writes stay in the run. When it ends, they become the outcome’s diff,
which every replica applies. If the run trapped or the app returned an error,
the diff is empty and the entry is still logged with its status
(422 APP_ERROR with details.{code, fields}, 422 TRAP with
details.{kind, detail}).
What the host offers a cloud run
Section titled “What the host offers a cloud run”The calimero:app world (crates/app-wit/wit/calimero-app.wit):
| Interface | What it does on a cluster |
|---|---|
storage |
The app’s replicated state: get, put, delete, the commutative add, ordered range |
context |
The caller, the app’s id and owner, its organization, the caller’s role and teams there, the applied index, unique ids, and starting an action |
events |
Emit an event to an audience; a removed member stops receiving at once |
blobs, files |
The app’s blobs and sealed files |
search |
The app’s own full-text indexes, in views |
xcall |
Send a call into another app (outbox), or call or view one synchronously |
action |
Inside an action: its key, and views and calls of the app |
wasi:clocks, wasi:random |
The leader’s wall clock and randomness, read once: followers apply the result |
wasi:http |
Outbound HTTPS, from actions and as one recorded request of a call (below) |
Three storage spaces in one state
Section titled “Three storage spaces in one state”A component has one space: its own keys, which the node keeps as hidden rows of
the app’s group, sealed on disk. Per-caller data is rows keyed by
env::caller(), and writer sets are rows the app checks before it writes
(apps/kv-store-with-shared-storage); the host keeps no space of its own inside
an app’s keys. (SDK 0.12 apps had a private and a host space beside the shared
one; they are gone with that SDK.)
What the node checks after the run
Section titled “What the node checks after the run”Before a run’s outcome becomes an entry, the node applies these host-side rules:
- Read-only team members: a member whose row in the app’s team is read-only
reaches the app’s views only: a call to any other method is refused with
403before it runs, so no run burns the developer’s fuel uncounted. - Entry size: an entry over the log’s cap is logged trapped (
HostDenied), and arguments over 512 KiB are413 ARGS_TOO_LARGE. - Conflicts: a run built on a state another entry has since moved past, or on a bundle an upgrade replaced, commits as a no-op and runs again.
Outbound HTTPS
Section titled “Outbound HTTPS”A component sends HTTPS from an action, or as one recorded request of a call (both below). Either way the request leaves only when every gate holds:
- the operator turned outbound on and allows the host (
[outbound]in the node’s configuration); - the app’s bundle is a signed release whose manifest declares a URL prefix covering the request;
- that prefix was approved for the publisher by whoever the cluster’s governance gives outbound approvals;
- never from a view,
init, a migration, or a read-only member’s call; httpson port 443 to a name that resolves only to public addresses, within the size limits, no redirects.
Actions (component apps)
Section titled “Actions (component apps)”A calimero:app component calls out from an action (spec
app-runtime-v0.14 D12, S9): an #[app::action] method that runs outside the
log, on the app’s leader. A call can also make one recorded request itself
(below), never a view.
- Start (D17). A client calls the action’s method through
/intents, or a call or timer runsenv::start_action(method, &args). Either way an entry logs that it started, under a key (a.<term>.<seq>.<n>), and the answer is{ "actionId" }. - Run. The leader runs pending actions in a bounded pool, with a 10 s
budget and the starter as caller (D13). An action has no storage. It reads
with
ctx.view, writes withctx.call, and sends HTTP withctx.fetchoverwasi:http. Eachctx.callis its own log entry under a key derived from the action’s key, the method, the arguments’ hash and an occurrence count (D27), so a retried action’s calls replay. A method marked#[app::allow(action)]is reachable only from the app’s own actions. - Outbound (S9, R14). A request leaves only through the same gate as
above. The URL must sit under a prefix the signed release’s manifest
declares, the owner must have approved it, the operator’s hosts apply, and
there is one request per run.
{{secret:NAME}}is filled in by the node and redacted from the response. Bodies are bounded in-memory buffers, so a guest cannot block a node thread. - End (D22). Success, or an app error, a fault or out-of-fuel, ends the
action at once. A timeout is retried with backoff, 5 attempts in all. The
end is logged. A failure sends an
ActionFailed { actionId, method, reason }event to the starter and runs the app’sonActionFailedas the starter. A new leader resumes every action that has not ended, with the same key.
Outbound HTTP from component calls
Section titled “Outbound HTTP from component calls”A component’s call, timer or xcall delivery may send one request itself
(env::fetch; spec D76), for a check that belongs in the same request as the
write it guards, such as a captcha service verifying a Turnstile token:
- The same gate as actions. A URL the signed release declares under
outbound, the owner approved and the operator allows;{{secret:NAME}}is filled in by the node and redacted from the response. Views,initand migrations cannot send (HostDenied), nor can a read-only member’s call. - Once, recorded. The leader sends it while it runs the call, before
proposing. The call’s log entry records the exchange (
Outcome::http: the request as the app made it, with placeholders, and the response the gate returned; bodies cut to 64 KiB), whatever the call’s status. Followers apply the diff and never fetch, and a retry with the same idempotency key replays the recorded answer. A run the log never commits (a leader race, a timeout) recorded nothing and sends again when it re-runs. - Bounded. The node waits at most 1.5 s for the response, inside the
call’s 2 s budget; a timeout, a refusal or an unreachable host is an error
the app’s
fetchreturns. - Trust. The leader sees the response before it is logged. Attestation covers the code that made the request and its TLS connection, not the remote server’s answer: the same trust as an action’s request.
apps/v2-fixtures/captcha’s verifyTurnstile is the example; the kv
fixture’s setHumanTurnstile reaches it with a synchronous xcall, so the
token is checked and the form written in one request.
Determinism is not what converges
Section titled “Determinism is not what converges”Only the leader runs, so the clock, randomness, outbound responses and search answers cannot make replicas disagree. Runs are still bounded and transactional: resources are capped (Limits), and a trap or an app error leaves an empty diff.
Views and upgrades
Section titled “Views and upgrades”A view runs the same way against a replica’s applied state, with search
available, and its writes are discarded (Reads). An
upgrade swaps the bundle; a named migration is refused
MigrationsNotSupported (Upgrades).