feat(dsl): drools/DMN parity, fail-loud contract, scope cleanup -- ~9k lines of DSL hardening#16
Merged
Merged
Conversation
added 3 commits
May 24, 2026 19:19
…gistry, end-to-end tests; bump to 26.05.08 Removes silent-failure pockets across the parser, evaluator, and action executor; adds a pluggable function-registry extension point and three new DSL primitives; brings docs in line with the actual codebase; and bumps the project version to 26.05.08. Core correctness fixes - Parser: complex map-shaped action handling now throws ASTException with the original-map + reconstructed-syntax context instead of catch-and- return-null. Sub-rule action lists now route through the same parseActionList as the top level, so YAML-collapsed forEach / while / do actions parse the same way in either context. - Lexer/parser: ExpressionParser now attaches the parsed array index to VariableExpression (was parsed then discarded). - Numeric coercion: toNumberSafe and toBigDecimal converge on one contract: null treated as ZERO (financial-aggregation convention), non-numeric strings raise IllegalArgumentException with operand type info. - Conditions/actions: evaluateConditions, evaluateConditionalBlock, and executeActions all propagate RuntimeExceptions wrapped in RuleEvaluationException; the outer evaluateRules catch converts these to success=false with the action/condition index, debug string, and cause. - Action executor: unknown function names in `call` actions and unknown ArithmeticOperationType branches throw IllegalArgumentException with the registry-aware diagnostic. Arithmetic actions on non-numeric operands throw rather than silently no-op'ing. - Expression evaluator: matches() raises on bad regex pattern; getPropertyValue throws on missing bean accessor (maps still get null on missing key, matching json_get semantics); is_valid rejects unknown validation types with a list of supported types. - 16+ financial / formatting / utility functions (calculate_loan_payment, compound_interest, amortization, debt_to_income_ratio, credit_utilization, loan_to_value, calculate_apr, calculate_credit_score, calculate_risk_score, payment_history_score, format_currency, format_percentage, distance_between, time_hour, in_range, calculate_debt_ratio, calculate_ltv, calculate_payment_schedule): catch-and-return-null replaced with throws via a shared wrapFunctionError helper that prefixes the message with the function name and preserves already-good diagnostics. - dateadd / datediff / toLong: bad inputs and unknown units now throw with the list of supported units. Reactive correctness - ASTRulesEvaluationEngine.evaluateRulesReactive wraps the synchronous visitor in Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic()) so REST/JSON built-ins can block safely without stalling the Netty event loop. - CacheServiceImpl fire-and-forget writes now end with .onErrorComplete() before .subscribe(); errors are still logged via the existing doOnError. Variable-store safety - EvaluationContext switched from ConcurrentHashMap (rejects null values with NPE) to Collections.synchronizedMap(LinkedHashMap). json_get returning null on a missing path no longer NPEs when stored; iteration order is now insertion-stable. - @DaTa replaced with @Getter + selective @Setter; the three variable maps are final, so the auto-generated bulk setters that would bypass the typed setters' validateVariableName guard rails no longer exist. Extension point (new) - org.fireflyframework.rules.core.dsl.function.RuleFunction: functional interface, Object apply(Object[] args). - org.fireflyframework.rules.core.dsl.function.CustomFunctionRegistry: Spring @component holding registered functions. Case-insensitive lookup. Checked before the built-in catalog, so custom functions may shadow built-ins. Wired through ASTRulesEvaluationEngine -> ActionExecutor -> ExpressionEvaluator via optional constructor parameters; existing callers keep working without changes. - ActionExecutor's default branch for `call <fn>` delegates to the expression evaluator, so the same registered function is reachable from both expression (run / calculate / condition) and action (call) contexts. New built-in functions - coalesce(a, b, c, ...): returns the first non-null argument. - if_else(condition, thenValue, elseValue): inline ternary expression for use inside run / calculate / output contexts. - is_in_range(value, low, high): function form of the between operator. - calculate_age(birthDate[, asOfDate]), format_date(date[, pattern]), validate_email(value), validate_phone(value): function forms that complement the existing operator equivalents. Dead-code / stub removal - Deleted orphan AST classes never produced by the parser: AssignmentAction, AssignmentOperator, ArithmeticExpression, ArithmeticOperation. Removed visitor methods across ASTVisitor, ActionExecutor, ExpressionEvaluator, ValidationVisitor, PythonCodeGenerator, YamlDslValidator, and ASTRulesEvaluationEngine. - Deleted DSLParser.validateAST() (was a stub with no callers). - Deleted the commented-out HealthIndicator TODO block in DatabaseConfig (actuator wired in via the web module; a proper indicator belongs there). - Deleted the permanently @disabled AuditTrailIntegrationTest (no testcontainers infrastructure was present; logic is exercised by the unit-level AuditHelperTest and AuditTrailServiceTest). - Cleaned two stale "this was the TODO that's now implemented" breadcrumbs. Test coverage - 345 tests, 0 failures, 0 errors, 0 skipped (was 323/0/5 before this work). - New: CustomFunctionRegistryTest (7), DslPrimitivesTest (9), DoWhileAnd- ConditionFunctionTest (3), EndToEndScenarioTest (5). - EndToEndScenarioTest exercises the full pipeline in one realistic loan- eligibility rule across approval / decline / tier-cutoff / empty-debt / circuit-breaker scenarios. - testCallAction split into a happy-path test against the `log` built-in and a typed-error test for unknown function names. - testDateFunctionsErrorHandling split into a happy path + two fail-loud assertions, matching the new contract. Documentation (updated to match the codebase) - README.md: features list rewritten; quick-start YAML uses canonical when/then/else syntax that actually parses; new Custom Functions and Error Contract sections. - docs/yaml-dsl-reference.md: new functions documented in their right sections; examples that used `calculate` for function calls corrected to `run` (calculate is pure-math-only); new Custom Functions extension-point section; Error Behavior Reference table contrasting old vs new contract for 11 situations; REST chain-friendly contract called out explicitly as the deliberate exception to fail-loud. - docs/developer-guide.md: all references to the four removed AST classes removed from the file-tree diagram, AST hierarchy diagram, visitor interface example, and visitor-implementation walkthrough; replaced with the actual current AST plus the new function/ package. - docs/architecture.md: Error Handling section expanded into a 12-row reference table. Version - 26.05.07 -> 26.05.08 across all five module poms and the parent.
…t guard Audits every YAML example in the docs against the actual parser source-of- truth, fixes a flurry of documentation bugs, and locks the docs to the implementation at build time via a new parameterised validation test. What was wrong -------------- - 12 examples in yaml-dsl-reference.md and 4 elsewhere used `calculate ... as <function-call>(...)`. The DSL restricts `calculate` to pure-math expressions and raises IllegalArgumentException for function/REST/JSON calls; these examples couldn't actually be evaluated. Corrected to use `run`. - Arithmetic-action grammar was documented backwards. The parser is `<keyword> <value> <preposition> <target-variable>`, so the correct form is `multiply 1.5 by risk_factor`, not `multiply risk_factor by 1.5`. Updated the operator table and examples; added an explicit grammar note. - "Validation operators in expressions" example used C-style ternary `(... ? X : Y)` -- a syntax the parser doesn't have. Rewritten using the `if_else(cond, then, else)` built-in (which is documented in the same doc). - Several complete examples in docs/yaml-dsl-reference.md, common-patterns- guide.md, b2b-credit-scoring-tutorial.md, and quick-start-guide.md used unquoted YAML strings containing colons (e.g., `"Loan approved: " + amount`) or other patterns that the YAML / DSL parser rejects. Fixed where the rewrite was mechanical; tagged the rest with TODO skip markers (see below). New: DocExamplesValidationTest ------------------------------ - Extracts every fenced ```yaml block from README.md, docs/yaml-dsl- reference.md, docs/quick-start-guide.md, docs/common-patterns-guide.md, and docs/b2b-credit-scoring-tutorial.md (60 blocks total). - Skips blocks that don't look like complete rules (missing every top-level key), and skips blocks explicitly tagged with `<!-- doc-test:skip -->` in the surrounding markdown (with an optional trailing rationale parenthetical). - Parses each remaining block through the real `ASTRulesDSLParser` and fails the build with the file:line of the offending block if parsing throws. - 49 documented rule examples are now actively validated at every build. Future doc drift -- a renamed function, a removed operator, a typo, a syntactic restriction -- is caught immediately with a precise message. - 11 blocks are deliberately marked skip: schema sketches with `[placeholder]` text and template snippets used to describe DSL shape, plus a small set of legacy walkthrough examples carrying explicit TODO notes for future rewrite. Source-of-truth catalogue ------------------------- A parallel audit confirmed the doc now correctly enumerates every operator, action keyword, and built-in function the parser accepts -- including synonyms (`equals`/`==`, `at_least`/`>=`, `in`/`in_list`, etc.), 30+ comparison operators, 33 unary operators, the 16 action keywords, and the ~70 built-in functions in the ExpressionEvaluator switch. No documented feature is missing from the parser; no parser feature is undocumented. Tests ----- - 394 tests, 0 failures, 0 errors, 0 skipped (was 345 before this commit). +49 from the new parameterised DocExamplesValidationTest. Version ------- No version change in this commit -- still on 26.05.08 from the previous commit on this branch.
…d dep; symmetric arithmetic grammar This is the modernisation pass. It removes every piece of "we kept it around" DSL surface that wasn't actually serving users, and fixes the one real arithmetic-grammar coherence issue. The result is a smaller, more uniform, and more honest DSL. Three parallel audits drove this commit: 1. dead-code/deprecated-API audit 2. DSL-surface inconsistency audit 3. deep design-coherence audit What's removed -------------- - **`JsonPathExpression`, `RestCallExpression` AST classes** -- zero `new` callers anywhere in the codebase; every visitor across `ASTVisitor`, `ActionExecutor`, `ExpressionEvaluator`, `ValidationVisitor`, `PythonCodeGenerator`, `YamlDslValidator.ValidationVariableCollector`, and `ASTRulesEvaluationEngine. VariableReferenceCollector` had a method for them, but the parser never produced them. JSON path / REST functionality is reached through the ordinary `FunctionCallExpression` path (`json_get`, `rest_get`, etc.) -- unchanged for users. Removes 200 lines of dark code. - **Top-level `circuit_breaker:` YAML config block** (`ASTCircuitBreakerConfig` inner record + `convertToCircuitBreakerConfig` parser branch + ASTRulesDSL field + `validateCircuitBreakerConfig` validator stub). Parsed by the YAML layer, stored in the model, "validated" by an empty validator method, but **never read by the evaluator at runtime**. Resilience is already provided by the `circuit_breaker "MESSAGE"` action, which is unchanged. - **`commons-math3` dependency** -- zero `import org.apache.commons.math*` anywhere in core. Was used by the now-removed `ArithmeticExpression` (deleted in an earlier commit). Dead weight. - **`@Deprecated` annotation on `parseRules(String)`** -- the method is a legitimate synchronous convenience wrapper, used by 9 callers (5 tests, 4 production). Removing the tag and updating the JavaDoc honestly. The evaluator's `evaluateRules(String, Map)` was never `@Deprecated` and is treated the same way. What's improved --------------- - **Arithmetic grammar is now symmetric for `multiply` and `divide`.** The parser previously accepted only `multiply VALUE by VARIABLE` (e.g., `multiply 1.5 by risk_factor`) -- the English-natural reading is `multiply VARIABLE by VALUE`, so users would write `multiply risk_factor by 1.5` and get a "Expected variable name after 'by'" error. Both forms are now accepted and produce the same `ArithmeticAction`. `add` and `subtract` remain unchanged (their value-first English form -- `add 5 to score`, `subtract penalty from total` -- is already natural). - New `ArithmeticActionSymmetryTest` (5 cases) locks in the symmetry contract and exercises both forms for both `multiply` and `divide`, plus the unchanged `add`/`subtract` behaviour, plus a complex-value-expression case. Documentation ------------- - `docs/yaml-dsl-reference.md` -- removes the documentation of the top-level `circuit_breaker:` block, replacing it with an explicit note that the only circuit-breaker surface is the *action*, with an example. - `docs/developer-guide.md` -- the AST file-tree, visitor interface, and hierarchy diagram drop their references to `JsonPathExpression` and `RestCallExpression`. - `docs/governance-guidelines.md` -- removes the now-invalid `circuit_breaker:` config example, replaces with the action-form equivalent. Tests ----- - 398 tests, 0 failures, 0 errors, 0 skipped. - +5 from `ArithmeticActionSymmetryTest`. - `DocExamplesValidationTest` continues to actively validate 49 documented rule examples at every build; the deletion of the `circuit_breaker:` block documentation removed it from validation (it would have failed under the new parser anyway).
| } catch (Exception e) { | ||
| log.warn("Failed to parse reconstructed loop action: {}", actionString, e); | ||
| // Fall through to complex action parsing | ||
| log.debug("Reconstructed loop parse failed for '{}', retrying as structured map", actionString); |
added 8 commits
May 24, 2026 20:35
…able, rewrite three skipped examples
Fills the last gaps in the docs after the modernisation. Two earlier
audit recommendations remained outstanding from the structure pass; this
commit closes them and rewrites the remaining TODO-marked doc examples so
the build-time guard covers more surface.
Mental Model section (new)
--------------------------
Adds an explicit "What This Engine Is (and Isn't)" subsection right under the
introduction in docs/yaml-dsl-reference.md. Lists capabilities with ✅/❌
honest about the engine's boundaries:
- ✅ Stateless expression-evaluation over a single input map
- ✅ 30+ operators, loops, sub-rules, custom function registry, circuit
breaker action, Python compilation
- ❌ No rule-chaining across separate evaluations
- ❌ No persistent working memory / fact base (it is not Drools KIE)
- ❌ No forward/backward inference
- ❌ No cross-input joins
- ❌ No short-circuit in `if_else()` -- both branches are evaluated eagerly
- ❌ No decision tables (use if/then/else chains or sub-rules instead)
- ❌ No truth maintenance / retraction
Closes the "honest limitations" recommendation from the deep-design audit,
which was its highest-priority clarity item.
Synonyms / canonical-forms table (new)
--------------------------------------
Adds a "Synonyms and Canonical Forms" subsection under Reserved Keywords
covering every keyword that has more than one accepted spelling:
- Comparison operators: `equals`/`==`, `at_least`/`>=`/`greater_than_or_equal`, `in_list`/`in`, etc.
- Logical operators: `and`/`AND`/`&&`
- Action verbs: `forEach`/`for`
- Function aliases: `length`/`len`, `count`/`size`, `avg`/`average`,
`uppercase`/`upper`, `tonumber`/`number`, `is_in_range`/`in_range`,
`json_get`/`json_path`, `if_else`/`ifelse`, ...
- YAML top-level keys: `inputs`/`input`, `outputs`/`output`
Each table marks the canonical form so new code has clear guidance while
existing rules using the alternate spellings continue to parse. Also
records the 26.05.08 removal of the top-level `circuit_breaker:` config
block in the migration note.
Three skipped doc examples rewritten + re-enabled in the build-time guard
-------------------------------------------------------------------------
- **common-patterns-guide.md "Application Data Validation"** -- previously
used `calculate error_count as size(errors)` (function call inside
`calculate` is rejected) and an unquoted error message containing `: `
(YAML interprets as key/value). Rewritten to `run error_count as
size(errors)` and the error-message action wrapped in YAML single-quotes.
- **common-patterns-guide.md "Credit Risk Assessment"** -- previously used
C-style ternary `(creditScore >= ... ? 40 : 20)` (the engine has no `?:`)
and unquoted strings with colons in the factor-summary lines. Rewritten
using `if_else(...)` and YAML-quoted action strings with `-` separators
instead of `:`.
- **yaml-dsl-reference.md "Example 4: Advanced Validation"** -- same ternary
and string-quoting issues. Rewritten using `if_else` and intermediate
`run` actions to compose the scoring components.
- **b2b-credit-scoring-tutorial.md "Multi-stage evaluation"** -- kept as
illustrative reading material (it references many constants that would
need to be wired through ConstantService for a runnable demo). The skip
rationale was updated to point readers to the EndToEndScenarioTest in
the test suite as the complete runnable equivalent. The one inline
`recommendation_summary` line that had a colon-in-string YAML hazard is
rewritten to use `run` + YAML quoting + `-` separators so even readers
copying that snippet won't be misled.
Tests
-----
- 401 tests, 0 failures, 0 errors, 0 skipped.
- DocExamplesValidationTest now actively validates 51 documented rule
examples (up from 49) -- the two newly-passing examples are
Application Data Validation and Credit Risk Assessment, both of which
rewrote `calculate`/`size()` and ternary patterns.
…on guide Closes the audit-identified gaps that justify a final commit. Adds three test surfaces and one new doc, none of which require any production source change -- they validate properties of the existing engine. Concurrent-evaluation safety test --------------------------------- Hundreds of concurrent evaluations of the same rule against different inputs assert no cross-talk between EvaluationContexts. A real correctness property of the engine (each evaluation owns its own context, custom functions are stateless lookups, the parser caches an immutable AST) but the property is only meaningful when exercised. Two scenarios: - 500 concurrent evaluations of a loop-bearing rule across a 16-thread pool; each evaluation accumulates `factor * 10` from a forEach over a literal list and the test asserts the result matches the evaluation's own factor input. - 200 concurrent evaluations against a CustomFunctionRegistry-backed function asserting each evaluation sees its own argument list (no torn reads). End-to-end domain-breadth scenarios ----------------------------------- The existing EndToEndScenarioTest exercises one domain (loan eligibility). Added a second suite spanning three more domains so a regression that breaks one rule shape but not another still surfaces: - **Insurance pricing** -- tiered conditionals, multiplicative arithmetic, mixed numeric/categorical inputs, the new symmetric arithmetic grammar. Two scenarios (young NYC driver with accident -> HIGH tier; experienced suburban clean record -> PREFERRED tier). - **Card transaction fraud risk** -- arithmetic actions, CustomFunctionRegistry invoked from a rule for a geographic-distance risk function, decision banding via if_else(). Two scenarios (large cross-border crypto -> BLOCK; small local verified-device purchase -> ALLOW). - **KYC / compliance gate** -- validation operators in conditions (is_email, is_phone), not_in_list for sanctioned-country list, and branched-failure collection where multiple violations all end up in rejection_reasons. Two scenarios (well-formed applicant passes; five simultaneous violations all surface). Migration guide --------------- New `docs/migration-guide.md` covering three onramps: - **From Drools (DRL)** -- side-by-side example, then a 12-row conceptual mapping table covering KieSession, rule LHS patterns, salience, agendaGroup, globals, forward chaining, accumulate, queries, decision tables, MVEL. Explicitly calls out what does NOT map (cross-fact joins, truth maintenance, backward chaining). - **From Easy Rules** -- side-by-side @Rule-annotated Java vs YAML equivalent, with the conceptual mapping for Facts, RuleListener, MVEL expressions, composite rules. - **From a hand-rolled if/else service** -- the most common onramp. Five-step translation pattern. Closes with an honest "when Firefly is the wrong tool" section listing the six rule-engine capabilities that this engine intentionally doesn't try to cover. Linked from the README docs section. DocExamplesValidationTest extended ---------------------------------- The build-time doc-example guard now also scans docs/migration-guide.md, adding 2 more validated rule examples (Application Approval and the Easy Rules port). Total documented rule examples actively validated at every build: **53** (was 51). Tests ----- - 411 tests, 0 failures, 0 errors, 0 skipped (was 401). - +10 net: 2 concurrent, 6 domain E2E, 2 migration-guide doc examples. Source-location-in-runtime-errors -- noted in the audit -- remains deferred because it requires tracking YAML row/column through the YAML parse, which the current SnakeYAML + Jackson layer does not expose. Action debug strings already give the offending statement; the YAML coordinate is an additive improvement, not a correctness gap.
…actors, string formatting, metrics wiring
Closes the "missing features" audit by adding 14 new built-in functions, wiring
the existing RuleEngineMetrics into the evaluation engine, and documenting
everything in the reference. All previously-deferred audit items that can be
delivered without a larger design change are landed in this commit.
New built-in functions
----------------------
**Functional list operations** -- the DSL has no inline-lambda syntax yet, so
these higher-order helpers take a string function name; the named function is
resolved through the same lookup the evaluator uses for any other function call
(CustomFunctionRegistry first, then the built-in catalogue), so user-registered
Spring beans and engine built-ins both work:
- `filter(list, "fn")` -- keep items where the named predicate is truthy
- `map(list, "fn")` -- transform every item via the named function
- `reduce(list, initial, "fn")` -- accumulate left-to-right; reducer called as
`fn(accumulator, item)`
- `find(list, "fn")` -- first matching item, or null if none
- `sort(list)` -- ascending; works on numbers and Comparable types
- `reverse(list)` -- reversed copy
- `distinct(list)` -- dedup preserving insertion order
**Statistical aggregates** -- complementing the existing `sum` / `avg`:
- `median(list)` -- numeric median (mean of the two middle elements on even-length)
- `variance(list)` -- sample variance (n-1 denominator)
- `stddev(list)` -- sample standard deviation
**Date field extractors** -- complementing the existing `now` / `today` /
`dateadd` / `datediff` / `calculate_age` / `format_date`:
- `current_iso()` / `now_iso()` -- ISO-8601 with offset
- `year_of(date)` -- 2026
- `month_of(date)` -- 1..12
- `day_of_month(date)` -- 1..31
- `day_of_week(date)` -- ISO: Monday=1 ... Sunday=7
**String formatting** -- closes the gap left by having only string `+` concatenation:
- `format(template, args...)` -- substitutes `{0}`, `{1}`, ... placeholders.
Raises a clean error if the template references a missing placeholder.
- `concat(args...)` -- joins all argument string representations.
Per-rule metrics wired into the evaluation engine
-------------------------------------------------
The existing `RuleEngineMetrics` class was auto-configured as a Spring bean but
never actually used. Wired into `ASTRulesEvaluationEngine`:
- `recordCompilation(success)` fires on every parse, tagged by status.
- `recordUnmatched(ruleId)` fires when a rule completes with a falsy condition
outcome or a triggered circuit breaker, tagged by the rule's `name` (or
`"anonymous"` if not declared).
The metrics bean is `@Autowired(required = false)`, so existing tests and
applications that don't have Micrometer on the classpath work unchanged. Three
new evaluation-engine constructors preserve backward compatibility with
existing test code (2-arg, 4-arg, 5-arg variants all delegate to the new 6-arg
canonical form).
Tests
-----
- 425 tests, 0 failures, 0 errors, 0 skipped (was 411).
- +14 cases in new `NewBuiltinFunctionsTest`: filter/map/reduce/find/sort/
reverse/distinct, median/variance/stddev, date extractors, format
placeholder substitution + missing-placeholder error, concat, and one
end-to-end test composing filter → map → sum → format into a transaction
summary rule.
Documentation
-------------
`docs/yaml-dsl-reference.md` updated with:
- New "Higher-Order List Functions (by named function)" section with examples.
- New "Statistical Aggregates" section.
- Expanded Date/Time section with the four new field extractors.
- Expanded String section with the templated `format()` and `concat()`.
Build-time `DocExamplesValidationTest` continues to cover every fenced YAML
example in the user-facing docs.
The Python compilation feature -- which generated standalone Python source from
parsed AST rules and shipped a separate Python runtime library -- is removed in
full. It's outside the core mission of "stateless YAML-DSL rule evaluation in
the JVM" and was carrying ~8400 lines of code, ~3 Java test classes, a 13-file
Python runtime package, 9 REST endpoints, 1 doc guide, and matching SDK schema
definitions, none of which interact with the actual rule evaluator anymore.
Deleted (37 files, -8400 lines)
-------------------------------
Java sources:
- `dsl/compiler/PythonCodeGenerator.java`
- `dsl/compiler/PythonCompilationService.java`
- `dsl/compiler/PythonCompiledRule.java`
- `web/controllers/PythonCompilationController.java`
- the `dsl/compiler/` package directory itself (now empty)
Tests (Java):
- `PythonCodeGeneratorTest.java`
- `PythonCompilationServiceTest.java`
- `PythonCompilerIntegrationTest.java`
Runtime (Python):
- `python-runtime/firefly_runtime/` (12 modules: core, datetime, financial,
interactive, json_utils, logging_utils, rest_client, security, __init__)
- `python-runtime/tests/` (5 test files)
- `python-runtime/examples/` (compiled b2b example + walkthrough)
- `python-runtime/setup.py`, `requirements.txt`, `run_tests.py`, `README.md`
Documentation:
- `docs/python-compilation-complete-guide.md`
OpenAPI spec:
- 9 `/api/v1/python/*` endpoints stripped surgically (compile, compile/rule/{id},
compile/rule/code/{code}, compile/batch, cache, cache/get, cache/check,
cache/rule, stats)
- `PythonCompiledRule` schema component removed
- `Python Compilation` tag removed
References cleaned in remaining files
-------------------------------------
- `README.md` -- removed Python from one-liner, overview paragraph, features
list, configuration example, and documentation index
- `docs/yaml-dsl-reference.md` -- updated capability table row
- `docs/architecture.md` -- removed the "Cryptographic Security (Python
Runtime)" subsection and the Python references in "Input Validation" and
"Safe Code Evaluation"; renumbered the trailing security subsections
- `pom.xml` (root) -- description no longer mentions Python
What still works (unchanged)
----------------------------
The Java rule evaluator is the canonical execution path. Everything that was
covered before:
- Parsing YAML to AST (ASTRulesDSLParser)
- Reactive + sync evaluation (ASTRulesEvaluationEngine)
- 70+ built-in functions including the new filter/map/reduce/median/stddev/
variance/date-extractors/format/concat
- CustomFunctionRegistry extension point
- REST/JSON path built-ins
- Audit trail, caching, validation
- Circuit-breaker action
- Constants tier loaded from DB
Tests
-----
- 408 tests, 0 failures, 0 errors, 0 skipped (was 425; -17 from the three
deleted Python-compiler test classes).
- `DocExamplesValidationTest` continues to actively validate 53 documented
rule examples at every build.
Closes the "review the whole DSL definition / syntax" task that was deferred
in the previous commits. The earlier commits *changed* the DSL (added /
removed surface, hardened semantics, fixed grammar inversions); this commit
documents the *design rationale* and the open tensions that didn't get a
behaviour change but should be visible to anyone proposing future changes.
What this doc contains
----------------------
- **Philosophy** -- why the DSL is YAML-shaped, why prose-style keywords,
why we optimise for reviewability + editability + safety + narrowness.
- **The shape of a rule** -- consolidated map of the 11 top-level keys,
the 3 variable tiers, the 16 action verbs, the 30+ comparison ops, and
the 70+ built-in functions.
- **Design tensions that exist today** -- documents the seams honestly:
- `calculate` vs `run` (parse-time safety check vs cognitive load)
- YAML colon-in-strings trap (YAML-layer issue, not the DSL's fault, but
real for users)
- Three condition shapes (when/then/else, sub-rules, conditions block)
- Operator symbol vs keyword duality (`equals` vs `==`, ...)
- Naming-tier enforcement is convention not parse-error
- Functional list ops take a string name not a lambda
- `is_in_range` overlaps `between`
- Output type declarations are advisory
- **Decisions already made** -- 7-row table of the major design questions
the recent audits closed, with the deciding commit recorded.
- **Future direction** -- 12-row backlog of non-breaking improvements,
ranked by value × cost. Inline lambdas, YAML lint, strict-mode flags,
per-rule timeout, rule composition, etc.
- **Out-of-scope** -- explicit non-goals (pattern matching, inference,
truth maintenance, decision tables, alternate front-ends, separate
compilation target). Pattern-of-no is documented so future "should we add
X" discussions can quickly reference the rationale.
Linked from the README docs index.
No code changes, no test changes. The reference + migration-guide + this
design-review form the three-doc set for understanding the DSL at three
different abstraction levels: user-facing reference, onramp from other
engines, and design rationale.
…iority, defaults, timeout
Closes the "implement everything per the design review" task. This commit lands
the substantive feature additions that bring the DSL up to drools/DMN parity for
typical rule-engine use cases. The dsl-design-review.md doc was a working
artifact and is removed; everything actionable from it is now either implemented
or documented in the user-facing references.
New DSL features
----------------
* DMN-style decision tables: `decision_table:` block with `inputs:`, `outputs:`,
`hit_policy:` (FIRST | COLLECT | ANY | UNIQUE), and rows of `when:` predicates
+ `then:` output maps. Output values are literal by default; prefix a string
with `=` to mark it as a DSL expression evaluated against the current context.
Supports an `otherwise: true` fallback row.
* Rule composition via `invoke_rule(code, ...)`: synchronously evaluate a stored
rule by code and return its output map. Inputs are passed as alternating
`"key", value` pairs trailing the rule code -- this avoids the YAML/JSON `{}`
flow-mapping ambiguity inside action lines. Wired through a new RuleInvoker
interface in core; RuleInvokerImpl in services delegates to RuleDefinitionService.
* Drools-style sub-rule priority (salience): `priority: N` on each sub-rule.
Higher priority evaluates first; ties preserve YAML declaration order via a
stable sort. Defaults to 0 when unspecified.
* Per-rule timeout: `timeout: 5s` (or `"500ms"`, raw milliseconds) declares a
wall-clock budget. Exceeding it fails the rule cleanly via Reactor's
`Mono.timeout()` with a precise error message.
* Input declarations with defaults: `inputs:` now accepts the richer shape
`{ name: { type: ..., default: ... } }`. Caller-omitted variables are filled
in from declared defaults; caller-supplied values always win. The previous
flat shapes (`[a, b, c]` and `{a: number, b: string}`) still work.
New built-in functions
----------------------
* Advanced math: `exp`, `ln`, `log10`, `sin`, `cos`, `tan`, `atan2`.
All throw cleanly on non-finite results (`exp(1000)` is an error, not Infinity).
* Hashing: `hash(value)` (SHA-256, hex) with optional second algorithm
arg (MD5, SHA-1, SHA-512).
* Statistical: `percentile(list, p)` with linear interpolation.
* Logging: `log(message [, level])` as a first-class function and action,
routing through SLF4J at TRACE/DEBUG/INFO/WARN/ERROR.
Polish
------
* Pre-parse YAML lint: detects unquoted ': ' inside action lines and surfaces a
precise line number, instead of letting SnakeYAML throw a confusing
"Unexpected character" error. The lint correctly skips colons inside `()`,
`{}`, `[]`, and quoted strings.
* Strict naming validation: NAMING_001 and NAMING_002 (input camelCase, constant
UPPER_CASE) are promoted from WARNING to ERROR severity.
* invoke_rule produces a clear diagnostic when no RuleInvoker bean is configured.
Tests
-----
Added DroolsDmnParityFeaturesTest with 19 scenarios across percentile, hash,
log, advanced math, sub-rule priority, input defaults, per-rule timeout,
invoke_rule (single + multiple inputs + odd-arg error), decision tables (FIRST,
OTHERWISE, COLLECT, UNIQUE-ambiguous, multi-input), and pre-parse YAML lint
(positive + negative).
Final test scoreboard: 431 passed, 0 failed.
Docs
----
* `docs/yaml-dsl-reference.md` -- added sections for decision tables, sub-rule
priority, per-rule timeout, input defaults, the new built-ins, and the
invoke_rule function. Documented the `=` prefix for decision-table expression
outputs.
* `README.md` -- updated overview, features list, added Decision Table and
Rule Composition examples to the Quick Start. Removed the dsl-design-review
reference (the doc is deleted; everything actionable was implemented).
* `docs/migration-guide.md` -- the Drools side-by-side now maps salience to
`priority:`, decision tables to `decision_table:`, modify-and-refire chains to
`invoke_rule`, and KieSession timeouts to per-rule `timeout:`.
* `docs/dsl-design-review.md` -- deleted (working artifact, not a shipping doc).
A docs audit against the actual implementation surfaced three places where the docs still claimed features were unsupported even though they shipped in the previous commit: * yaml-dsl-reference.md Mental Model table -- promoted decision tables from "❌ -- represent as if/then/else chains" to "✅ DMN-style decision_table: block with FIRST/COLLECT/ANY/UNIQUE hit policies". Added rows for sub-rule priority, invoke_rule rule composition, per-rule timeout, and input defaults so the table reflects every drools/DMN-parity feature now in the engine. * migration-guide.md Drools comparison table -- updated to say Firefly supports decision tables (was "Not supported"); added rows for salience (`priority:`), per-rule timeout, and input defaults; updated the rule-chaining cell to mention invoke_rule for cross-rule composition. * yaml-dsl-reference.md operator catalogue -- added the `length_equals`, `length_greater_than`, and `length_less_than` operators (implemented since release but not previously documented).
…6.05.08
The CI build was failing because sibling Firefly framework libraries
(fireflyframework-utils, fireflyframework-validators, fireflyframework-kernel,
fireflyframework-cache, fireflyframework-starter-core) are referenced via
${project.version} and are not yet published at 26.05.08.
Reverting the rule-engine version to 26.05.07 so this branch builds in CI.
The bump to 26.05.08 will land in a follow-up commit once the broader
Firefly framework coordinated release ships.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Eleven-commit modernisation + drools/DMN-parity expansion of the Firefly Rule Engine. Hardens the error contract, broadens the built-in catalogue, removes scope debt (Python tier, orphan AST, dead config, etc.), and finishes with a substantial feature pass that brings the DSL up to drools/DMN parity for typical rule-engine use cases: decision tables, rule composition (
invoke_rule), sub-rule priority (salience), input defaults, per-rule timeout, plus a wider built-in catalogue and a pre-parse YAML lint.Test scoreboard:
431 passed, 0 failed, 0 errors, 0 skipped(was 323/0/5 at baseline; +108 net new tests).CI status: all four GitHub Actions checks pass (build, CodeQL java-kotlin, CodeQL actions, plus default-setup CodeQL).
Doc-test guard: 57 documented rule examples actively parsed at every build.
Net code delta: roughly
-7700lines vsmaindespite adding 7 new test classes, the drools/DMN feature surface, and 3 substantive new doc sections.Version note
This PR currently stays on 26.05.07 -- the rule-engine pulls in sibling Firefly framework libraries (
fireflyframework-utils,fireflyframework-validators,fireflyframework-kernel,fireflyframework-cache,fireflyframework-starter-core) via${project.version}. Those siblings have not yet shipped 26.05.08, so bumping the rule-engine ahead of them breaks the CI build. The version bump will land in a follow-up once the framework coordinated release publishes 26.05.08.The branch name still references
26.05.08for continuity with the prior PR thread.Commits on this branch
3f2cc23ac49281DocExamplesValidationTestbuild-time guard479c172f8fea5e22e12b2ecfff131c787dbf8504fa97b740b9ffb4c0b2cd021Highlights
1. Drools/DMN-parity feature pass (commit
97b740b)Decision tables (DMN-style): tabular
decision_table:block withinputs:,outputs:,hit_policy:(FIRST / COLLECT / ANY / UNIQUE), rows ofwhen:predicates +then:output maps, and optionalotherwise: truefallback. String outputs are literal by default; prefix with=to mark as a DSL expression evaluated against the current context.Rule composition via
invoke_rule(code, ...): synchronously evaluate a stored rule by code and return its outputs as a Map. Inputs are passed as alternating"key", valuepairs (avoids the YAML/JSON{}flow-mapping ambiguity in action lines). Wired through a newRuleInvokerinterface in core;RuleInvokerImplin services delegates toRuleDefinitionService.Sub-rule priority (salience):
priority: Non each sub-rule, drools-style. Higher priority evaluates first; ties preserve YAML declaration order via a stable sort.Per-rule timeout:
timeout: 5s(also500ms/ raw ms). Exceeding the budget fails the rule cleanly via ReactorMono.timeout().Input defaults:
inputs:now accepts{ name: { type, default } }. Caller-omitted variables are filled from declared defaults; caller-supplied values always win.2. New built-ins (commit
97b740b)Advanced math:
exp,ln,log10,sin,cos,tan,atan2. All throw cleanly on non-finite results.Hashing:
hash(value [, algorithm])-- SHA-256 default; MD5, SHA-1, SHA-512 also supported.Statistics:
percentile(list, p)with linear interpolation.Logging:
log(message [, level])as a first-class function + action, routing through SLF4J at TRACE/DEBUG/INFO/WARN/ERROR.3. Pre-parse YAML lint (commit
97b740b)Detects unquoted
:inside action lines and surfaces a precise line number, instead of letting SnakeYAML throw a confusing "Unexpected character" error. Correctly skips colons inside(),{},[], and quoted strings.4. Strict naming validation (commit
97b740b)NAMING_001(inputcamelCase) andNAMING_002(constantUPPER_CASE) promoted from WARNING to ERROR severity. Mis-cased declarations now fail validation cleanly.5. Fail-loud error contract (commit
3f2cc23)~30 silent failure paths converted to
success=falsewith precise diagnostics. The deliberate exception is REST functions which keep returning a structured{success, error, message}map for chain-friendly retry / circuit-break handling.6.
CustomFunctionRegistryextension point (commit3f2cc23)RuleFunction(functional interface) +CustomFunctionRegistry(Spring@Component). Custom functions checked before the built-in catalog, case-insensitive, reachable from both expression and action contexts.7. Symmetric
multiply/dividearithmetic grammar (commit479c172)Both
multiply VALUE by VARIABLEandmultiply VARIABLE by VALUEnow parse to the same AST node.8. Per-rule Micrometer metrics (commit
ecfff13)firefly.ruleengine.compilations+firefly.ruleengine.evaluationscounters tagged by status,rule.id, and outcome.@Autowired(required = false)— works without Micrometer.9. Python compilation tier removed (commit
1c787db)37 files, -8400 lines.
dsl/compiler/,python-runtime/,PythonCompilationController,python-compilation-complete-guide.md, OpenAPI spec entries. The Java rule evaluator is the canonical execution path.10. Dead code & tech-debt removed (commit
479c172)AssignmentAction,AssignmentOperator,ArithmeticExpression,ArithmeticOperation,JsonPathExpression,RestCallExpression)DSLParser.validateAST()stub@DisabledAuditTrailIntegrationTestcircuit_breaker:YAML configuration blockcommons-math3dependencyHealthIndicatoractuator-TODO comments11. Reactive correctness (commit
3f2cc23)Schedulers.boundedElastic()EvaluationContextvariable maps switched to synchronisedLinkedHashMap(null-tolerant)CacheServiceImplfire-and-forget writes terminated with.onErrorComplete()12. CI/code-scanning fixes (commit
b2cd021+ GitHub config)1c787db).13. Documentation aligned to reality + locked at build time
yaml-dsl-reference.md,developer-guide.md,architecture.md,governance-guidelines.md,quick-start-guide.md,common-patterns-guide.md,b2b-credit-scoring-tutorial.md,migration-guide.md-- all updatedinvoke_rule), Per-Rule Timeout, Input Declarations with Defaults, length operator cataloguedsl-design-review.md(working artifact),python-compilation-complete-guide.mdDocExamplesValidationTest: now validates 57 documented rule examples at every buildlength_equals/length_greater_than/length_less_thanoperatorsStats
mainNet code delta vs
main: roughly -7700 lines.Test Plan
mvn clean verifygreen across all 5 modulesDocExamplesValidationTest-- 57 rule examples from 6 docs all parseDroolsDmnParityFeaturesTest-- 19 scenariosNewBuiltinFunctionsTest(14)EndToEndScenarioTest,E2EDomainScenariosTest(11 cases across 4 domains)ConcurrentEvaluationTest,ArithmeticActionSymmetryTest,CustomFunctionRegistryTestfireflyframework-utils/-validators/-kernel/-cache/-starter-corepublish 26.05.08Version
26.05.07-- unchanged frommain. The 26.05.08 bump will land in a follow-up coordinated with the broader Firefly framework release.Replaces
This PR supersedes the now-closed #15. Same branch, eleven commits on top with the audit + modernisation + polish + cleanup + feature + scope-narrowing + drools/DMN-parity + docs-reality + CI passes.