1//! What happens to one thing the child says, as data: the steps a turn takes, where Jev is asked 2//! something, and where a turn can end in a fixed line instead of an answer. The website draws its 3//! diagram from this, and `tests/conversation.rs` runs the real pipeline into every exit listed 4//! here, so the picture cannot show a step or an ending the code does not have. 5//! 6//! This is a description of `Conversation::respond`, not the thing that runs it: the order of the 7//! steps is the order of the code, and a test that disagrees fails. 8 9use crate::types::{Direction, Fallback}; 10 11/// Where a step happens. 12#[derive(Clone, Copy, Debug, PartialEq, Eq)] 13pub enum Place { 14 /// On the child's tablet, in the Rust core. 15 Tablet, 16 /// The language model, through the household's gateway. 17 Model, 18 /// Jev, asked by the service that holds the key. 19 Jev, 20} 21 22/// What Jev is asked at a step. 23#[derive(Clone, Copy, Debug, PartialEq, Eq)] 24pub struct JevCheck { 25 /// Which way the message travels past the guard; `None` for a question that is not a check. 26 pub direction: Option<Direction>, 27 /// The question, in plain words. 28 pub asks: &'static str, 29} 30 31/// A way a step can end the turn, and what the child hears instead. 32#[derive(Clone, Copy, Debug, PartialEq, Eq)] 33pub struct Exit { 34 /// Why, in plain words. Unique within its step: the tests find the scenario by it. 35 pub cause: &'static str, 36 pub then: Fallback, 37 /// What the parents' log shows. 38 pub parents_see: &'static str, 39} 40 41#[derive(Clone, Copy, Debug, PartialEq, Eq)] 42pub struct Step { 43 pub id: &'static str, 44 pub place: Place, 45 pub what: &'static str, 46 pub jev: Option<JevCheck>, 47 /// Whether the step happens only when the child showed a picture. 48 pub only_with_pictures: bool, 49 pub exits: &'static [Exit], 50 /// What the step writes in the parents' log when it passes, as the event's name. 51 pub logs: &'static str, 52} 53 54const REFUSED: Exit = Exit { cause: "refused", then: Fallback::ChildRefused, parents_see: "the refusal and its reason" }; 55const GROWN_UP: Exit = Exit { 56 cause: "refused: she is hurt, unsafe or asked to keep a secret", 57 then: Fallback::NeedsAGrownUp, 58 parents_see: "the refusal, shown first", 59}; 60const JEV_DOWN: Exit = Exit { cause: "Jev cannot be reached", then: Fallback::GuardUnavailable, parents_see: "a failure at the check" }; 61 62/// The turn, in order. 63pub const TURN: [Step; 9] = [ 64 Step { 65 id: "keep-pictures", 66 place: Place::Tablet, 67 what: "Any picture she shows is kept before anything is sent anywhere.", 68 jev: None, 69 only_with_pictures: true, 70 exits: &[Exit { cause: "a picture cannot be kept", then: Fallback::LogUnavailable, parents_see: "nothing: the log is what broke" }], 71 logs: "", 72 }, 73 Step { 74 id: "log-heard", 75 place: Place::Tablet, 76 what: "What she said is written to the parents' log, before the guard or the model sees it.", 77 jev: None, 78 only_with_pictures: false, 79 exits: &[Exit { cause: "the log cannot be written", then: Fallback::LogUnavailable, parents_see: "nothing: the log is what broke" }], 80 logs: "Heard", 81 }, 82 Step { 83 id: "check-words", 84 place: Place::Jev, 85 what: "Jev is asked whether what she said is fit for a child of her age to say to the cat.", 86 jev: Some(JevCheck { direction: Some(Direction::FromChild), asks: "May this be said?" }), 87 only_with_pictures: false, 88 exits: &[REFUSED, GROWN_UP, JEV_DOWN], 89 logs: "Guarded", 90 }, 91 Step { 92 id: "describe-picture", 93 place: Place::Model, 94 what: "A picture is first described in plain words by a model call that has no persona.", 95 jev: None, 96 only_with_pictures: true, 97 exits: &[Exit { cause: "the model cannot be reached", then: Fallback::ModelUnavailable, parents_see: "a failure at the model" }], 98 logs: "PictureSeen", 99 }, 100 Step { 101 id: "check-picture", 102 place: Place::Jev, 103 what: "Jev reads only words, so the description is judged exactly as her speech is.", 104 jev: Some(JevCheck { direction: Some(Direction::FromChild), asks: "May this be said?" }), 105 only_with_pictures: true, 106 exits: &[REFUSED, GROWN_UP, JEV_DOWN], 107 logs: "Guarded", 108 }, 109 Step { 110 id: "recall", 111 place: Place::Jev, 112 what: "What the cat remembers is searched by meaning. When more than four memories fit, Jev orders them; fewer, and nothing is asked. This only adds: it never ends a turn.", 113 jev: Some(JevCheck { direction: None, asks: "Which memory helps most?" }), 114 only_with_pictures: false, 115 exits: &[], 116 logs: "Recalled", 117 }, 118 Step { 119 id: "model-writes", 120 place: Place::Model, 121 what: "The model writes a reply, from the parents' prompt, the memories and a window of the chat.", 122 jev: None, 123 only_with_pictures: false, 124 exits: &[ 125 Exit { cause: "the model cannot be reached", then: Fallback::ModelUnavailable, parents_see: "a failure at the model" }, 126 Exit { cause: "the allowance for thinking is used up", then: Fallback::Resting, parents_see: "a failure at the model, said as a rest" }, 127 ], 128 logs: "ModelWrote", 129 }, 130 Step { 131 id: "check-answer", 132 place: Place::Jev, 133 what: "Jev is asked whether what the model wrote is fit for her to hear.", 134 jev: Some(JevCheck { direction: Some(Direction::ToChild), asks: "May this be heard?" }), 135 only_with_pictures: false, 136 exits: &[ 137 Exit { cause: "refused", then: Fallback::AnswerRefused, parents_see: "what the model wrote, and the refusal" }, 138 JEV_DOWN, 139 ], 140 logs: "Guarded", 141 }, 142 Step { 143 id: "say", 144 place: Place::Tablet, 145 what: "The tablet speaks the answer.", 146 jev: None, 147 only_with_pictures: false, 148 exits: &[], 149 logs: "Said", 150 }, 151]; 152 153/// Every place the app asks Jev something, in the code. Three are steps of a turn; the others 154/// (a fact to remember, a chat summary) happen after it, off to the side (`reflect.rs`). 155#[derive(Clone, Copy, Debug, PartialEq, Eq)] 156pub struct JevUse { 157 pub id: &'static str, 158 pub asks: &'static str, 159 /// What happens when Jev cannot answer. 160 pub on_failure: &'static str, 161} 162 163pub const JEV_USES: [JevUse; 6] = [ 164 JevUse { id: "check-words", asks: "Is what she said fit for her age, and what is it about?", on_failure: "refused: the cat says a fixed line" }, 165 JevUse { id: "check-picture", asks: "The same two questions, of the words that describe her picture.", on_failure: "refused: the cat says a fixed line" }, 166 JevUse { id: "check-answer", asks: "The same two questions, of what the model wrote.", on_failure: "refused: she hears a fixed line instead" }, 167 JevUse { id: "check-fact", asks: "The same two questions, of a fact the cat is about to remember about her.", on_failure: "the fact is not kept, and the log says so" }, 168 JevUse { id: "check-summary", asks: "The same two questions, of the summary that stands in for a long chat.", on_failure: "the chat is left as it was" }, 169 JevUse { id: "rank-memories", asks: "Which of these remembered things helps most with what she just said?", on_failure: "the cat remembers by meaning alone" }, 170]; 171 172/// Things that are not Jev, so the page does not credit it with them. 173pub const NOT_JEV: [(&str, &str); 6] = [ 174 ("Writing the cat's words", "The language model does, through the household's gateway. Jev never writes text."), 175 ("Understanding speech", "The tablet turns her voice into text before anything else happens."), 176 ("Reading pictures", "A model call describes a picture in words; Jev only judges those words."), 177 ("Finding memories by meaning", "A separate embedding model on the household's own machine turns text into vectors; Jev only orders the shortlist."), 178 ("The allowance and the bedtime", "Plain rules in the core: minutes a day and quiet hours."), 179 ("The parents' log", "Written by the core before each step. Jev is not asked to remember anything."), 180]; 181 182#[cfg(test)] 183mod tests { 184 use super::*; 185 186 #[test] 187 fn ids_are_unique_and_causes_are_unique_within_a_step() { 188 for (i, a) in TURN.iter().enumerate() { 189 assert!(TURN.iter().skip(i + 1).all(|b| b.id != a.id), "{}", a.id); 190 for (j, x) in a.exits.iter().enumerate() { 191 assert!(a.exits.iter().skip(j + 1).all(|y| y.cause != x.cause), "{} {}", a.id, x.cause); 192 } 193 } 194 } 195 196 #[test] 197 fn every_fixed_line_the_cat_can_say_is_an_exit_of_some_step() { 198 for fallback in Fallback::ALL { 199 assert!(TURN.iter().any(|s| s.exits.iter().any(|e| e.then == fallback)), "{fallback:?} ends no step"); 200 } 201 } 202 203 #[test] 204 fn a_step_that_asks_jev_is_placed_at_jev_and_the_others_are_not() { 205 for step in TURN { 206 assert_eq!(step.jev.is_some(), step.place == Place::Jev, "{}", step.id); 207 } 208 } 209 210 #[test] 211 fn every_jev_step_of_the_turn_is_a_use_and_every_use_has_a_failure() { 212 for step in TURN.iter().filter(|s| s.jev.is_some()) { 213 let id = if step.id == "recall" { "rank-memories" } else { step.id }; 214 assert!(JEV_USES.iter().any(|u| u.id == id), "{}", step.id); 215 } 216 assert!(JEV_USES.iter().all(|u| !u.on_failure.is_empty())); 217 } 218}