jevcrates.git / jev-facts

Chapter 23: jev-facts — everything a rule is waiting on, in one envelope

Suppose you are deciding what to do with something, and your decision is a set of rules: if it is safe and it only reads, let it through; if it deletes, ask first. Each rule stands on facts (is it safe? does it read? does it delete?), and the facts are things only Jev can tell you.

The slow way is to ask as you go. Rule one wants "safe", so you ask Jev and wait. It also wants "reads", so you ask again and wait again. Three rules, five facts, five round trips.

A rules engine that knows its rules can do better. Before it asks anything, it can look at every rule still in the running and collect every fact any of them is waiting on. Jev answers many questions in one request for the price of one wait. So the engine says "I want these five", and this crate puts five questions in one envelope, sends nothing itself, and when the answers come back, reads each one as the fact it teaches.

That is all jev-facts is: the piece between "these facts are wanted" and "these facts are known".

sequenceDiagram
  participant E as A rules engine (rete, or your own)
  participant F as jev-facts
  participant J as Jev
  E->>F: wanted(facts): safe, reads, deletes, load
  F-->>E: Prepared: one request, three questions
  E->>J: the request (sent however the host sends)
  J-->>E: the response
  E->>F: prepared.judged(response), then learn(...)
  F-->>E: safe = no, reads = no, deletes = yes, load = 0.3

Four facts, three questions. That is not a mistake: "reads" and "deletes" are two readings of one question, "what does the command do?". They share an id, the question is asked once, and each fact takes its own value out of the one answer.

A host says what its facts are by implementing one trait:

impl jev_facts::Source for Command {
    type Fact = Fact;
    type Value = Value;
    fn question_id(&self, fact: Fact) -> String { /* "safe", "act", "load" */ }
    fn question(&self, fact: Fact) -> Result<Question, ProtocolError> { /* the Noul, Choice or Score */ }
    fn learned(&self, fact: Fact, judged: &Judged) -> Option<Value> { /* what an answer makes it */ }
}

Aside: it does not know what a rules engine is. Nothing here depends on one. The engine these projects use is rete (in the rustcrates repository), whose Network::next returns Next::Ask(facts): exactly the list wanted takes. But rete knows nothing of Jev, and this crate knows nothing of rete. A host with three hand-written ifs can use it just the same. The two meet in the host, in about ten lines.

Aside: it does not send anything either. wanted hands back a Prepared: the request body, exactly as it will go, and the most it can cost. The host sends it with whichever client it has (jev-worker on a Cloudflare Worker, jev-http on a server). That is why a page can show the request while Jev is still thinking, and why the tests here need no network.

Try it. cargo test -p jev-facts. The tests are a small domain of their own, a shell command with four facts. Read every_wanted_fact_goes_in_one_request_and_a_shared_question_once first; then make learned read the wrong type and watch an_answer_of_the_wrong_type_teaches_nothing name the question.

For the people who maintain it

learn is all or nothing: every fact with its value, or the first fact that was not taught (Unlearned::NotAsked when the request had no such question, Unlearned::WrongType when the answer is not the type asked). A host never records half an answer.

prepare is the same step for questions that are not facts (lmjtfy's second request asks questions a language model drafted): ids and questions in, a Prepared out.

In this folder

PathWhat
src/lib.rsSource, Judged, Part, Prepared, prepare, wanted, learn, Unlearned.
src/tests.rsThe command domain and six tests.
Cargo.tomlDepends on jev-protocol only.

← Previous: Chapter 22¾: jev-ui/assets · Up: jevcrates · The end of the guide. Back to Chapter 16: jevcrates