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

Decisions

A decision is a pure function: typed inputs in, typed outputs out, evaluated by a graph of nodes. It has no side effects, no IO, and — given the same inputs and the same stored model — always produces the same result. That determinism is what makes decisions safe to version, audit, replay and run identically in a browser (WASM), over HTTP, and inside a Lambda.

Shape

Decision
├── inputs        # typed, required/optional
├── outputs       # typed
├── nodes
│   ├── input
│   ├── decision_table
│   ├── expression
│   ├── switch
│   └── output
└── tests         # given/expect cases, run on every change

Node types

  • decision_table — a set of rules; each rule pairs a condition per input column with a value per output. hit_policy controls how multiple matching rules resolve ("first" — first match wins, evaluated top to bottom, is the common case). Cell conditions use a compact predicate syntax — ranges, comparators, membership, wildcards — documented in Expressions.
  • expression — computes one output from an expression over the decision’s inputs (and other nodes’ outputs).
  • switch — maps a value to one of several cases (when/then pairs) with a default fallback; simpler than a table when you’re branching on one value rather than combining several.
  • output — binds computed values to the decision’s declared outputs. Every decision ends in one.

Nodes can reference each other’s results by id, so a decision is a small DAG: a table can feed an expression, an expression can feed a switch, and so on, ending at the output node.

Tests

Every decision carries its own tests: given inputs and expected outputs, checked on every change. A project can additionally define test_suites — named groups of cases against a specific decision, useful for broader regression coverage than the inline per-decision tests. Both run through the same engine as production evaluation, so a green test is a real guarantee, not a mock.

{
  "name": "prime borrower is low risk",
  "given": { "credit_score": 780, "debt_to_income": 0.2 },
  "expect": { "risk_tier": "LOW" }
}

Storage and simulation

A decision is created/edited as an artifact (kind: "decision") scoped to a project — see Projects & artifacts. Two ways to run one without deploying anything:

  • Simulate a saved decision: POST /projects/{project}/decisions/{artifactId}/simulate with the input object as the body.
  • Simulate ad hoc: POST /engine/simulate with { decision, inputs } — useful while iterating on a model that isn’t saved yet.

Both paths call the exact same engine entry point a deployed workflow’s decision_task uses (ruleflow_engine::simulate), so simulation results are never an approximation of production behavior.

Versioning

A decision artifact is mutable while you iterate; a release freezes its current model into an immutable snapshot. Deployed workflows always run the decision model inlined at deploy time from the release that was deployed — never the live, possibly-since-edited artifact — so a running environment’s behavior never drifts out from under you.