Run rules from any other language
Ruby, PHP, Rust, C#, PowerShell, shell or anything else - through the rule server over HTTP, or the command or WebAssembly module over the engine protocol.
A language without a native library has three options. All of them return the same evaluation result as the libraries, because they are the libraries.
| Option | Choose it when | Cost |
|---|---|---|
| Rule server (HTTP) | The caller is a service that may make a network call, and you can run one more service | A network hop per evaluation; the server must be protected with a token |
Command rule-cascade engine | The program may start a child process | One binary per OS and CPU to ship; about 0.14 ms per evaluation with the process kept alive |
WebAssembly rule-cascade.wasm | You cannot ship or start a native binary (sandbox, plug-in host) and have a WASI preview 1 runtime | Slower to start; about 3 times slower per request than the command |
Custom operators cannot cross a process boundary in any of the three. A rule that needs one fails
closed with RULE-EVALUATION-ERROR. If your rulesets use custom operators, use a native library,
or embed the rule server with createRuleServer(store, { token, operators }).
Option A: the rule server
Run it with a token
npm ci && npm run build
RULE_SERVER_TOKEN=$(openssl rand -hex 32) RULES_DIR=examples/contracts node packages/server/dist/main.jsIn production, run it as in the Kubernetes playbook.
Call it from your backend
curl -s localhost:8080/evaluations -H "Authorization: Bearer $RULE_SERVER_TOKEN" \
-H 'Content-Type: application/json' -d '{
"ruleset": "acme.payments.transfer", "entity": "Transfer", "operation": "create",
"actor": {"id": "u-1", "roles": ["teller"]},
"data": {"type": "international", "amount": 500, "currency": "USD", "memo": "gift",
"beneficiary": {"name": "X", "country": "KP", "swiftCode": "ABCDKPPY"}}}'The response is the evaluation result with HTTP 200; act on decision. 400 is a malformed
body, 401 a missing token, 404 an unknown ruleset.
Keep the server away from browsers
The rule server evaluates whatever actor it is sent. Only your backend may call
POST /evaluations, with the actor it took from its own authentication. Browsers may fetch the
client manifest (GET /rulesets/{id}/manifest?channel=client), which needs no token.
Option B: the command or the WebAssembly module
Build the engine
cd packages/go
go build -o dist/rule-cascade ./cmd/rule-cascade
GOOS=wasip1 GOARCH=wasm go build -o dist/rule-cascade.wasm ./cmd/rule-cascadeStart one engine process and load the bundle once
Write one JSON request per line; read one response per line, in order. The load request carries
the whole bundle on one line.
BUNDLE=conformance/bundles/acme.payments.transfer.bundle.json
{
jq -c '{id: 0, command: "load", bundle: .}' "$BUNDLE"
echo '{"id":1,"command":"evaluate","ruleset":"acme.payments.transfer","request":{"entity":"Transfer","operation":"create","data":{"type":"domestic","amount":-5}}}'
} | packages/go/dist/rule-cascade engine{"id":0,"ok":true,"result":{"ruleset":"acme.payments.transfer","version":"1.0.0","checksum":"sha256:c192dd53..."}}
{"id":1,"ok":true,"result":{"ruleset":"acme.payments.transfer", ... "decision":"deny","findings":[...]}}Use a client for your language
examples/engine-clients has one for Python, Node.js, Ruby, PHP,
Java, shell, Rust, C# and PowerShell (process/) and two that run the module in process (wasm/).
Every one prints the same output, and CI runs those whose language is on the runner:
sh examples/engine-clients/run-all.shGet the client details right
- One line each way, in turn: write, flush, read. A client that writes many requests before reading blocks once the pipes are full.
- No limit on the line length; UTF-8 without a byte order mark.
- Check
ok; when it is nottrue,error.codeanderror.messagesay why. - Inherit the engine's standard error.
- When the engine stops, start a new one and
loadagain: evaluation is pure, resending is safe. - For parallel work, run a pool of engine processes, one request in flight per process.
Done when your program evaluates the two requests of the engine-client examples (a domestic
transfer is allow, a transfer to KP is deny with ORG-TRF-001) through the server or the
engine, and the response's checksum equals the bundle's.
Enforce in a backend API
Load the bundle at start-up, evaluate every state-changing operation on the server channel, answer 422 on deny, persist, then run commands once. Java, Node.js, Go and Python.
Batch processing
Evaluate thousands of records against one bundle, reproducibly - with a native library, or through one engine process from any language.