Contributing
The conformance suite is the specification. A change to behaviour changes the specification, the reference implementation, the conformance cases and every runtime, in one pull request.
The one rule
The conformance suite is the specification. A change to behaviour changes the specification, the reference implementation, the conformance cases and every runtime, in one pull request. Where the prose and the suite disagree, the suite wins and the prose is a bug.
Prerequisites
| For | You need |
|---|---|
| Rulesets, specifications, reference implementation, tools | Python 3.10+, pip install -r tools/requirements.txt |
| TypeScript runtime, rule server, React, Node.js and agent-tool examples | Node.js 20+, npm ci at the repository root |
| Java runtime | JDK 17+; Maven 3.9+ only for the packaged build and the Spring Boot example |
Go runtime, the rule-cascade command, the WebAssembly module, the Go example | Go 1.22+. CI runs 1.22 with GOTOOLCHAIN=local to prove the minimum, and 1.24 |
| Engine client examples (optional) | Whichever of Ruby, PHP, Rust, jq, the .NET SDK and PowerShell you have; pip install wasmtime for the Python WebAssembly client. Missing ones are skipped |
Verifying a change
make verifyruns what CI runs, except the Maven builds, the React example build, the Docker image and the other operating systems. It needs all four toolchains. Each part is a target of its own:
| Target | What it does |
|---|---|
make contract | rulecheck check on every ruleset in examples/ and conformance/sources; tools/lint_specs.py (schema, OpenAPI descriptions, operator list in bindings/, generated files current); the unit tests of the tools |
make conformance | The conformance suite on the reference implementation |
make python | The tests of packages/python: the suite through the engine protocol, and generated files current |
make typescript | npm ci, build, type check, the suite in process, the tests of the runtime and of the rule server |
make java | Compiles with javac, all warnings as errors, and runs the suite in process. No Maven, no network |
make go | go vet, gofmt, the suite in process, and the native command in packages/go/dist |
make go-release | The command for Linux, macOS and Windows on amd64 and arm64, the WebAssembly module, and SHA256SUMS |
make engines | The suite over the engine protocol against the TypeScript, Java, Go, WebAssembly and Python engines, with the one driver. Needs typescript, java, go and go-release first |
make engine-clients | Runs every example in examples/engine-clients whose language is installed and compares its output |
Not part of verify:
| Target | What it does |
|---|---|
make java-maven | mvn verify for the Java runtime, as CI builds it |
make example-frontend | Builds the React example |
make sync | Regenerates everything derived from the rulesets and the schema (see below) |
make clean | Removes build output |
The examples in examples/backend-node, examples/backend-go, examples/batch and
examples/agent-tools have their own tests, which CI runs in one job each:
npm run build && npm run test:examples # Node.js service and agent tools
(cd examples/backend-go && go test ./...) # Go service
python -m unittest discover -s examples/batch -v # batch recipe, on every engine that is built
(cd packages/go && GOTOOLCHAIN=go1.22.12 go test ./...) # the Go runtime on its declared minimumA passing suite ends with conformance: 1899 cases, 0 failure(s), for every engine.
Changing the specification
Work in this order. Each step has something to run before the next.
- Specification and schema. Edit
spec/v1/SPECIFICATION.md. For a change to the shape of a ruleset, editspec/v1/rule-cascade.schema.json, and only that copy. For a change to the wire API, editspec/v1/rule-evaluation.openapi.yaml. - Reference implementation. Implement it in
packages/python/src/rule_cascade: expressions inexpressions.py, loading inruleset.py, evaluation inevaluate.py, the protocol inengine.py. - Conformance cases. Add hand-written cases that pin the behaviour down, including the errors:
conformance/expressions.json,load-errors.json,protocol.json. For behaviour that needs a whole ruleset, add golden tests to an example ruleset or to a ruleset inconformance/sources. - Regenerate.
python tools/rulecheck.py synccopies the schema into the four packages and regenerates fixtures, bundles, expectations and the evaluation corpus. Read the diff: an unexpected change there is a behaviour change. Thenmake contract conformance python. - Every runtime. Port the change to
packages/typescript,packages/javaandpackages/goand run each suite in process:make typescript java go. - The driver.
make go-release enginescertifies every engine, the WebAssembly module included, over the engine protocol. - Everything that describes it.
CHANGELOG.md; the package READMEs;docs/; for a new operator also theopenum inbindings/rule-draft.response-format.json, whichtools/lint_specs.pycompares with the schema. A significant design choice gets an ADR indocs/adr/.
Common changes:
| You want to | Do this |
|---|---|
| Change an example ruleset | Edit the YAML under examples/contracts or examples/catalog, then make sync |
| Add an operator | It belongs to a new profile: add it to the schema enum and to specification 4.3, implement it, add expression cases. Then steps 4 to 7 |
| Add a load-time check | A new code in specification section 5, cases in conformance/load-errors.json, the check in every compiler (LoadErrorCode.java in Java) |
| Change the schema | Edit spec/v1/rule-cascade.schema.json, then make sync. Every runtime reads its own copy; the Java validator fails at load if the schema starts to use a keyword it does not implement |
| Fix a bug in one runtime | Add the failing case to conformance/ first. If the reference is wrong too, fix it and run make sync |
| Change the engine protocol | Specification section 13, conformance/protocol.json, engine.py, then the engine of every runtime |
Adding a runtime in a new language
Start at the evaluator level (specification section 11). It needs a JSON parser, decimal arithmetic with 34 significant digits and a regular-expression engine, and no YAML and no JSON Schema.
-
Expressions (section 4): literals,
var, the 45 operators, function calls, custom operators, and the scanner for portable patterns (4.4).conformance/expressions.jsonhas 499 cases. -
Bundles and evaluation (sections 7 and 8): read a bundle, select rules, run the phases, produce findings, effects and commands. Check the shape of a request before evaluating.
-
The engine protocol (section 13): a program that reads one JSON request per line and writes one response per line, with
version,load,manifest,evaluateandexpression. AnswercompilewithUNSUPPORTEDand report the levelevaluator. Register the three conformance operators ofconformance/README.mdbehind an option. -
Certify it with the driver, and fix what it reports:
PYTHONPATH=packages/python/src python tools/rulecheck.py conformance \ --engine "<your command> engine --conformance-operators"The driver skips the compiler-level cases for an engine that does not report the level
compiler. An evaluator runs 1683 cases. Four of them arecompilecases inconformance/protocol.jsonthat the driver does not skip yet; an engine that answerscompilewithUNSUPPORTEDfails exactly those four until it reaches the compiler level. -
Make it part of the repository: a package under
packages/<language>with a README, a test that runs the suite in process, a Makefile target, a line inmake engines, a CI job on the three operating systems, and an entry indocs/languages.md.
The compiler level comes second: schema validation, inheritance, the load-time checks, the checksum and the manifests (sections 5 to 7), certified by the load-error cases and by producing exactly the published bundles. A language that only needs to enforce rules never needs it: bundles are compiled in CI by any existing compiler.
A language that needs neither a library nor a port uses the rule-cascade command or the
WebAssembly module; see examples/engine-clients.
Generated files
Do not edit these by hand. python tools/lint_specs.py and python tools/rulecheck.py sync --check
fail when one is stale, and .gitattributes marks them as generated so that diffs collapse them.
Review their diffs all the same.
| File | Generated from | By |
|---|---|---|
packages/python/src/rule_cascade/rule-cascade.schema.json, packages/typescript/src/rule-cascade.schema.json, packages/java/src/main/resources/.../rule-cascade.schema.json, packages/go/rule-cascade.schema.json | spec/v1/rule-cascade.schema.json | rulecheck sync |
conformance/fixtures/*.json | The YAML files in examples/contracts, examples/catalog and conformance/sources | rulecheck sync |
conformance/bundles/*.bundle.json | The fixtures listed under documents in conformance/rulesets.json, compiled by the reference | rulecheck sync |
expect and bundles in conformance/rulesets.json | The same. documents and schemaDocuments are maintained by hand | rulecheck sync |
conformance/evaluations.json | The seeded request generators in tools/rulecheck.py and the reference's answers | rulecheck sync |
examples/frontend-react/src/manifest.client.json | examples/contracts/payments-transfer.ruleset.yaml | rulecheck sync |
examples/derived/*.ruleset.yaml | The OpenAPI descriptions of the examples | rulecheck derive; the commands are in docs/openapi.md, and a unit test checks they are current |
tools/tests/fixtures/jsonlogic_expected.json | What json-logic-js 2.0.5 returns for the converter's test cases | The steps at the top of tools/tests/fixtures/jsonlogic_expected.mjs |
make sync is python tools/rulecheck.py sync. Build output (dist/, target/) is not committed.
Pull requests
- Branch:
<type>/<short-description>, for examplefeat/date-operators. - Commits: Conventional Commits. The types in use are
feat,fix,refactor,chore,cianddocs. The scope is the area:spec,conformance,python,typescript,java,go,server,tools,examples,docs. Mark a breaking change with!, as infeat(spec)!: .... - A change that spans runtimes is one pull request with one commit per area: the specification, reference and conformance cases first, then each runtime.
- One logical change per pull request. CI must be green.
- Anything a user can observe goes in
CHANGELOG.md, in the section of the version in progress. - Significant design choices get an ADR in
docs/adr/. - Use the checklist in
.github/pull_request_template.md.
Names for rulesets, rules, codes, files and packages are in docs/naming-conventions.md. How to write rulesets is in docs/authoring-guidelines.md.
Compatibility
| Area | Promise |
|---|---|
| Specification | Minor versions only add. Existing documents stay valid and keep their meaning |
| Operators | The meaning of an existing operator never changes; new ones arrive in a new profile |
| Bundle format | ruleCascadeBundle is versioned like the specification: a runtime rejects a major version it does not implement |
| Load error codes, protocol error codes and finding fields | Stable identifiers; never renamed within a major version |
| Runtime APIs | Semantic Versioning per package |
While the specification is a draft (1.0.0-alpha.N), these promises describe the intent for 1.0;
alpha versions may break, and the changelog says where.
Rendered from CONTRIBUTING.md in the repository. Edit it there.
Conformance suite
A runtime conforms to Rule Cascade 1.0 when it passes the cases in this directory for the level it claims. Every runtime in this repository runs exactly these files, on every operating system.
Changelog
All notable changes are recorded here. The format follows Keep a Changelog and the project uses Semantic Versioning.