ADR 0010: A ruleset that needs a newer runtime fails at load, not on each request
Status: accepted
Status: accepted
Context
New operators arrive in a new minor version of the specification (section 10). A runtime used to
check only the major version of a document and the format version of a bundle, and an evaluator
trusts a bundle without running the load checks again. So a 1.0 runtime that received a bundle using
a 1.1 operator loaded it, and then failed closed with RULE-EVALUATION-ERROR on every request that
reached the rule. The first sign of the wrong deployment order was blocked users.
The first operators that needed a new version were isDate and isDateTime. Without them a rule
cannot tell a date that exists (2024-02-29) from one that does not (2023-02-29), so a
well-formed but impossible date reached daysBetween or yearsBetween and failed closed instead of
showing the user a message.
Decision
- The version a ruleset needs travels with it. A document that uses an operator added in 1.1
declares
ruleCascade: 1.1.0; a ruleset whose resolved version is 1.0 and that uses one does not load (OPERATOR_NOT_IN_VERSION). The resolved ruleset takes its parent'sruleCascadewhen the parent's minor version is later, so a 1.0 child of a 1.1 parent is a 1.1 ruleset. Patch versions never change the resolved value, so the checksums of existing chains stay the same. Every manifest carries the resolved value. - A runtime refuses what it does not implement. A document of a later minor version, and a
bundle's manifest or a single manifest whose
ruleCascadehas another major version, a later minor version, or is not a version at all, are refused withVERSION_UNSUPPORTED. A document of another major version, or whoseruleCascadeis not a version, already fails the schema (SCHEMA_INVALID). An absentruleCascadein a manifest is1.0.0, so existing bundles keep loading. - The bundle format does not change.
ruleCascadeBundlestays1.0.0: the version of the rules and the version of the container are different questions. isDateandisDateTimeare built on each runtime's existing date parser: they aretrueexactly when the date operators would accept the value, and never an evaluation error.
Consequences
- Deploying a bundle to a runtime that is too old fails when the bundle is loaded, at start-up or on a refresh, where it can be caught and rolled back.
- Raising
ruleCascadeis a major change of a ruleset (authoring guidelines 1.5): consumers on an older runtime refuse it. Its checksum changes even when the rules do not. - Runtimes released before this decision check only the major version, so they still accept a 1.1 bundle and fail per request. The protection starts with the first runtimes that implement 1.1. Upgrade every runtime that evaluates a ruleset before the ruleset uses a 1.1 operator.
- Each new operator now also needs a row in each runtime's table of operators and the version that added them.
Alternatives considered
- Bump the bundle format to 2.0.0. Old runtimes would refuse new bundles today, but every bundle, 1.0 rules included, would need a new runtime, and the format did not change.
- Let the compiler infer the version from the operators used. No author action, but the version a document conforms to would be invisible in the source, and a reviewer could not see a ruleset stop working on older runtimes in the diff.
- Check only at evaluation time. That is what happened before: the failure reaches users.