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/).