README.mdpreviewREADME.mdsource102 lines · 4.8 KB · raw
1# rete
2
3A Rete rules engine that knows nothing about what it is deciding. It knows
4facts, tests on facts, priority, and a network in which rules that begin with
5the same tests share them. What the facts are is a **domain**, a type the host
6implements.
7
8The name says what it is: a Rete network (Forgy, 1979). It is not called
9`rules` because a host has its own rules, and its own crate of them, on top of
10this one; lmjtfy's is `packages/rules`, which is the LMJTFY domain over `rete`.
11
12## What a host provides
13
14A [`Domain`](src/domain.rs) names its facts, the values each can take, and what
15a rule may do:
16
17| Item | What it is |
18|---|---|
19| `Fact`, `Value` | What can be known, and what it can turn out to be. |
20| `Effect` | A step that costs a call and teaches one fact (`Domain::teaches`). |
21| `End` | How a rule stops the query outright. |
22| `Note` | Something that is so when a rule holds, and that decides no step. |
23| `asked_for(fact)` | Whether a source outside the network supplies the fact when asked, or an effect teaches it. |
24| names | Each of the above has a name, which is the vocabulary of rules written as data and what a trace logs. |
25
26Rules are `Rule { name, when: &[Test], then }`, usually a `const`. Order is
27priority: of the rules that hold, the first with something left to do decides.
28`Network::compile` merges them.
29
30## The step that asks for everything at once
31
32Some facts cost a round trip each. `Network::next(&known)` returns
33`Next::Ask(facts)` with every fact that any rule still alive is waiting on, so
34the host sends one request with N questions and records what comes back in
35`Known`. The other answers are `Next::Do(effect)`, `Next::End(end)` and
36`Next::Done`. Nothing in the crate does I/O or is async: a host that must
37`await` between steps calls `next` in its own loop.
38
39A host that can answer synchronously passes a `Host`, two callbacks (`ask` for
40the batch, `perform` for an effect), to `Network::run`. It returns the outcome,
41what is known, and a `Trace`. A source that does not supply a wanted fact stops
42the run as `Outcome::Stuck` rather than looping.
43
44## The trace
45
46Every `Run`, and any step a host takes by hand (`Network::explain`), can say
47which rule fired and what it stood on. `Trace::records()` gives them as
48`Record`s, which serialize to one JSON line each: the event (`asked`, `fired`,
49`learned`, `done`, `ended`, `stuck`), the rule's index and name, the names and
50values of the facts, and counts. Names, ids and counts only, never what a fact
51was about, so a host can log every state change.
52
53```json
54{"step":2,"event":"fired","rule":2,"name":"look at it","then":"do review","facts":["touches code","lines changed"],"values":["yes","known"],"count":2}
55```
56
57## Rules as data
58
59`load` reads a JSON file and validates it against the domain's vocabulary. It
60fails, with every problem and the rule it is in, on an unknown fact, a value
61the fact cannot have, an effect, end or note the domain does not allow, a rule
62with no tests, a repeated or empty name, and any field it does not know. The
63rules it returns compile beside the ones written in code (`LoadedRule::rule`),
64after them, so code outranks data.
65
66JSON, because every host already has a parser and its error says where it
67stopped. The array's order is priority. A review rule:
68
69```json
70{ "rules": [
71  { "name": "tests untouched",
72    "when": [ { "fact": "touches code", "is": "yes" },
73              { "fact": "touches tests", "is": "no" } ],
74    "then": { "note": "ask for tests" } },
75  { "name": "big change gets a look",
76    "when": [ { "known": "lines changed" } ],
77    "then": { "do": "review" } }
78] }
79```
80
81A test is `{"fact", "is"}` or `{"known"}`; what a rule does is exactly one of
82`{"do"}`, `{"end"}` and `{"note"}`. A data rule cannot do I/O: the engine is
83pure, so it can read facts and say what to do, and the host decides whether to
84do it. A domain refuses by default (`parse_effect`, `parse_end` and `parse_note`
85return an error unless it opts in), so what a user may write is what the host
86wrote down. `export` writes code rules as the same data, to start a user from.
87
88## Use it
89
90A project depends on it by path, through a git submodule of this repository
91(as lmjtfy does at `third-party/rustcrates`), and never copies it. Run the tests
92with `mise run test` from the repository root.
93
94| File | What it holds |
95|---|---|
96| [`src/domain.rs`](src/domain.rs) | `Domain`, `Test`, `Then`, `Rule` and `Known`. |
97| [`src/network.rs`](src/network.rs) | `Network`: `compile`, `decide`, `next`, `held`, `used`, `explain`. |
98| [`src/trace.rs`](src/trace.rs) | `Host`, `Network::run`, `Trace` and the `Record` a host logs. |
99| [`src/load.rs`](src/load.rs) | Rules as data: `load`, `load_specs`, `export`, and what can be wrong with a rule. |
100| [`src/tests.rs`](src/tests.rs) | The engine and the loader on a small domain of their own, a change to be reviewed. |
101
102The picture of a network is the sibling crate [`rete-draw`](../rete-draw/).