> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ilyama.golain.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Node contract

> What a node may return from invoke, health, and capabilities, and what the platform does with each value.

The return code is not a label. It decides whether the platform retries, waits, gives up, or raises a security incident, and it decides what an operator is told about a device they may never see. Getting it wrong does not fail loudly. It produces a fleet that quietly reports the wrong thing.

This applies to every node: C-ABI nodes on macOS and Linux, and COM nodes on Windows share one code space and one set of JSON shapes.

## Return codes

`node_invoke` (Unix) or `Invoke` (Windows) returns a 32-bit code. One number space, three regions, and only one of them is yours.

| Range | Owner | You may return it |
| - | - | - |
| `-1` to `-99` | Omega shim | **No** |
| `-100` to `-199` | Omega dispatcher | **No** |
| `0` | success | Yes |
| `1` and above | **you** | Yes |

### 0, success

The operation did what it was asked. The platform marks the invoke **acked** and the deployment or schedule moves on.

Return `0` only when the work actually happened. A node that returns success for an op it does not implement is worse than one that fails, because the plan proceeds as though the device is in a state it is not.

### 1 and above, your failures

Everything positive is yours to define. The platform marks the invoke **failed** and shows your message against the device.

Give each value one meaning and keep it stable across versions. Operators build runbooks on these numbers.

### Negative, never

Omega synthesises these itself when a node never ran, and the platform reacts to each one differently. Returning one from your code makes an operator believe the platform failed when your code did.

| Code | Means | What the platform does |
| - | - | - |
| `-1` to `-5` | shim: buffer too small, panic, bad UTF-8, null pointer, serialise failure | terminal **failed** |
| `-100` | generic dispatch failure | terminal **failed** |
| `-101` | no node registered for that domain | **waits indefinitely** without consuming the retry budget, because a fleet policy dispatched before its node's OTA landed is normal |
| `-102` | node registered but disabled | terminal **failed** |
| `-103` | node ABI outside the supported range | terminal **failed** |
| `-104` | dispatcher timed the node out (60 s wall clock per invoke) | bounded retries with backoff, then gives up |
| `-105` | **security event**: signature verification of the node binary failed, so it was not loaded | distinct terminal state, reported as a compromise signal |
| `-106` | could not spawn the child process | bounded retries with backoff |

Two are actively dangerous to borrow. `-101` parks the invoke forever with nobody alerted. `-105` sends somebody hunting a compromise that did not happen. For "I am not ready yet", use a positive code and say so in the message.

```rust theme={null}
// Rust: keep your codes in one place and positive.
const OK: i32 = 0;
const ERR_UNKNOWN_OP: i32 = 1;
const ERR_BAD_PARAMS: i32 = 2;
const ERR_NOT_READY: i32 = 3;
```

On Windows there is a second axis: `Invoke` also returns an `HRESULT`. A failed operation returns `S_OK` with a non-zero `returnCode`. A failed `HRESULT` means the node itself is broken, and Omega reports a dispatch error and drops your message.

## What you are acting for

Every request JSON Omega hands you carries a reserved `_omega` object alongside the op's own parameters. It says what this invoke is for, which the parameters never do.

```json theme={null}
{
  "name": "world",
  "_omega": {
    "request_id": "ota:7f3a2b10-...:b2b2c3d4-...:1",
    "source": "ota",
    "deployment_id": "7f3a2b10-...",
    "step_id": "b2b2c3d4-...",
    "attempt": 1
  }
}
```

| Field | Present | What it is |
| - | - | - |
| `request_id` | always | The handle to quote in a log line or a support call. Unique per invoke. |
| `source` | always | `ota` (a plan step), `sched` (your own cadence firing), or `control` (somebody clicked **Run**). |
| `deployment_id` | `ota` only | The deployment this op is part of. |
| `step_id` | `ota` only | The plan step. |
| `attempt` | `ota` only | Which try this is. |

Log `request_id` on every operation that changes the machine. It is the only value that ties your event to the deployment or command that caused it, months later, without the Console in front of you.

`_omega` is reserved. Do not accept it as a parameter of your own, and if your `input_schema` sets `additionalProperties: false`, allow `_omega` explicitly or every invoke fails validation. Omega overwrites whatever a caller put there, so `source: "ota"` really does mean a signed plan step.

## Messages

The message is displayed in the Console beside the device name and stored with the invoke result. A person reads it, usually while something is wrong, about a machine they cannot reach.

| | |
| - | - |
| Say what happened | `disabled USB storage on 3 removable devices` |
| Say why it failed | `cannot disable USB storage: policy is enforced by Active Directory` |
| Say what would fix it | `service did not restart within 30s - check the system log for 'telemetry'` |

Avoid `Error 2` (the code has its own field), `OK` (says nothing about what changed), and stack traces (they render in a table cell). Always set a message, including on success. `collected 23 components` tells an operator the run was real.

The message is capped at 1 MiB. It is a status line, not a data channel. Bulk output belongs in your node's own database, replicated separately.

## Health

```json theme={null}
{ "status": "healthy", "detail": "hello node ready" }
```

| Status | Meaning | Effect |
| - | - | - |
| `healthy` | ready to serve every op | normal |
| `degraded` | serving, but something is wrong worth an operator's time | registered, surfaced as degraded |
| `unavailable` | cannot serve | **registration is refused** |

Omega polls health in a child process and reports a change on `node/report/health`. Answer honestly: returning `healthy` unconditionally removes the only signal anyone has that your node is broken. Answer cheaply: check a cached flag, not the network.

## Capabilities

```json theme={null}
{
  "abi_version": 1,
  "domain": "hello",
  "ops": [
    { "name": "greet", "idempotent": true,
      "input_schema": { "type": "object", "properties": { "name": { "type": "string" } } } },
    { "name": "ping",  "idempotent": true, "cadence_secs": 3600 }
  ]
}
```

| Field | |
| - | - |
| `abi_version` | must be an ABI Omega supports, currently `1`, or registration is refused |
| `domain` | your one domain, identical to what `node_domain` returns |
| `ops[].name` | user-facing. The Console renders each as a button. `unregister` is reserved: Omega synthesises it for every node, and a node declaring it is refused at install. |
| `ops[].idempotent` | a promise the platform relies on |
| `ops[].cadence_secs` | present makes the op scheduled; omit for on-demand |
| `ops[].input_schema` | optional JSON Schema for the params |

`idempotent` is load-bearing. It means running the op twice is indistinguishable from running it once, which is what makes a retry after a lost response safe. If your op appends to a log, charges something, or reboots a machine, it is not idempotent, and claiming otherwise gets it run twice.

`cadence_secs` is a request, not a guarantee. An operator can retune the schedule from the Console, and the platform's value wins. The device reports the schedule it is actually running in `node/report/registry`.

## Checklist

* Success is exactly `0`, and only when the work happened
* Every failure code is positive and has one stable meaning
* No negative code is ever returned
* No panic, exception, or crash can escape `invoke`
* Every result sets a message a person can act on
* Health is cheap, honest, and never lies about `healthy`
* `idempotent` is true only where a second run genuinely changes nothing
* No op is named `unregister`
* `_omega.request_id` is logged for every operation that changes the machine
* `_omega` is allowed by any strict `input_schema`, and never a parameter of your own


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.