From a1370cf4246aba2fb66225e87c3f9fe927f94182 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 7 Nov 2026 03:16:27 +0100 Subject: [PATCH] persona: a reply that cannot be placed is a text-free pointer in the public session A reply that answers no brief, or briefs of two conversations, was read by nothing and re-paged on every Persona read. It is now a route in the public conversation marked as a pointer: the line carries the reply id and a fixed note, never its text, and the Persona reads the reply by id. Heard, it is resolved like any reply. Co-Authored-By: Claude Sonnet 5.5 --- crates/mind-body/src/persona.rs | 29 +++++++++++++++----- crates/mind-body/src/persona/tests.rs | 39 ++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/crates/mind-body/src/persona.rs b/crates/mind-body/src/persona.rs index b687a85..ae070a2 100644 --- a/crates/mind-body/src/persona.rs +++ b/crates/mind-body/src/persona.rs @@ -962,8 +962,8 @@ impl Conversation { } /// The conversation a reply is told in: the one conversation its briefs' origins name, or none when they name two or -/// it answers no brief (a result the Self could not attribute). Such a reply is told in no session, so no member's words -/// reach another's room, and it stays in force. +/// it answers no brief (a result the Self could not attribute). Such a reply has no conversation: the public session is told a +/// pointer to it without its text, so no member's words reach another's room. fn reply_conversation(answers: &[Option]) -> Option { let mut conversations = answers.iter().map(|origin| Conversation::of_origin(origin.as_ref())); let first = conversations.next()?; @@ -979,10 +979,12 @@ struct Route { conversation: Conversation, /// The origin of the first brief the reply answers that has one: what a session opened for the reply alone records. origin: Option, + /// The reply could not be placed in one conversation (it answers none, or briefs of two): the public session is told a pointer to it, never its text. + pointer: bool, } /// Her in-force replies this process has not told, oldest first, each with its conversation: one selection of the replies and -/// one keyed selection of the briefs they answer. A reply whose briefs name two conversations is left out: no session tells it. +/// one keyed selection of the briefs they answer. A reply with no one conversation is a pointer route in the public one. fn reply_routes(mind: &dyn MindPort, told: &BTreeSet) -> Result> { let replies: Vec<_> = headers(mind, &in_force(any_of(of_kind(PipelineKind::Reply), "root", &[&mind.instance().0])))?.into_iter().filter(|header| !told.contains(&header.id.id.0)).collect(); let mut keys: Vec = replies @@ -1006,12 +1008,13 @@ fn reply_routes(mind: &dyn MindPort, told: &BTreeSet) -> Result> = answers.iter().map(|brief| origins.get(&brief.id.0).cloned().flatten()).collect(); - let Some(conversation) = reply_conversation(&of_answers) else { continue }; + let placed = reply_conversation(&of_answers); routes.push(Route { id: header.id.id.0.clone(), ordinal: header.admission.ordinal, at_unix_ms: instant_ms(&header.admission.admitted_at).unwrap_or(0), - conversation, + pointer: placed.is_none(), + conversation: placed.unwrap_or(Conversation::Public), origin: of_answers.into_iter().flatten().next(), }); } @@ -1123,11 +1126,19 @@ struct HeardReply { /// The briefs it answers, each with its repo (empty when the brief's repo could not be read). answers: Vec<(String, String)>, text: String, + /// A pointer to a reply that could not be placed: it holds no answers and no text, only the id. + pointer: bool, } +/// What a pointer line says, in the organ's own words: the reply is in the mind under its id, and nothing of it is in this line. +const UNPLACED_NOTE: &str = "Your Self finished something it could not place in any one conversation. Its words are not here: read the reply by this id, privately, and decide where it belongs."; + /// A reply as one line: `role=self` is the organ's word that it is her Self's, and every field a Self wrote is quoted, so nothing in /// it can end the line or pass for a message, a notice or a field. fn reply_line(reply: &HeardReply) -> String { + if reply.pointer { + return format!("reply role=self at={} id={} unplaced=true note={}\n", at_text(reply.at_unix_ms), quoted(&reply.id), quoted(UNPLACED_NOTE)); + } let answers = reply .answers .iter() @@ -1951,8 +1962,12 @@ fn decode_reply(text: &str, rooms: &[String], people: &[String], open: &[String] /// document cannot be read is left for the next read. Only the routes of the session's own conversation are given. fn unheard_replies(mind: &dyn MindPort, routes: &[Route]) -> Result> { let mut found = Vec::new(); - for Route { id, ordinal, .. } in routes { + for Route { id, ordinal, at_unix_ms, pointer, .. } in routes { let (id, ordinal) = (id.clone(), *ordinal); + if *pointer { + found.push(HeardReply { id, ordinal, at_unix_ms: *at_unix_ms, answers: Vec::new(), text: String::new(), pointer: true }); + continue; + } let one = Selection { keys: Some(vec![id.clone()]), projection: "document".into(), ..Selection::default() }; let PipelinePageItems::Documents(views) = mind.query(&one)?.items else { bail!("the mind answered headers to a document selection") }; let Some(view) = views.into_iter().next() else { continue }; @@ -1967,7 +1982,7 @@ fn unheard_replies(mind: &dyn MindPort, routes: &[Route]) -> Result Route { - Route { id: id.into(), ordinal: at_unix_ms, at_unix_ms, conversation, origin: None } + Route { id: id.into(), ordinal: at_unix_ms, at_unix_ms, conversation, origin: None, pointer: false } } fn at_ms(delivery: ChatDelivery, at_unix_ms: u64) -> ChatDelivery { @@ -6542,22 +6542,47 @@ fn a_reply_to_a_public_brief_or_a_brief_with_no_origin_is_told_in_the_public_ses for words in ["old-brief words", "public-brief words"] { assert_eq!(all.contains(words), public, "{words} is told in the public session only: {all}"); } - assert!(!all.contains("no-brief words"), "a reply the Self could not attribute is told in no session: {all}"); + assert!(!all.contains("no-brief words"), "a reply the Self could not attribute puts no text in any session: {all}"); } } +/// The inputs of the sessions that were told a pointer, and every input the model was given. +fn pointed_inputs(rig: &Rig) -> (Vec, Vec) { + let all: Vec = rig.model.requests.borrow().iter().map(|told| told.input.clone()).collect(); + (all.iter().filter(|input| input.contains("unplaced=true")).cloned().collect(), all) +} + #[test] -fn a_reply_whose_briefs_name_two_conversations_is_told_in_no_session_and_stays_in_force() { +fn a_reply_whose_briefs_name_two_conversations_is_a_text_free_pointer_in_the_public_session_only() { assert_eq!(reply_conversation(&[Some(dm_origin(AQUA_DM, "@a:chat")), Some(dm_origin(LIBBY_DM, "@l:chat"))]), None); assert_eq!(reply_conversation(&[Some(dm_origin(AQUA_DM, "@a:chat")), None]), None); let pending = vec![direct(1, AQUA_DM, "@aqua:chat", AQUA_ASK), direct(2, LIBBY_DM, "@libby:chat", "libby asks about cedar"), delivery(3, "swarm", "@m:chat", "public asks about oak")]; - let rig = Rig::new(pending, &[says(&[]), says(&[]), says(&[])]); + let rig = Rig::new(pending, &[says(&[]), says(&[]), says(&[]), says(&[])]); rig.mind.committed(briefs_in_every_room()); rig.mind.committed(vec![self_reply_to("r-both", &["ask-aqua", "ask-libby"], "both-rooms words")]); assert_eq!(failure_of(rig.go()), None); - assert_eq!(rig.model.spawns.borrow().len(), 3, "only the deliveries' sessions"); - assert!(!rig.model.requests.borrow().iter().any(|told| told.input.contains("both-rooms words"))); - assert!(reply_in_force(&rig.mind, &reply_id("r-both")).unwrap()); + let (pointed, all) = pointed_inputs(&rig); + assert_eq!(pointed.len(), 1, "one pointer, in one session: {all:?}"); + assert!(pointed[0].contains(&reply_id("r-both")), "{}", pointed[0]); + assert!(!pointed[0].contains(AQUA_ASK) && !pointed[0].contains("libby asks about cedar"), "no direct message in the pointer's session: {}", pointed[0]); + assert!(!all.iter().any(|input| input.contains("both-rooms words")), "no reply text reaches any line"); + // The Persona reads it by id: the reply document holds the words the line does not. + let PipelineDocument::Reply(reply) = rig.mind.view(K::Reply, &reply_id("r-both")).unwrap().document else { panic!("a reply") }; + assert_eq!(reply.text.0, "both-rooms words"); + assert!(!reply_in_force(&rig.mind, &reply_id("r-both")).unwrap(), "heard once, it no longer re-pages on every read"); +} + +#[test] +fn a_reply_that_answers_no_brief_is_a_text_free_pointer_with_its_id() { + let rig = Rig::new(vec![delivery(1, "swarm", "@m:chat", "public asks about oak")], &[says(&[]), says(&[])]); + rig.mind.committed(briefs_in_every_room()); + rig.mind.committed(vec![self_reply_to("r-none", &[], "stranded words")]); + assert_eq!(failure_of(rig.go()), None); + let (pointed, all) = pointed_inputs(&rig); + assert_eq!(pointed.len(), 1, "{all:?}"); + assert!(pointed[0].contains(&reply_id("r-none")) && pointed[0].contains("could not place"), "{}", pointed[0]); + assert!(!all.iter().any(|input| input.contains("stranded words"))); + assert!(!reply_in_force(&rig.mind, &reply_id("r-none")).unwrap()); } #[test]