Skip to content

SP-13: rule engine completeness — operators, builder, port+service, handlers, modes (v26.06.93)#121

Merged
ancongui merged 10 commits into
mainfrom
worktree-parity-sp13-rule-engine
Jun 10, 2026
Merged

SP-13: rule engine completeness — operators, builder, port+service, handlers, modes (v26.06.93)#121
ancongui merged 10 commits into
mainfrom
worktree-parity-sp13-rule-engine

Conversation

@ancongui

Copy link
Copy Markdown
Contributor

Summary

Thirteenth sub-project of the PyFly↔Spring-Boot parity initiative — brought the rule engine (the weakest subsystem, ~18% parity → functional parity).

  • Rich operators: between, contains, not_contains, starts_with, ends_with, exists, is_null, is_empty (None-safe).
  • Fluent builder DSL: field(...).<op>(), all_of/any_of/not_, action helpers, rule(id)...build() / ruleset(...)...build().
  • Loading + validation: from_json + a RuleSetValidator (validate_ruleset, RuleValidationError).
  • Hexagonal port + service: RuleEnginePort + ActionHandler SPI; RuleEngineService (evaluate/evaluate_by_name/repo passthroughs) with pyfly_rule_*_total metrics.
  • Pluggable action handlers (RuleEvaluator(action_handlers=...)) for call/custom types without subclassing.
  • Evaluation modes ALL/FIRST_MATCH (pyfly.rule-engine.mode).
  • E2E integration test + full docs rewrite. Forward-chaining out of scope by design.

Test Plan

  • ruff / format / mypy --strict clean (673 src files).
  • Fast suite: 4698 passed, 7 skipped, 45 deselected.
  • ~200 rule_engine tests (operators, builder, validation, modes, handlers, service, e2e).
  • Real Docker integration suite: 45 passed (no regression).
  • Review: APPROVED; the one Important finding (ActionHandler SPI shape) fixed.
  • CI lint/typecheck/test/build pass on this PR.

Andrés Contreras Guillén 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.
…s, builder, port+service, handlers, modes, metrics)
@ancongui ancongui merged commit d12852d into main Jun 10, 2026
5 checks passed
@ancongui ancongui deleted the worktree-parity-sp13-rule-engine branch June 10, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant