[FEATURE] Add Rule Dependency Graph - #6
Merged
Merged
Conversation
…lds in README and CHANGELOG
…ion and issue with non-expressive error message
… checks and removing cycle detection
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.
Description
Adds rule dependency management to Excanon. Rules can now declare ordering and prerequisite
relationships to other rules by name, instead of relying solely on their position in the
loaded JSON array:
after— pure ordering. The referenced rule(s) run first; the dependent rule thenevaluates its own conditions and runs (or doesn't) exactly as it would today, regardless of
whether the referenced rule matched.
requires— ordering plus a gate. The referenced rule(s) run first, and the dependentis only evaluated if every rule it requires actually fired (matched) during the same
evaluation. An unmet prerequisite skips the dependent, and that skip propagates
transitively through chains of
requires.Rule sets with no
after/requiresedges anywhere continue to execute in exactly theiroriginal JSON-array order (no observable behavior change for existing callers).
Cycles in the dependency graph — via
after,requires, or a mix of both — are rejected atload_rules/2time with a descriptive error. Cyclic/iterative rule execution is intentionallyout of scope for this iteration.
Changes
lib/rule.ex: addedafter/requiresfields to%Rule{}(default[]), with validationin
Rule.new!/1(must be a list of strings if present).lib/rule_graph.ex(new, internal —@moduledoc false): validates a loaded rule set(duplicate names, unknown
after/requiresreferences, dependency cycles via:digraph)and produces a topologically-sorted rule list. Uses a hand-rolled, tie-broken Kahn's
algorithm rather than
:digraph_utils.topsort/1so that rules with no ordering constraintbetween them keep their original relative order — this is what makes the zero-edge case
byte-identical to today's behavior.
lib/stateful_rule_engine.ex:load_rules/2now runs loaded rules throughRuleGraph.build/1before storing them;execute_rules/2was reworked to track whichrules fired during an evaluation so
requiresgating can be checked.evaluate/2's publiccontract is unchanged.
README.md/CHANGELOG.md: documented the new fields and the load-time error cases.Breaking: rule
namevalues must now be unique within a loaded set — previouslyduplicate names were silently accepted.
RULE_DEPENDENCY_GRAPH_PLAN.md(the design spec) andRULE_DEPENDENCY_GRAPH_EXECUTION_PLAN.md(the work-package breakdown used to implement it).Type of Change
How Has This Been Tested?
mix test— 156 tests, 0 failures, including the full pre-existing suite unmodified (thebackward-compatibility guarantee) plus new coverage in
test/rule_test.exs,test/rule_graph_test.exs(new), andtest/stateful_rule_engine_test.exsfor: fieldvalidation, ordering via
afterandrequiresindependently and mixed, diamonddependencies, duplicate-name rejection, unknown-reference rejection, cycle rejection
(via
afteronly,requiresonly, a mix, and self-loops), and prerequisite gating (met,unmet, and transitive).
mix credo,mix dialyzer,mix sobelow— all clean, no new findings.mix docs— confirmedRuleGraphdoes not appear in generated documentation(
@moduledoc falserespected), whileStatefulRuleEngine's docs reflect the new fields.Manual exploratory checks (ordering, gating, duplicate-name and cycle error messages)
against a running engine.
Unit tests pass
Integration tests pass
Manual testing completed
Checklist
Additional Notes
RuleGraphis intentionally undocumented as public API (@moduledoc false) — it's aninternal implementation detail. The feature itself is documented at the
StatefulRuleEngineand README level instead.it's called out explicitly in
CHANGELOG.mdsince existing callers with accidental namecollisions across rules could start seeing
{:error, "Duplicate rule names: ..."}fromload_rules/2where they previously didn't.