Reconciles this PR's Token-based alpha/beta matching (#300) with the
rule-actions/provenance layer merged separately in #1096. That PR built
bind_reasoner()/execute_matches() action-firing/_executed_activations/
reset_action_history() on top of the still-broken always-True stubs
(via an interim _bindings_for_rule() regex re-extraction), so main and
this branch touched the same propagation code with incompatible shapes.
Kept this branch's Token(facts, bindings) model for alpha/beta
propagation (the actual fix for #300) and layered main's action/
provenance plumbing on top of it, sourcing Match.bindings directly from
Token.bindings instead of re-deriving them with _bindings_for_rule(),
which is now redundant and removed. Also fixes a 2-tuple/3-tuple
unpacking break in test_matches_reasoner_match_rule caused by
Reasoner._match_rule()'s return shape changing upstream, and drops an
unrelated encoding-only .gitignore diff.
Verified: tests/reasoning/ (106 tests) and flake8 --max-line-length=88
both clean on the merged tree.
unify_condition() rebuilt a regex (re.split + concat + re.match) for
every fact tested against every alpha node. Since RETE evaluates many
facts across many alpha nodes, this repeated construction added
significant overhead.
- Extract regex construction into _build_condition_regex() (reused by
unify_condition and AlphaNode).
- AlphaNode.__init__ now compiles its condition once (no initial
bindings at alpha time) into self._compiled and reuses it per fact.
- On compile failure, log a WARNING and treat the node as non-matching,
consistent with the earlier observability fix.
- Add tests for the compiled path and the compile-failure fallback.
Refs #300
Previously unify_condition() silently caught re.error and returned None
with no log context, unlike Reasoner._match_pattern() which logs the
pattern/regex/fact on failure. This made malformed conditions hard to
diagnose in the RETE engine.
- Add a module-level logger ("semantica.rete_engine") for the standalone
unify_condition() helper.
- On re.error, log a WARNING including the condition pattern, compiled
regex, and fact string before returning None.
- Also catch unexpected exceptions (noqa BLE001) with the same context,
mirroring Reasoner._match_pattern behaviour.
- Add tests asserting both error paths log a warning and return None.
Refs #300
AlphaNode._matches and BetaNode._can_join were placeholder stubs that
always returned True, so the Rete network fired every rule for every
fact. Add a regex-based unify_condition (reusing Reasoner._match_pattern's
approach) that binds ?vars via named groups and enforces repeated-variable
and cross-condition binding consistency.
Rework propagation around a Token model (facts + bindings) instead of bare
facts: AlphaNode emits single-fact tokens, and BetaNode.join merges left/
right tokens, concatenating facts in condition order and returning a merged
token only when shared variables agree. This fixes a P1 chained-join defect
where rules with three or more conditions lost bindings and accumulated
wrong facts at the third join, and a conflicting third condition could
spuriously fire. Beta nodes now keep both left/right token memories and
join each new token against every token on the opposite side.
Also fix an adjacent bug where beta nodes were never wired into their
inputs' children, blocking propagation. Adds tests/reasoning/test_rete_engine.py
including a TestThreeConditionChain suite (valid match, third-level conflict
suppression, insertion-order independence, complete in-order Match.facts,
multiple left tokens joining one right fact, parity against
Reasoner._match_rule, and reset clearing all token memory).