SP-13: rule engine completeness — operators, builder, port+service, handlers, modes (v26.06.93)#121
Merged
Merged
Conversation
added 10 commits
June 10, 2026 02:35
… Item 1) Adds between, contains, not_contains, starts_with, ends_with, exists, is_null, is_empty operators to RuleEvaluator._eval_condition; all None-safe with guarded style matching existing operators. Updates Condition docstring with full operator reference. Fixes the test_unknown_operator_is_surfaced test to use a truly unknown operator now that 'between' is a known op.
…P-13 Part A Item 2) Adds builder.py with field(), all_of(), any_of(), not_(), set_action(), increment_action(), log_action(), RuleBuilder (rule() factory), and RuleSetBuilder (ruleset() factory). Updates __init__.py to export all builder helpers and validation symbols. Tests confirm builder-constructed rules evaluate identically to raw-dataclass rules through RuleSetEvaluator.
…13 Part A Items 3-4) Adds validation.py with validate_ruleset(), RuleSetValidator.check()/ assert_valid(), and RuleValidationError; detects duplicate ids, unknown operators, bad between values, missing action targets, unknown action types, and malformed compound conditions. Adds test_operators.py (all 8 new operators, true/false/None per op, boundary cases) and test_loading_and_validation.py (from_json round-trip vs from_yaml, all validator error paths, assert_valid raises).
… (SP-13 Part B Items 1-2) - New ports/outbound.py: ActionHandler Protocol (runtime_checkable) + RuleEnginePort Protocol. - RuleEvaluator gains action_handlers ctor param; set/increment/log extracted into _make_default_handlers() callables; _execute_action delegates to self._handlers[type] or raises NotImplementedError for unknown types (audit #215 preserved, isolation preserved). - New EvaluationMode enum (ALL=default, FIRST_MATCH); RuleSetEvaluator gains mode param; FIRST_MATCH breaks after first result.matched, returning only the evaluated subset. - All existing tests pass unchanged (no subclassing required, same error semantics).
…13 Part B Items 3-4) - New service.py: RuleEngineService(repository, evaluator?, *, metrics?) satisfies RuleEnginePort; evaluate() sync, evaluate_by_name() async (raises RuleSetNotFoundError on miss); save/get/list_rulesets() thin passthrough; 4 labeled counters emitted when MetricsRecorder is injected (evaluations/matched/actions_fired/errors, label=ruleset). - auto_configuration.py: rule_set_evaluator reads pyfly.rule-engine.mode (all|first-match); new rule_engine_service bean wires repository + evaluator + optional MetricsRecorder. - __init__.py exports: EvaluationMode, ActionHandler, RuleEnginePort, RuleEngineService, RuleSetNotFoundError.
… facade (SP-13 Part B Item 5) - test_action_handlers.py: custom call handler invoked + mutates ctx; receives full Action; builtins survive injection; unregistered type raises NotImplementedError + sibling isolation; custom evaluator still errors on unknown types; override replaces builtin. - test_modes.py: ALL evaluates every rule + fires all actions; FIRST_MATCH stops after first match, lower-priority actions never fire; non-matching high rule does not stop FIRST_MATCH; no-match case returns all results; shared-context mutations visible. - test_service.py: save+evaluate_by_name round-trip; not-found raises RuleSetNotFoundError (KeyError subclass); sync evaluate(); get/list passthrough; counters (evaluations, matched, actions_fired, errors) increment with correct ruleset label.
…-13 Part C) Adds tests/rule_engine/test_end_to_end.py — a PluginSystemIntegrationTest-style proof that the full rule-engine subsystem composes correctly. Covers: builder/YAML equivalence (6 assertions), EvaluationMode.ALL with a 3-rule order-processing scenario including a custom 'call' ActionHandler (audit log), EvaluationMode.FIRST_MATCH stop-on-first-match semantics, MetricsRecorder counter increments via _FakeMetricsRecorder, repository round-trips, action isolation (sibling actions run even when one fails), and compound all_of conditions.
…SP-13 Part C) Rewrites docs/modules/rule-engine.md to accurately document the now-complete engine from Parts A+B. Adds: full model reference (Condition/Action/Rule/RuleSet field tables), complete operator reference table with None-safety notes for all 19 leaf operators and 3 compound operators, RuleSetLoader.from_yaml/from_json/from_dict with a YAML example, validation coverage (validate_ruleset / RuleSetValidator / RuleValidationError), fluent builder API (field/all_of/any_of/not_, action helpers, RuleBuilder/RuleSetBuilder chain methods), evaluation semantics (RuleEvaluator, RuleSetEvaluator, EvaluationMode.ALL vs FIRST_MATCH, action-isolation guarantee), custom ActionHandler protocol with a wiring example, RuleEnginePort / RuleEngineService method signatures, InMemoryRuleSetRepository, MetricsRecorder counter table, auto-config property table, and an "out of scope / by design" section for stateful forward-chaining and call/calculate extension points. Removes stale operator list and subclassing advice.
…egistry's callable shape (review)
…s, builder, port+service, handlers, modes, metrics)
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
Thirteenth sub-project of the PyFly↔Spring-Boot parity initiative — brought the rule engine (the weakest subsystem, ~18% parity → functional parity).
between,contains,not_contains,starts_with,ends_with,exists,is_null,is_empty(None-safe).field(...).<op>(),all_of/any_of/not_, action helpers,rule(id)...build()/ruleset(...)...build().from_json+ aRuleSetValidator(validate_ruleset,RuleValidationError).RuleEnginePort+ActionHandlerSPI;RuleEngineService(evaluate/evaluate_by_name/repo passthroughs) withpyfly_rule_*_totalmetrics.RuleEvaluator(action_handlers=...)) forcall/custom types without subclassing.ALL/FIRST_MATCH(pyfly.rule-engine.mode).Test Plan
4698 passed, 7 skipped, 45 deselected.