Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Concepts

This page lists what Compute Gateway does today. Where something is missing or does not work against the live services, it says “Not yet”.

Modules

A module is a WebAssembly file plus a capability list: the capabilities it needs. You publish both to Drive under your app id, as {name}@{version}. A version can’t be changed once published. Compute Gateway fetches the module from Drive on every call; you name it with a module_ref:

{"owner_uid": "<the person's id>", "app_id": "acme.hello", "name": "main", "version": "1.0.0"}

owner_uid is the person the module runs for, and the only caller Compute Gateway lets run it or read, tick or message its runs: any other token gets 403, even one holding the grant id or run id. app_id is both the Drive app the module is published under and the Granite app the grant must be for.

Drive stores the capability list as JSON:

{"module": "main", "capabilities": ["console.print", "time.now"]}

What the module must export:

CallExport
One-shot call (POST /v1/compute/invoke)main: () -> i32; the return value is exit
Durable run (POST /v1/compute/runs)run_init: () -> i32 and run_step: () -> i32
Nora interactive runA module compiled from a Nora Program.interactive

Modules that use any host function that reads or writes bytes must export their linear memory as memory. 64-bit memories are refused.

Grants

Before running a module, Compute Gateway asks Granite whether the grant_id you send:

  • is active and not expired,
  • covers the resource compute:<app_id>, the module’s own app,
  • holds every capability the module’s list declares, and
  • was granted to the module’s app_id.

If not, the call fails with 400. When Granite answers “no”, the message starts granite refused grant <id>: and gives Granite’s reason. Each durable-run tick and each Nora message checks the grant again, so revoking it stops the next step.

The grant’s scopes are the capabilities the module may use: Compute Gateway links only the host functions for those scopes. Its limits must include per_call_fuel_cap, a whole number; a grant without it is refused. In Granite, put these in the request’s requested_scopes and proposed_limits, for example:

{"requested_scopes": ["console.print", "time.now"], "proposed_limits": {"per_call_fuel_cap": 10000000}}

Granite’s docs cover filing the request and the person’s approval.

Fuel and metering

Fuel counts the WebAssembly work a module does. For each call (and each step of a durable run), Compute Gateway:

  1. asks Spend Gateway to reserve up to the grant’s per_call_fuel_cap units for the person and your app. If Spend Gateway refuses, the call fails with 400 and a message starting spend authorize;
  2. runs the module with the reserved fuel as its limit;
  3. charges the fuel the module used, one unit per unit of fuel.

A module that traps or runs out of fuel is still charged the fuel it used. A module that fails before it starts (it doesn’t compile, or imports a host function its grant doesn’t allow) is charged nothing.

Only the module’s own work is metered here. Any cost of the services a capability calls is that service’s.

Spend Gateway decides whether to reserve; see its docs. Spend Gateway takes no payments.

Calls and runs

There are three ways to run a module.

One-shot call. POST /v1/compute/invoke runs main once and returns its exit code, printed output and output bytes.

Durable run. POST /v1/compute/runs runs a module in steps, keeping its state between them. Each step reads the state and the result of the previous command, then emits one command:

CommandMeaning
{"cmd":"done"}Finished, exit code 0. Emitting nothing also means done.
{"cmd":"exit","code":3}Finished with that exit code.
{"cmd":"perform","cap":"drive.namespace.read","args":{"key":"notes"},"corr":"c1"}Use a capability. corr is yours, echoed back.

The command’s cap must be one the grant holds. Fast capabilities run within the same request and feed the next step. A slow one (model.gateway.call, pigeon.outbox.send) suspends the run: the response has "status":"suspended" and pending_cap.

You drive the run. Compute Gateway never ticks a run on its own: call POST /v1/compute/runs/{id}/tick to perform the command and run the next step, and repeat until status is done or failed. A tick that is retried after the command was performed does not perform it again. A suspended run you stop ticking stays suspended, with no time limit, and you can tick it again later. If a request is cut off partway through a run, reading the run shows it suspended on the command it had reached, fast or slow, and a tick carries on from there. A log append made just before the cut is not written twice. It holds none of the owner’s funds while it waits: each step settles its spend before the run suspends. If Spend Gateway is down when a step settles, the request answers 503 but keeps the step’s result, and the run takes no further step until a tick settles it; tick the run again, even if it reads done or failed. Every tick checks the module and the grant again: if the module is no longer in Drive, or the grant was revoked in the meantime, is no longer known, or no longer covers the command the run is waiting on, the tick fails with 400 and the run ends failed.

The next step reads the result as:

{"corr":"c1","ok":true,"result":{"bytes":"eyJtb2RlbCI6ImhhaWt1In0="}}

bytes is base64. On failure, ok is false and error holds a message.

When the run is done, its output is the final state. A failed run’s response, and reading it later, carries error, the reason it failed.

Nora interactive run. POST /v1/nora/interactive-runs starts a Nora interactive module, and POST /v1/nora/interactive-runs/{id}/messages sends it one message. Compute Gateway keeps the module’s state between messages; each response carries the rendered text and the module’s commands. Compute Gateway does not carry out those commands.

Capabilities

CapabilityHost functions (module host)What it does
(none needed)get_input_len, get_input, post_outputRead the call’s payload; append to output
(none needed, runs only)get_state_len, get_state, set_state, get_msg_len, get_msg, emit_cmdDurable-run state and commands
console.printconsole_print(i32), console_say(ptr, len)Print a number or a UTF-8 string to stdout
time.nowtime_now() -> i32The current Unix time in seconds
drive.namespace.readdrive_read_len, drive_readRead a file from the person’s space for your app in Drive
drive.namespace.append_logdrive_log(ptr, len)Append a line to that space’s log, the file log (one JSON {"message": ...} per line, with an "id" when a durable run wrote it, up to 256 KiB in all)
model.gateway.callmodel_callCall a model through Model Gateway
pigeon.outbox.sendpigeon_send(to, message)Post into the person’s Pigeon thread to (a thread id they linked; see API examples)
connector.call plus connector.call:<name>connector_callCall a connector the person registered; see API examples

Functions that return bytes copy at most out_max bytes into the module’s memory and return the number copied; call the _len function first to size the buffer. In a durable run, use the capability as a perform command instead of calling the host function.

Compute Gateway binds the person and your app to every capability call; the module cannot name a different person, app or Drive space.

Not yet:

  • drive.namespace.append_log has been tested against a local Drive, not yet the live one.
  • pigeon.outbox.send has been tested against a local Pigeon, not yet the live one.
  • model.gateway.call has been tested against a local Model Gateway, not yet the live one.

A module’s model.gateway.call runs under the person’s Model Gateway link, not the grant the call or run names. The person links once; see API examples. Until they do, every model call fails without reaching Model Gateway.