Rule Cascade
Reference

Changelog

All notable changes are recorded here. The format follows Keep a Changelog and the project uses Semantic Versioning.

All notable changes are recorded here. The format follows Keep a Changelog and the project uses Semantic Versioning.

[Unreleased]

Changes go into the 1.0.0-alpha.2 section below until that version is tagged.

[1.0.0-alpha.2] - 2026-10-03 (in progress)

The project is now called Rule Cascade. A ruleset is compiled once into a JSON bundle and evaluated identically by Python, TypeScript, Java and Go, by a single-file command for Linux, macOS and Windows, and by a WebAssembly module. The conformance suite has 1899 cases.

Breaking

  • Name. Rule Contract became Rule Cascade. The document key is ruleCascade (was ruleContract), the schema is spec/v1/rule-cascade.schema.json, the OpenAPI extension is x-rule-cascade, the npm packages are @yarlisaisolutions/rule-cascade and @yarlisaisolutions/rule-cascade-server, the Maven artifact is rule-cascade-core and the Java package is io.github.yarlisaisolutions.rulecascade.
  • Specification: checksums. The resolved ruleset has the new members types, functions and operators and the renamed key, so the checksum of every ruleset changes.
  • Specification: findings. finding.component is replaced by finding.location, an object with the page, screen, section and component the rule's target names.
  • Specification: patterns. matches accepts only the portable subset of section 4.4. \s and \b, which alpha.1 allowed, are rejected; . matches line breaks; $ is the very end of the string; counts, nested counts multiplied and the pattern length are limited to 1000.
  • Specification: numbers. A JSON number is the IEEE 754 double nearest to what was written, in every runtime. Every number leaving the engine is rounded to 15 significant digits, whether it was computed or passed through. A number beyond the range of a double is refused on the way in and is an evaluation error on the way out.
  • Specification: strictness. An object that is not exactly {var}, {op, args} or {fn, args} is not an expression; an unknown root is null; list indexes are runs of ASCII digits. Dates are written with ASCII digits and nothing after the value, offsets go up to 23:59, and the UTC date lies in the years 0001 to 9999. lower changes only the ASCII letters.
  • Specification: evaluation. A state rule applies all of its effects or none. An evaluation request is shape-checked and refused before anything is evaluated.
  • Specification: YAML. A ruleset in YAML is read by the YAML 1.2 core schema: unquoted no, on and 2026-10-03 are strings. Scalars that YAML 1.1 and 1.2 read differently must be quoted.
  • Tools. rulecheck export and rulecheck corpus are replaced by rulecheck sync. tools/rulecheck.py is no longer the reference implementation; it is the command-line front end of packages/python.
  • Rule server: a token is required (breaking for deployers). The server refuses to start without RULE_SERVER_TOKEN: it logs a fatal line and exits 1 before loading any rules. Set RULE_SERVER_ALLOW_OPEN=1 to serve every endpoint without a token, on a private network only. Embedded, createRuleServer throws unless it is given token or allowOpen: true. The Kubernetes deployment no longer marks the token Secret optional.
  • Specification: patterns. A group that repeats must not contain an unbounded quantifier ((a+)+, (a*){2,}): such a pattern backtracks exponentially and is refused with PATTERN_NOT_PORTABLE. matches refuses a subject longer than 10000 code points with an evaluation error.
  • Specification: nesting depth. An evaluation request whose data, original, actor or ctx is nested more than 64 deep (also every root of env in the expression command) is refused with BAD_REQUEST before anything is evaluated, and a rule expression or function body nested more than 128 deep fails the load with EXPRESSION_TOO_DEEP. Depth is checked before recursing, so no runtime can overflow its stack.
  • TypeScript compiler. Schema validation stops at the first problem (Ajv allErrors: false): reporting every problem took time exponential in expression depth. compile now reports the first schema problem only.

Added

  • Specification: locale fallback. A message is read from the catalog of the requested locale, then from each shorter prefix of the tag (fr-CA, fr), then from the default locale.
  • Specification: one manifest on its own. Every runtime reads a single manifest (RuleSet.from_manifest, fromManifest, FromManifest), which is how a native front end receives rules. In the engine protocol load accepts a manifest, reports channels and missingOperators, and a channel the ruleset lacks is CHANNEL_UNAVAILABLE.
  • Runtimes: start-up check for custom operators. Each runtime reports the custom operators a ruleset needs that the host has not registered.
  • TypeScript: the manifest client keeps working through an outage of the manifest endpoint by returning the cached manifest and reporting that it is stale.
  • Catalog: a server rule protects the credit limit. The read-only state guides the UI; the new validation rule is what stops the change.
  • Specification: targets. A rule may target a page, screen and section in addition to a component and fields, or a semantic data type. Types are declared under types and bound to fields in entities.<Name>.fieldTypes; a rule on a type runs once per bound field with value and field in scope. A request may carry a view that narrows the evaluation to one place.
  • Specification: functions and custom operators. functions declares reusable expressions, called with {fn, args}, with lexical scope and no recursion. operators declares x-* operators that the host supplies; an operator that is missing or fails makes the rule fail closed.
  • Specification: operators. Fifteen core operators: between, mod, abs, min, max, round, upper, trim, concat, substring, text, map, filter, typeOf and yearsBetween. The core profile now has 45.
  • Specification: bundles. A bundle holds the server and the client manifest of a compiled ruleset (section 7). Manifests gained fieldTypes, functions and operators. There are two conformance levels: evaluator and compiler (section 11).
  • Specification: engine protocol. JSON Lines on standard input and output with the commands version, load, manifest, evaluate, expression and compile (section 13).
  • Specification: authoring in YAML (section 12) and the load codes TYPE_UNKNOWN, SCOPE_INVALID, FUNCTION_UNKNOWN, FUNCTION_ARITY, FUNCTION_RECURSIVE, FUNCTION_REDEFINED, OPERATOR_UNDECLARED, FIELD_TYPE_REBOUND, BUNDLE_UNSUPPORTED and BUNDLE_INVALID. The recommended scope levels gained project and module.
  • Evaluation API. GET /rulesets/{rulesetId}/bundle; view in the request and location in a finding; the Bundle and View schemas. The API description is version 1.1.0.
  • Conformance. 499 expression cases (were 147), 191 load-error cases (84), five fixtures with 66 golden tests (two with 17), a 1000-case evaluation corpus (300), the five published bundles, and 88 engine-protocol cases. Two rulesets exist only for the suite: conflicts, ordering and failing closed. Three conformance operators (x-test-reverse, x-test-sum, x-luhn) test the custom-operator mechanism.
  • Python. packages/python, the rule_cascade package: the reference implementation as an installable library, with load, RuleSet.from_bundle, evaluate, evaluate_expression and the engine protocol (python -m rule_cascade engine).
  • TypeScript. Bundles (RuleSet.fromBundle, bundle()), custom operators, view and finding.location, requestProblem and RequestError, missingOperators, patternProblem, jsonFinite, the Engine class and the rule-cascade-node engine command.
  • Java. Bundles (RuleSet.fromBundle, bundle()), CustomOperator, withOperators and requiredOperators, View, and the engine protocol (Engine, Main engine). The schema validator interprets the embedded schema file instead of mirroring it.
  • Go. A new runtime, package rulecascade, at both conformance levels, with no dependencies outside the standard library.
  • Command. rule-cascade, built from the Go runtime: version, check, compile, manifest, evaluate and engine. packages/go/scripts/build-all.sh builds static binaries for Linux, macOS and Windows on amd64 and arm64, with checksums.
  • WebAssembly. The same command as a WASI preview 1 module, rule-cascade.wasm, and packages/go/wasi/run.mjs to run it under Node.js.
  • Rule server. Serves bundles (GET /rulesets/{id}/bundle, behind the token) and loads precompiled *.bundle.json files next to source rulesets. createRuleServer takes custom operators and reports the missing ones; the command logs them at start-up and after a reload.
  • Tools. rulecheck compile writes a bundle. rulecheck conformance --engine "<command>" certifies any engine over the engine protocol. rulecheck sync [--check] regenerates the schema copies, fixtures, bundles, expectations, the evaluation corpus and the example client manifest. rulecheck check reports YAML_NOT_PORTABLE and NUMBER_NOT_PORTABLE.
  • Tools: JSON Logic. rulecheck jsonlogic import|export converts expressions in both directions and refuses what it cannot convert faithfully (docs/json-logic.md).
  • Tools: OpenAPI. rulecheck derive turns the constraints of an OpenAPI component schema into a baseline ruleset with stable finding codes (docs/openapi.md).
  • Examples. A rule catalog by data type and severity (examples/catalog, 34 rules, 25 golden tests); two derived rulesets with their tests (examples/derived); the same two evaluations from Python, Node.js, Ruby, PHP, Java, shell, Rust, C# and PowerShell over the engine protocol, and in process with the WebAssembly module from Node.js and Python (examples/engine-clients). The Spring Boot example can start from a bundle and registers a custom operator.
  • Bindings. The rule-draft response format lists the 45 core operators, function calls, places and types; the evaluate_rules tool accepts a view. bindings/README.md explains both files.
  • CI. Every runtime is built and certified on Linux, Windows and macOS, in process and over the engine protocol. A job cross-compiles the command, certifies the WebAssembly module, runs the engine clients and uploads the binaries. The tools have unit tests (tools/tests).
  • Documentation. docs/authoring-guidelines.md, ADRs 0005 to 0009, packages/python/README.md.
  • Examples: services, batch and agents. examples/backend-node (node:http) and examples/backend-go (net/http) enforce the payments contract the way the Spring Boot example does: evaluate, refuse with 422 problem details, persist, run commands once per idempotency key, and serve the client manifest with an ETag. examples/batch streams a CSV or JSON Lines file through any engine over the engine protocol, one decision per line. examples/agent-tools runs the tools of bindings/llm-tools.json against the rule server, with the actor taken from the session; its README maps the same tools onto MCP.
  • Tests. The React hook and the manifest client of the TypeScript runtime have their own tests. The container smoke test (packages/server/test/smoke-image.sh) also checks that the image fails closed without a token, that a broken reload is refused while the previous rules keep serving, and that SIGTERM drains.
  • Cross-runtime version check. tools/check_versions.py (run by make contract and the CI contract job) fails unless every runtime declares the same version: the TypeScript and server package.json, the Java pom.xml, Go's version.go, and Python's pyproject.toml and __init__.py (PEP 440 1.0.0a2 is read as 1.0.0-alpha.2).
  • CI. One job per new example on Linux, and a job that runs the Go runtime and the Go example on Go 1.22, the minimum go.mod declares, with GOTOOLCHAIN=local so no newer toolchain is used.

Changed

  • Specification. Canonical numbers are exact. abs keeps 34 digits. Members of a protocol request are checked before a ruleset is looked up; optional members may be null.
  • TypeScript, Java. Both runtimes implement the evaluator and the compiler level and are at version 1.0.0-alpha.2.
  • Rule server. POST /evaluations checks the body with the shape check every runtime applies and names the problem in its 400 answer. A ruleset that came from a bundle is listed with its id, version and checksum only. If-None-Match is compared as RFC 9110 says: a list of entity tags, weak or strong, or *. A path with malformed percent-encoding is 400, not 500. A body over the limit is 413 without being read to the end.
  • Tools. tools/lint_specs.py also checks that every pattern in the schema is portable, that the operator list in bindings/ matches the schema, and that all generated files are current.
  • Makefile. make verify also runs the Python package tests, the Go runtime, the release build, every engine over the protocol and the engine clients.
  • Documentation. docs/architecture.md, docs/naming-conventions.md, ADRs 0001 to 0003, CONTRIBUTING.md, SECURITY.md and the deployment notes describe the current design. The README has a table of supported versions (declared minimum, tested in CI, highest observed; no declared maximum) and says what is not verified: the Kubernetes manifests have never been applied, the WebAssembly module has not run in a browser, and the x-rule-cascade binding is checked at lint time only. The claim that the runtimes agree beyond the suite now says which part can be repeated from the repository.

[1.0.0-alpha.1]

Added

  • Specification 1.0 (draft): ruleset schema, normative semantics, evaluation API.
  • Conformance suite: expression cases, load-error cases, fixture checksums and client manifests, golden tests, and a 300-case evaluation corpus.
  • Reference implementation and linter in Python (tools/rulecheck.py).
  • TypeScript runtime for browsers and Node.js, with a React hook and a manifest client.
  • Java runtime with no dependencies.
  • Rule server: stateless HTTP service with health probes, graceful shutdown and safe reload.
  • Examples: organisation and feature rulesets, an OpenAPI binding, a React form, a Spring Boot API.
  • Deployment: Dockerfile and Kubernetes manifests for the rule server.
  • Bindings for OpenAI function tools and structured rule drafting.

Rendered from CHANGELOG.md in the repository. Edit it there.

On this page