rete
A Rete rules engine that knows nothing about what it is deciding. It knows facts, tests on facts, priority, and a network in which rules that begin with the same tests share them. What the facts are is a domain, a type the host implements.
The name says what it is: a Rete network (Forgy, 1979). It is not called
rules because a host has its own rules, and its own crate of them, on top of
this one; lmjtfy's is packages/rules, which is the LMJTFY domain over rete.
What a host provides
A Domain names its facts, the values each can take, and what
a rule may do:
| Item | What it is |
|---|---|
Fact, Value | What can be known, and what it can turn out to be. |
Effect | A step that costs a call and teaches one fact (Domain::teaches). |
End | How a rule stops the query outright. |
Note | Something that is so when a rule holds, and that decides no step. |
asked_for(fact) | Whether a source outside the network supplies the fact when asked, or an effect teaches it. |
| names | Each of the above has a name, which is the vocabulary of rules written as data and what a trace logs. |
Rules are Rule { name, when: &[Test], then }, usually a const. Order is
priority: of the rules that hold, the first with something left to do decides.
Network::compile merges them.
The step that asks for everything at once
Some facts cost a round trip each. Network::next(&known) returns
Next::Ask(facts) with every fact that any rule still alive is waiting on, so
the host sends one request with N questions and records what comes back in
Known. The other answers are Next::Do(effect), Next::End(end) and
Next::Done. Nothing in the crate does I/O or is async: a host that must
await between steps calls next in its own loop.
A host that can answer synchronously passes a Host, two callbacks (ask for
the batch, perform for an effect), to Network::run. It returns the outcome,
what is known, and a Trace. A source that does not supply a wanted fact stops
the run as Outcome::Stuck rather than looping.
The trace
Every Run, and any step a host takes by hand (Network::explain), can say
which rule fired and what it stood on. Trace::records() gives them as
Records, which serialize to one JSON line each: the event (asked, fired,
learned, done, ended, stuck), the rule's index and name, the names and
values of the facts, and counts. Names, ids and counts only, never what a fact
was about, so a host can log every state change.
{"step":2,"event":"fired","rule":2,"name":"look at it","then":"do review","facts":["touches code","lines changed"],"values":["yes","known"],"count":2}
Rules as data
load reads a JSON file and validates it against the domain's vocabulary. It
fails, with every problem and the rule it is in, on an unknown fact, a value
the fact cannot have, an effect, end or note the domain does not allow, a rule
with no tests, a repeated or empty name, and any field it does not know. The
rules it returns compile beside the ones written in code (LoadedRule::rule),
after them, so code outranks data.
JSON, because every host already has a parser and its error says where it stopped. The array's order is priority. A review rule:
{ "rules": [
{ "name": "tests untouched",
"when": [ { "fact": "touches code", "is": "yes" },
{ "fact": "touches tests", "is": "no" } ],
"then": { "note": "ask for tests" } },
{ "name": "big change gets a look",
"when": [ { "known": "lines changed" } ],
"then": { "do": "review" } }
] }
A test is {"fact", "is"} or {"known"}; what a rule does is exactly one of
{"do"}, {"end"} and {"note"}. A data rule cannot do I/O: the engine is
pure, so it can read facts and say what to do, and the host decides whether to
do it. A domain refuses by default (parse_effect, parse_end and parse_note
return an error unless it opts in), so what a user may write is what the host
wrote down. export writes code rules as the same data, to start a user from.
Use it
A project depends on it by path, through a git submodule of this repository
(as lmjtfy does at third-party/rustcrates), and never copies it. Run the tests
with mise run test from the repository root.
| File | What it holds |
|---|---|
src/domain.rs | Domain, Test, Then, Rule and Known. |
src/network.rs | Network: compile, decide, next, held, used, explain. |
src/trace.rs | Host, Network::run, Trace and the Record a host logs. |
src/load.rs | Rules as data: load, load_specs, export, and what can be wrong with a rule. |
src/tests.rs | The engine and the loader on a small domain of their own, a change to be reviewed. |
The picture of a network is the sibling crate rete-draw.