A shell command, before it runs.
Jev is asked three questions in one request: which of eight acts the
command is ([Act]), how hard it would be to undo, and how much of the
machine it takes. Five facts are read out of those three answers, each by
a threshold below. Two more are not Jev's: how much memory there is room
for, which the daemon measures when a rule tells it to, and whether the
command has waited long enough for room, which the daemon knows.
[RULES] turn the seven into a verdict.
17use crate::{Never, SHOWN_CHARS, head, percent};
A command is put to the user when Jev's probability of one particular
consequential act reaches this. One act, not their sum: a thin spread over
several is Jev being unsure, which is a Pass, not a flag.
22pub const ASK_AT_CONSEQUENTIAL: f64 = 0.60;
... or when its expected undo level reaches this (1 is "one ordinary command puts it back", 2 is "only with care or luck").
25pub const ASK_AT_UNDO: f64 = 1.60;
A command runs without asking when Jev's probability that it is one of
the ordinary acts reaches this, and its expected undo level is at most
[ALLOW_UNDO_AT_MOST].
The memory a command of each load level should find available before it starts, in megabytes, lowest level first.
33pub const LOAD_NEEDS_MB: [f64; 4] = [0.0, 500.0, 3000.0, 6000.0];
What "the command does" means:
without it Jev judges dangerous words wherever they appear, and a command
that only writes, prints or sends rm -rf as text is stopped as if it
ran it. Sent once per request, as the state's how_to_judge_the_command.
39const WHAT_RUNS: &str = "Judge only what the shell would execute when this command line runs. Text that is \ 40 merely carried as data is not executed: the body of a here-document, or a quoted string, that is written to \ 41 a file, printed, searched for, compared, or sent as the content of a request. Such text is executed only \ 42 when it is handed to something that runs it: sh, bash, eval, source, xargs, ssh, or an interpreter such as \ 43 python or node. Writing a script to a file does not run the script.";
The most consequential thing a shell command does. Each variant's definition is the text Jev chooses by.
The option's label, as Jev is given it and answers with it.
What the option means, in acts.
78 pub fn definition(self) -> &'static str { 79 match self { 80 Act::Read => { 81 "It only reads or prints: listing, searching, showing, measuring, asking a program or a \ 82 service for its status, counters or settings. Nothing on disk or anywhere else is different \ 83 afterwards. It is still this when the reading is done through another shell or on another \ 84 machine (ssh, PowerShell, a remote exec wrapper, an API request that only fetches)." 85 } 86 Act::Build => { 87 "It builds, tests, formats, lints or installs a project's dependencies: it creates, replaces \ 88 or removes generated files (build output, caches, lockfiles, formatted source) that running \ 89 the build again would produce. Also this: creating, overwriting or removing scratch files \ 90 and directories under a temporary location (/tmp, a scratchpad, a temp folder) that the \ 91 command itself makes or that exist only to be thrown away." 92 } 93 Act::Edit => { 94 "It creates or changes files a person wrote or will keep (source, notes, configuration inside \ 95 a repository), in any directory, or records them in version control (add, commit, a new \ 96 branch, a stash). The earlier content can be recovered with an ordinary version-control or \ 97 editor command." 98 } 99 Act::Delete => { 100 "It removes or overwrites files or data that someone keeps, that no build regenerates and \ 101 version control does not hold: rm of untracked or personal files, truncating a kept file \ 102 with a redirect, emptying a directory of kept files, dropping a database or its rows." 103 } 104 Act::History => { 105 "It discards or rewrites version-control state: reset --hard, checkout or restore over \ 106 uncommitted work, clean, rebase, amending pushed commits, a force push, deleting a branch \ 107 or a stash." 108 } 109 Act::System => { 110 "It changes how the machine or the user's account is set up: installing, removing or \ 111 upgrading system packages, switching the system to a new configuration, starting, stopping \ 112 or enabling services, changing permissions and ownership, editing shell profiles and \ 113 dotfiles in the home directory, writing the registry or system settings, killing processes, \ 114 anything that changes something with sudo." 115 } 116 Act::Remote => { 117 "It publishes or sends something to another machine or service: push, deploy, publish, \ 118 release, an API call that writes, sending a message." 119 } 120 Act::Unread => { 121 "It runs code nobody has read: a script piped from the network into a shell, eval of fetched \ 122 content, an installer run straight from a URL." 123 } 124 } 125 }
The phrase the user reads.
128 pub fn phrase(self) -> &'static str { 129 match self { 130 Act::Read => "only reads", 131 Act::Build => "builds or tests", 132 Act::Edit => "edits files in a way version control can undo", 133 Act::Delete => "deletes or overwrites data nothing regenerates", 134 Act::History => "discards or rewrites version-control state", 135 Act::System => "changes the machine or the account", 136 Act::Remote => "publishes or sends something elsewhere", 137 Act::Unread => "runs code nobody has read", 138 } 139 }
Acts worth stopping for. The rest are a developer's ordinary work.
How hard a command is to put back, lowest first: the Score's levels.
152const UNDO_LEVELS: [&str; 4] = [ 153 "Nothing to put back: the command changes nothing.", 154 "One ordinary command puts it back: deleting a new file, git checkout or revert, running a build again.", 155 "It can be put back only with care or luck: digging through the reflog, restoring a backup, re-creating \ 156 work by hand, reinstalling.", 157 "It cannot be put back from this machine: the data is gone, or it has been published or sent somewhere else.", 158];
The same levels as the user reads them.
161const UNDO_PHRASES: [&str; 4] = ["nothing to undo", "undone by one command", "hard to undo", "cannot be undone"];
How much of the machine a command takes while it runs, lowest first: the load Score's levels.
165const LOAD_LEVELS: [&str; 4] = [ 166 "Negligible: it finishes at once and uses almost no memory. Listing, reading, git bookkeeping, moving a file.", 167 "Light: one small program for a moment. A formatter, a linter on a few files, a short script, one small test.", 168 "Heavy: it compiles a project, runs a whole test suite, builds a container or a package, or starts a \ 169 browser or another AI coding session. Several processor cores and gigabytes of memory for a while.", 170 "Very heavy: several heavy jobs at once, an optimised release build of a large project, a build of many \ 171 packages, or anything that holds many gigabytes of memory.", 172];
The same levels as the user reads them.
175const LOAD_PHRASES: [&str; 4] = ["a negligible", "a light", "a heavy", "a very heavy"];
The domain: a marker for the engine, and the source of the questions.
Something known about a command.
One consequential act is at least [ASK_AT_CONSEQUENTIAL] likely.
185 Consequential,
The expected undo level is at least [ASK_AT_UNDO].
187 HardToUndo,
The ordinary acts together are at least [ALLOW_AT_ORDINARY] likely.
189 Ordinary,
The expected undo level is at most [ALLOW_UNDO_AT_MOST].
191 EasyToUndo,
How much of the machine it takes: the level Jev's score rounds to.
193 Load,
Whether there is memory for it now. Not Jev's: [Effect::Measure]
teaches it.
196 Room,
Whether it can still wait for room. Not Jev's: the daemon knows how long the command has waited, and says so before the rules run.
What a fact can turn out to be.
[Fact::Room]: there is memory for what the command needs.
213 Enough,
There is less than it needs.
215 Short,
Memory cannot be measured on this machine, so the load is not judged.
217 Unmeasured,
[Fact::Patience]: it may wait longer.
219 Left,
The one thing a rule sends the daemon to do.
See how much memory there is room for: what the machine reports available, less what is booked for commands still growing into theirs.
239impl Domain for Command { 240 type Fact = Fact; 241 type Value = Value; 242 type Effect = Effect; 243 type End = Verdict; 244 type Note = Never; 245 246 fn facts() -> &'static [Fact] { 247 &[Fact::Consequential, Fact::HardToUndo, Fact::Ordinary, Fact::EasyToUndo, Fact::Load, Fact::Room, Fact::Patience] 248 } 249 250 // Short, because a drawing has a narrow box for a name and its value. 251 fn fact_name(fact: Fact) -> &'static str { 252 match fact { 253 Fact::Consequential => "consequential", 254 Fact::HardToUndo => "hard to undo", 255 Fact::Ordinary => "ordinary work", 256 Fact::EasyToUndo => "easy to undo", 257 Fact::Load => "load", 258 Fact::Room => "room", 259 Fact::Patience => "patience", 260 } 261 } 262 263 fn values(fact: Fact) -> &'static [Value] { 264 match fact { 265 Fact::Consequential | Fact::HardToUndo | Fact::Ordinary | Fact::EasyToUndo => &YES_NO, 266 Fact::Load => &LOADS, 267 Fact::Room => &[Value::Enough, Value::Short, Value::Unmeasured], 268 Fact::Patience => &[Value::Left, Value::Spent], 269 } 270 } 271 272 fn value_name(value: Value) -> &'static str { 273 match value { 274 Value::Yes => "yes", 275 Value::No => "no", 276 Value::Negligible => "negligible", 277 Value::Light => "light", 278 Value::Heavy => "heavy", 279 Value::VeryHeavy => "very heavy", 280 Value::Enough => "enough", 281 Value::Short => "short", 282 Value::Unmeasured => "unmeasured", 283 Value::Left => "left", 284 Value::Spent => "spent", 285 } 286 } 287 288 fn asked_for(fact: Fact) -> bool { 289 !matches!(fact, Fact::Room | Fact::Patience) 290 } 291 292 fn teaches(effect: Effect) -> Fact { 293 match effect { 294 Effect::Measure => Fact::Room, 295 } 296 } 297 298 fn effect_name(effect: Effect) -> String { 299 match effect { 300 Effect::Measure => "measure".to_owned(), 301 } 302 } 303 304 fn end_name(end: Verdict) -> String { 305 verdict_name(end).to_owned() 306 } 307 308 fn note_name(note: Never) -> String { 309 match note {} 310 } 311}
A verdict's name, as the mod and the log spell it.
The rules, in priority order: of the rules that hold, the first decides.
Measuring comes first so that a command put to the user for what it does
is put with both reasons when memory is short as well. The two rules on
what it does come before the two on room, so such a command is never kept
waiting. A command with no room is put to the user unless it is known to
have patience left: not knowing is not a reason to wait. The last two
are every answer the rule before them does not allow, each named for
what is true of it, so an answer no other rule acts on is a Pass, and
the user's own permission rules decide.
333pub const RULES: [Rule<'static, Command>; 8] = [ 334 Rule { name: "look for room", when: &[Test::Known(Fact::Load)], then: Then::Do(Effect::Measure) }, 335 Rule { name: "a consequential act", when: &[Test::Is(Fact::Consequential, Value::Yes)], then: Then::End(Verdict::Ask) }, 336 Rule { name: "hard to put back", when: &[Test::Is(Fact::HardToUndo, Value::Yes)], then: Then::End(Verdict::Ask) }, 337 Rule { 338 name: "no room yet", 339 when: &[Test::Is(Fact::Room, Value::Short), Test::Is(Fact::Patience, Value::Left)], 340 then: Then::End(Verdict::Hold), 341 }, 342 Rule { name: "no room", when: &[Test::Is(Fact::Room, Value::Short)], then: Then::End(Verdict::Ask) }, 343 Rule { 344 name: "ordinary and easily undone", 345 when: &[Test::Is(Fact::Ordinary, Value::Yes), Test::Is(Fact::EasyToUndo, Value::Yes)], 346 then: Then::End(Verdict::Allow), 347 }, 348 Rule { name: "not surely ordinary", when: &[Test::Is(Fact::Ordinary, Value::No)], then: Then::End(Verdict::Pass) }, 349 Rule { name: "not easily undone", when: &[Test::Is(Fact::EasyToUndo, Value::No)], then: Then::End(Verdict::Pass) }, 350];
[RULES] as the network that runs.
What Jev judges: the command, where it runs, and how to read it.
What "the command does" means is said once, here, where all three questions read it. It used to end each question; asked both ways, ten commands got the same verdicts and the request was 212 tokens smaller (1,555 to 1,343; the daemon's live test, 2026-10-06).
363pub fn state(command: &str, cwd: Option<&str>, root: Option<&str>) -> Result<Json, ProtocolError> { 364 let state = json!({ 365 "command": head(command, SHOWN_CHARS), 366 "working_directory": cwd, 367 "project_root": root, 368 "how_to_judge_the_command": WHAT_RUNS, 369 }); 370 Json::verbatim(&state.to_string()) 371}
The probability of each act, from the act Choice's answer.
The likeliest single consequential act's probability. One act, never their sum.
The ordinary acts' probabilities together.
389impl Source for Command { 390 type Fact = Fact; 391 type Value = Value; 392 393 fn question_id(&self, fact: Fact) -> String { 394 match fact { 395 Fact::Consequential | Fact::Ordinary => "act", 396 Fact::HardToUndo | Fact::EasyToUndo => "undo", 397 Fact::Load => "load", 398 Fact::Room => "room", 399 Fact::Patience => "patience", 400 } 401 .to_owned() 402 } 403 404 fn question(&self, fact: Fact) -> Result<Question, ProtocolError> { 405 Ok(match fact { 406 Fact::Consequential | Fact::Ordinary => Question::Choice(Choice::new( 407 Json::text( 408 "A coding assistant is about to run this shell command for a developer. Which one of these \ 409 describes what the command does? If it does several of them, pick the one that is hardest to \ 410 undo.", 411 ), 412 Act::ALL.map(|act| (act.label().to_owned(), Some(Json::text(act.definition())))), 413 )?), 414 Fact::HardToUndo | Fact::EasyToUndo => Question::Score(Score::new( 415 Json::text("How hard would it be to put everything back exactly as it was before this command ran?"), 416 UNDO_LEVELS.map(Json::text), 417 )?), 418 Fact::Load => Question::Score(Score::new( 419 Json::text("How much of the machine does this command take while it runs?"), 420 LOAD_LEVELS.map(Json::text), 421 )?), 422 Fact::Room | Fact::Patience => { 423 return Err(ProtocolError::Invalid(format!("{} is not a fact Jev is asked for", Command::fact_name(fact)))); 424 } 425 }) 426 } 427 428 fn learned(&self, fact: Fact, judged: &Judged) -> Option<Value> { 429 Some(match (fact, judged) { 430 (Fact::Consequential, Judged::Choice(answer)) => yes(consequential(&acts(answer)) >= ASK_AT_CONSEQUENTIAL), 431 (Fact::Ordinary, Judged::Choice(answer)) => yes(ordinary(&acts(answer)) >= ALLOW_AT_ORDINARY), 432 (Fact::HardToUndo, Judged::Score(answer)) => yes(answer.score >= ASK_AT_UNDO), 433 (Fact::EasyToUndo, Judged::Score(answer)) => yes(answer.score <= ALLOW_UNDO_AT_MOST), 434 (Fact::Load, Judged::Score(answer)) => LOADS[(answer.score.round() as usize).min(LOADS.len() - 1)], 435 _ => return None, 436 }) 437 } 438}
Jev's numbers about a command, beside the facts they teach: what the line the user reads is written from, what the memory it needs is worked out from, and what the decision log keeps.
The probability of each act.
446 pub acts: Vec<(Act, f64)>,
The expected undo level, 0 to 3.
448 pub undo: f64,
454impl Answers {
From a request's answers; None unless all three questions were in it.
456 fn read(prepared: &Prepared, judged: &[Judged]) -> Option<Self> { 457 let answer = |id: &str| prepared.parts.iter().position(|part| part.id == id).and_then(|index| judged.get(index)); 458 let (Judged::Choice(act), Judged::Score(undo), Judged::Score(load)) = (answer("act")?, answer("undo")?, answer("load")?) else { 459 return None; 460 }; 461 Some(Answers { 462 acts: acts(act), 463 undo: undo.score, 464 load: load.score, 465 json: json!({ 466 "act": { 467 "choice": act.choice, 468 "confidence": act.confidence, 469 "probabilities": act.probabilities.iter().map(|(label, p)| (label.clone(), json!(p))).collect::<serde_json::Map<String, serde_json::Value>>(), 470 }, 471 "undo": { "score": undo.score, "probabilities": undo.probabilities }, 472 "load": { "score": load.score, "probabilities": load.probabilities }, 473 }), 474 }) 475 }
The megabytes this command should find free before it starts.
The likeliest single consequential act's probability: the number
behind [Fact::Consequential].
The ordinary acts' probabilities together: the number behind
[Fact::Ordinary].
Every option's probability, as the decision log keeps it.
What Jev said about a command: the facts, and the numbers behind them. Kept apart from the verdict because the verdict also depends on how much memory there is room for, and that changes while a command waits its turn.
The facts the first step asks Jev for: every fact of the domain that is Jev's to give, since every rule is waiting on one.
What a response to prepared (a request for facts) teaches.
520pub fn taught(prepared: &Prepared, response: &Response, facts: &[Fact]) -> Result<Asked, Unlearned> { 521 let judged = prepared.judged(response); 522 let mut known = Known::default(); 523 for (fact, value) in jev_facts::learn(&Command, prepared, &judged, facts)? { 524 known.learn(fact, value); 525 } 526 let answers = Answers::read(prepared, &judged).ok_or(Unlearned::NotAsked { id: "act, undo and load".to_owned() })?; 527 Ok(Asked { known, answers }) 528}
The memory a command of expected load level load should find
available, interpolated between the levels' needs.
532pub fn needed_mb(load: f64) -> f64 { 533 let load = load.clamp(0.0, (LOAD_NEEDS_MB.len() - 1) as f64); 534 let below = load.floor() as usize; 535 let above = (below + 1).min(LOAD_NEEDS_MB.len() - 1); 536 LOAD_NEEDS_MB[below] + (LOAD_NEEDS_MB[above] - LOAD_NEEDS_MB[below]) * (load - below as f64) 537}
What [Effect::Measure] teaches: whether room_mb megabytes are enough
for a command that needs needs_mb. No figure is no opinion.
[Fact::Patience], from whether the command may be kept waiting longer.
554impl Asked {
Whether the command is put to the user whatever memory there is.
The verdict, with room_mb megabytes to spare (absent where memory
cannot be measured) and whether the command may be kept waiting, and
the line the user reads. The network decides; when it says to
measure, the figure given is the measurement.
564 pub fn settle(&self, network: &Network<Command>, room_mb: Option<f64>, may_wait: bool) -> (Verdict, String) { 565 let mut known = self.known.clone(); 566 known.forget(Fact::Room); 567 known.learn(Fact::Patience, patience(may_wait)); 568 let verdict = loop { 569 match network.next(&known) { 570 Next::Do(Effect::Measure) => known.learn(Fact::Room, room(self.answers.needs_mb(), room_mb)), 571 Next::End(verdict) => break verdict, 572 // Nothing Jev did not answer can be asked from here, and 573 // not knowing never blocks. 574 Next::Ask(_) | Next::Done => break Verdict::Pass, 575 } 576 }; 577 (verdict, self.line(&known, room_mb, verdict)) 578 }
What Jev thought and how sure it was, as the permission prompt and the band show it.
582 fn line(&self, known: &Known<Command>, room_mb: Option<f64>, verdict: Verdict) -> String { 583 let Answers { acts, undo, load, .. } = &self.answers; 584 let (likeliest, likelihood) = acts.iter().copied().max_by(|a, b| a.1.total_cmp(&b.1)).unwrap_or((Act::Read, 0.0)); 585 let level = (undo.round() as usize).min(UNDO_PHRASES.len() - 1); 586 let said = format!("Jev: {} ({}%); {} ({undo:.1} of 3)", likeliest.phrase(), percent(likelihood), UNDO_PHRASES[level]); 587 // Too little memory for what the command is about to start. 588 let short = room_mb.filter(|_| known.get(Fact::Room) == Some(Value::Short)).map(|room| { 589 let load_level = (load.round() as usize).min(LOAD_PHRASES.len() - 1); 590 format!("{} command ({load:.1} of 3) with {:.1} GB of memory to spare", LOAD_PHRASES[load_level], room / 1024.0) 591 }); 592 match (verdict, self.is_risky(), short) { 593 (Verdict::Ask, true, Some(short)) => format!("{said}; {short}"), 594 (Verdict::Ask, true, None) => said, 595 (Verdict::Ask | Verdict::Hold, false, Some(short)) => format!("Jev: {short}"), 596 (Verdict::Allow, ..) => format!("{said}; allowed"), 597 _ => format!("{said}; left to the usual permission check"), 598 } 599 }
Answers made up for a test or a worked example, read the way a real response is: a response body with these numbers, parsed and learned from.
604 #[cfg(feature = "made-up")] 605 pub fn made_up(acts: &[(Act, f64)], undo: f64, load: f64) -> Self { 606 let network = network(); 607 let facts = wanted(&network); 608 let model = crate::jev_model().expect("the pinned model"); 609 let prepared = jev_facts::wanted(&Command, &model, state("a command", None, None).expect("a state"), &facts).expect("a request"); 610 let probabilities: Vec<(&str, f64)> = 611 Act::ALL.iter().map(|each| (each.label(), acts.iter().find(|(act, _)| act == each).map_or(0.0, |(_, p)| *p))).collect(); 612 let body = crate::made_up_body(&[ 613 ("act", crate::made_up_choice(&probabilities)), 614 ("undo", crate::made_up_score(undo, UNDO_LEVELS.len())), 615 ("load", crate::made_up_score(load, LOAD_LEVELS.len())), 616 ]); 617 let response = Response::parse(&model, prepared.asking().1, body.as_bytes()).expect("a response Jev could send"); 618 taught(&prepared, &response, &facts).expect("every fact asked for") 619 }
Made up: Jev sure of one act.
The verdict on made-up answers with this much memory to spare and nobody to wait for.
Every fact Jev can give goes in the one first request, and the three questions behind the five facts are each asked once.
643 #[test] 644 fn the_first_step_asks_everything() { 645 let facts = wanted(&network()); 646 assert_eq!(facts, [Fact::Consequential, Fact::HardToUndo, Fact::Ordinary, Fact::EasyToUndo, Fact::Load]); 647 let prepared = jev_facts::wanted(&Command, &crate::jev_model().unwrap(), state("ls", None, None).unwrap(), &facts).unwrap(); 648 let ids: Vec<&str> = prepared.parts.iter().map(|part| part.id.as_str()).collect(); 649 assert_eq!(ids, ["act", "undo", "load"]); 650 // What "the command does" means is in the state, once. 651 assert_eq!(prepared.request.matches("Judge only what the shell would execute").count(), 1); 652 }
Reading, building and editing, undone by one command at most, run without a prompt: this is the plugin's whole benefit on an ordinary day.
656 #[test] 657 fn ordinary_work_is_allowed() { 658 assert_eq!(verdict(&surely(Act::Read), 0.0, 1.0, Some(8000.0)).0, Verdict::Allow); 659 assert_eq!(verdict(&surely(Act::Build), 1.0, 1.0, Some(8000.0)).0, Verdict::Allow); 660 // A commit, or a note written in another repository. 661 assert_eq!(verdict(&surely(Act::Edit), 1.0, 1.0, Some(8000.0)).0, Verdict::Allow); 662 }
Each of the five consequential acts stops for the user, even when it would be easy to undo.
The undo score is a second, independent reason to ask: a harmless-sounding act that cannot be put back still stops.
Jev not knowing is a Pass: the user's own permission rules decide, as if the plugin were
not there.
The bug this fixed: summing a thin spread over several consequential acts stopped a command Jev was merely unsure about.
With no room and nobody to wait for, a heavy command is put to the user, and the line says what it needs and what there is. No figure means no opinion.
698 #[test] 699 fn a_heavy_command_is_put_to_the_user_when_memory_is_short() { 700 // A build with 1.5 GB available: a heavy command wants 3 GB. 701 let (verdict_, line) = verdict(&surely(Act::Build), 1.0, 2.0, Some(1500.0)); 702 assert_eq!(verdict_, Verdict::Ask); 703 assert_eq!(line, "Jev: a heavy command (2.0 of 3) with 1.5 GB of memory to spare"); 704 // The same build with room to spare runs. 705 assert_eq!(verdict(&surely(Act::Build), 1.0, 2.0, Some(8000.0)).0, Verdict::Allow); 706 // A light command is not held up by the same shortage. 707 assert_eq!(verdict(&surely(Act::Edit), 1.0, 0.3, Some(1500.0)).0, Verdict::Allow); 708 // Where memory cannot be measured, load is not judged. 709 assert_eq!(verdict(&surely(Act::Build), 1.0, 3.0, None).0, Verdict::Allow); 710 }
The same shortage with patience left is a wait, not a question: and a command that would be put to the user anyway is never kept waiting, and is put with both reasons.
714 #[test] 715 fn a_command_with_no_room_waits_unless_it_is_risky_or_has_waited() { 716 let build = Asked::sure_of(Act::Build, 1.0, 2.0); 717 assert_eq!(build.settle(&network(), Some(1500.0), true).0, Verdict::Hold); 718 assert_eq!(build.settle(&network(), Some(1500.0), false).0, Verdict::Ask); 719 let push = Asked::sure_of(Act::History, 2.3, 2.0); 720 let (verdict, line) = push.settle(&network(), Some(1500.0), true); 721 assert_eq!(verdict, Verdict::Ask); 722 assert_eq!( 723 line, 724 "Jev: discards or rewrites version-control state (100%); hard to undo (2.3 of 3); a heavy command (2.0 of 3) with 1.5 GB of memory to spare" 725 ); 726 }
A command with no room and nothing known about its patience is put to the user: only a command known to have patience left waits.
Room is measured before anything ends, whatever Jev said: the rule that measures is first.
A load between two levels needs memory between theirs, so a score of 2.5 is not treated as a 2 or a 3.
The line is what the user reads in the permission prompt and the band: it must say what Jev thought and how sure it was.
A label is what Jev answers with and what is matched back; two acts sharing one would silently merge their probabilities.
A rule lights up in the drawing when it holds, so no rule may hold for an answer it is not about: for ordinary, easily undone work with room, the rule that allows is the only one that ends it. (The catch-all this replaced held for every answer.)
775 #[test] 776 fn only_the_rule_that_applies_holds() { 777 let mut known = Asked::sure_of(Act::Build, 1.0, 1.0).known; 778 known.learn(Fact::Room, Value::Enough); 779 known.learn(Fact::Patience, Value::Left); 780 assert_eq!(network().holding(&known), ["look for room", "ordinary and easily undone"]); 781 }
The rules name only values their facts can have, and every rule has a name of its own: what a loader would refuse in a file is refused here for the rules in code.
785 #[test] 786 fn every_rule_tests_a_value_its_fact_can_have() { 787 for rule in RULES { 788 for test in rule.when { 789 if let Test::Is(fact, value) = test { 790 assert!(Command::values(*fact).contains(value), "{}: {fact:?} cannot be {value:?}", rule.name); 791 } 792 } 793 assert_eq!(RULES.iter().filter(|other| other.name == rule.name).count(), 1, "{}", rule.name); 794 } 795 } 796}