Extending
The guest ABI
The contract is small on purpose. A rule is a pure function, and the interface is shaped so it cannot be anything else.
Three exports, zero imports
(memory (export "memory") 2)
(func (export "alloc") (param i32) (result i32))
(func (export "garboard_rule") (param i32 i32) (result i64))
| Export | Purpose |
|---|---|
memory |
Linear memory the host writes the input into. |
alloc |
Host asks the guest for a buffer of N bytes. |
garboard_rule |
The check: given a pointer and length, return a packed pointer and length for the output. |
Zero imports is the security property. A module that imports nothing cannot call the host — no network, no filesystem, no clock, no randomness. It is not sandboxed by policy; there is no door to lock, because the interface never had one.
The data
Input is a JSON document of parsed facts. Output is a JSON document of findings. Both cross the boundary as bytes in linear memory.
JSON rather than a binary format because a rule author debugging their own rule should be able to print the input and read it. The performance difference is irrelevant at the size of one pull request’s changed files.
What this shape guarantees
- Deterministic. Same facts in, same findings out. A custom rule cannot break the determinism the rest of the gate depends on, because it has no source of nondeterminism to reach.
- Bounded. A pure function over a bounded input has a bounded cost.
- Answerable. When a diligence reviewer asks what a customer’s rule can do, the answer is complete: read the facts it was handed, return JSON.
Practically
You will not write this by hand. TinyGo and Rust both emit it from ordinary code with a couple of annotations — see TinyGo. The ABI is here because a security reviewer will ask what the boundary is, and “it is a WebAssembly module” is not an answer.
