Rule Cascade
Playbooks

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.

OptionChoose it whenCost
Rule server (HTTP)The caller is a service that may make a network call, and you can run one more serviceA network hop per evaluation; the server must be protected with a token
Command rule-cascade engineThe program may start a child processOne binary per OS and CPU to ship; about 0.14 ms per evaluation with the process kept alive
WebAssembly rule-cascade.wasmYou cannot ship or start a native binary (sandbox, plug-in host) and have a WASI preview 1 runtimeSlower 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.js

In 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-cascade

Start 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.sh

Get 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 not true, error.code and error.message say why.
  • Inherit the engine's standard error.
  • When the engine stops, start a new one and load again: 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.

Source: site/content/docs/playbooks/other-languages.mdx

On this page