From 6aedd5415562cca87c1da4e5f3fb76cd3c2db7c4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 7 Nov 2026 01:03:46 +0100 Subject: [PATCH] mind-body: the committed surface follows HeartbeatView and Addressing; the responsibilities list says which chat names are exact; a test's direct message does not name a Mind Co-Authored-By: Claude Sonnet 5.5 --- crates/mind-body/src/persona.rs | 11 ++++++++--- crates/mind-body/src/persona/tests.rs | 9 +++++---- crates/mind-body/tests/public_surface.txt | 11 +++++++---- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/crates/mind-body/src/persona.rs b/crates/mind-body/src/persona.rs index 5271917..5b3fc15 100644 --- a/crates/mind-body/src/persona.rs +++ b/crates/mind-body/src/persona.rs @@ -2169,9 +2169,14 @@ fn render_responsibilities(mind: &dyn MindPort, instance: &Slug, names: &BTreeMa for steward in &stewards { if let PipelineFacts::Stewardship { repo, note, names: listed, .. } = &steward.facts { let chat = project_names(repo, listed); - let words = chat.iter().map(|name| name.text.as_str()).collect::>().join(", "); - let how = if listed.is_empty() { "any case" } else { "exactly as written" }; - out.push_str(&format!("- steward of {} (chat names, {how}: {words}): {}\n", repo.0, note.0)); + let how: Vec = [(true, "exactly as written"), (false, "any case")] + .into_iter() + .filter_map(|(exact, how)| { + let words = chat.iter().filter(|name| name.exact == exact).map(|name| name.text.as_str()).collect::>().join(", "); + (!words.is_empty()).then(|| format!("{how}: {words}")) + }) + .collect(); + out.push_str(&format!("- steward of {} (chat names, {}): {}\n", repo.0, how.join("; "), note.0)); chat_names.extend(chat); if !repos.contains(repo) { repos.push(repo.clone()); diff --git a/crates/mind-body/src/persona/tests.rs b/crates/mind-body/src/persona/tests.rs index 7dbdb53..f51d7f0 100644 --- a/crates/mind-body/src/persona/tests.rs +++ b/crates/mind-body/src/persona/tests.rs @@ -1187,7 +1187,7 @@ fn a_turn_shows_her_stewardships_follow_ups_and_the_rulings_they_cite() { let input = first_input(&rig); assert!(input.contains("What you are responsible for"), "{input}"); assert!(input.contains("Open follow-ups, newest first:"), "{input}"); - assert!(input.contains("- steward of GameCult/cinny (chat names, any case: cinny): the chat client; design law in eureka-body:ruling:client-first."), "{input}"); + assert!(input.contains("- steward of GameCult/cinny (chat names, exactly as written: cinny): the chat client; design law in eureka-body:ruling:client-first."), "{input}"); let (newer, older) = (input.find("eureka-body/newer").unwrap(), input.find("eureka-body/older").unwrap()); assert!(newer < older, "{input}"); assert!(input.contains("Ship the other thing.") && !input.contains("Second line."), "{input}"); @@ -1212,7 +1212,7 @@ fn her_responsibilities_show_each_stewardships_chat_names_and_the_line_on_changi let rig = Rig::new(vec![], &[]); rig.mind.committed(vec![steward_naming("GameCult/BadDaySimulator", &[]), steward_naming("GameCult/Thing", &["Thing"])]); let text = render_responsibilities(&rig.mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text; - assert!(text.contains("- steward of GameCult/BadDaySimulator (chat names, any case: baddaysimulator, bad day simulator): the project"), "{text}"); + assert!(text.contains("- steward of GameCult/BadDaySimulator (chat names, exactly as written: BadDaySimulator; any case: bad day simulator): the project"), "{text}"); assert!(text.contains("- steward of GameCult/Thing (chat names, exactly as written: Thing): the project"), "{text}"); assert_eq!(text.matches("These names address you in chat as a tag does. To change a project's names, admit its stewardship's next sequence with `names`").count(), 1, "{text}"); let last_steward = text.rfind("- steward of").unwrap(); @@ -2229,7 +2229,8 @@ fn a_dm_memory_names_its_confidant_not_its_subject() { fn about_can_name_an_absent_mind() { let (libby, op) = ("@libby:chat", "@op:chat"); let decided = reply(&[("!dm-op:chat", "ok")], json!([keeps("Decided", "Libby does the shaders for the Cinny fork", &[libby])]), json!([]), json!(null)); - let rig = Rig::new(vec![direct(1, "!dm-op:chat", op, "Libby will do the shaders for the Cinny fork")], &[decided, says(&[("swarm", "go")])]); + let dm = ChatDelivery { is_direct: true, ..delivery_of(1, "!dm-op:chat", op, "Libby will do the shaders for the Cinny fork", "$dm-libby") }; + let rig = Rig::new(vec![dm], &[decided, says(&[("swarm", "go")])]); assert_eq!(failure_of(rig.go()), None); assert_eq!(rig.sent_texts().len(), 1); assert_eq!(rig.chat.acked.borrow().len(), 1); @@ -3789,7 +3790,7 @@ fn a_small_machine() -> Rig { /// What project-name-addresses-steward made of the base: the stewardship lines show their chat names and one line says what they do. fn with_chat_names(base: &str) -> String { - let mut lines: Vec = base.lines().map(|line| line.replace("- steward of GameCult/Huginn: ", "- steward of GameCult/Huginn (chat names, any case: huginn): ").replace("- steward of GameCult/cinny: ", "- steward of GameCult/cinny (chat names, any case: cinny): ")).collect(); + let mut lines: Vec = base.lines().map(|line| line.replace("- steward of GameCult/Huginn: ", "- steward of GameCult/Huginn (chat names, exactly as written: Huginn): ").replace("- steward of GameCult/cinny: ", "- steward of GameCult/cinny (chat names, exactly as written: cinny): ")).collect(); let last = lines.iter().rposition(|line| line.starts_with("- steward of")).unwrap(); lines.insert(last + 1, "These names address you in chat as a tag does. To change a project's names, admit its stewardship's next sequence with `names` and a resolution superseding the previous record; names you list replace the derived ones and match exactly as you write them, so list each casing you want.".into()); lines.join("\n") + "\n" diff --git a/crates/mind-body/tests/public_surface.txt b/crates/mind-body/tests/public_surface.txt index bc0f3cf..e0870c3 100644 --- a/crates/mind-body/tests/public_surface.txt +++ b/crates/mind-body/tests/public_surface.txt @@ -11,6 +11,8 @@ mind_body: mod queue mind_body: mod self_run mind_body: mod tempo mind_body::address: impl Addressee +mind_body::address: struct Addressee # [derive (Clone , Debug , PartialEq , Eq , serde :: Serialize , serde :: Deserialize)] pub-fields[] +mind_body::address: struct Name # [derive (Clone , Debug , PartialEq , Eq , serde :: Serialize , serde :: Deserialize)] pub-fields[] mind_body::chat_client: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum Refusal { Brake , PersonaMismatch , UnknownRoom , EmptyText , SendFailed , UnknownPersona , RoomMoved , } mind_body::chat_client: const ACK : & str mind_body::chat_client: const PENDING : & str @@ -89,16 +91,17 @@ mind_body::heartbeat: impl Control for FileControl mind_body::heartbeat: impl FileStore mind_body::heartbeat: impl FileStore: fn check (& self) -> Result < () > mind_body::heartbeat: impl FileStore: fn new (path : impl Into < PathBuf >) -> Self +mind_body::heartbeat: impl HeartbeatView for FileStore mind_body::heartbeat: impl PersonaId mind_body::heartbeat: impl PersonaId: fn parse (spec : & str) -> Result < Self > mind_body::heartbeat: impl Row < '_ > mind_body::heartbeat: impl Store for FileStore -mind_body::heartbeat: impl TempoView for FileStore mind_body::heartbeat: impl fmt :: Display for Outcome mind_body::heartbeat: impl fmt :: Display for PollTrace mind_body::heartbeat: pub trait Control { fn envelope (& self) -> SwarmEffective ; fn effective (& self , instance : & Slug) -> Effective ; } -mind_body::heartbeat: pub trait Store { fn load (& self) -> Result < Option < HeartbeatState > > ; fn save (& self , state : & HeartbeatState) -> Result < () > ; fn record (& self , trace : & PollTrace) -> Result < () > ; } -mind_body::heartbeat: pub trait TempoView { fn latest (& self) -> Result < Option < PollTrace > > ; } +mind_body::heartbeat: pub trait HeartbeatView { fn latest (& self) -> Result < Option < PollTrace > > ; fn addressing (& self) -> Result < Option < Addressing > > ; } +mind_body::heartbeat: pub trait Store { fn load (& self) -> Result < Option < HeartbeatState > > ; fn save (& self , state : & HeartbeatState) -> Result < () > ; fn record (& self , trace : & PollTrace) -> Result < () > ; fn load_addressing (& self) -> Result < Option < Addressing > > ; fn save_addressing (& self , addressing : & Addressing) -> Result < () > ; } +mind_body::heartbeat: struct Addressing # [derive (Clone , Debug , Default , PartialEq , Eq , DatabaseEntry)] pub-fields[minds: Vec < Addressee >] mind_body::heartbeat: struct Config # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[personas: Vec < PersonaId >, operators: Vec < String >] mind_body::heartbeat: struct FileControl pub-fields[] mind_body::heartbeat: struct FileStore pub-fields[] @@ -191,7 +194,7 @@ mind_body::persona: pub use model_connector :: PlanQuota ; mind_body::persona: struct Config # [derive (Clone , Debug , PartialEq , Eq)] pub-fields[rooms: Vec < String >, operators: Vec < String >, minds: Vec < String >, own_sender: String, model: String, doctrine: Vec < PathBuf >] mind_body::persona: struct FileTrace pub-fields[] mind_body::persona: struct HuginnPersona pub-fields[] -mind_body::persona: struct Organs< 'a > pub-fields[mind: & 'a dyn MindPort, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, chat: & 'a dyn Chat, persona: & 'a dyn PersonaState, trace: & 'a dyn Trace, tempo: Option < & 'a dyn TempoView >, lender: & 'a dyn Lender, spawner: & 'a dyn Spawner, run_cap_s: u64, sandbox: & 'a Path, scratch: & 'a Path] +mind_body::persona: struct Organs< 'a > pub-fields[mind: & 'a dyn MindPort, launcher: & 'a dyn Launcher, clock: & 'a dyn Clock, host: & 'a str, chat: & 'a dyn Chat, persona: & 'a dyn PersonaState, trace: & 'a dyn Trace, heartbeat: Option < & 'a dyn HeartbeatView >, lender: & 'a dyn Lender, spawner: & 'a dyn Spawner, run_cap_s: u64, sandbox: & 'a Path, scratch: & 'a Path] mind_body::persona: struct PersonaReply # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[intent: String, say: Vec < Say >, start_self_run: Option < StartSelfRun >, remember: Vec < Remember >, settle: Vec < Settle >, tempo_vote_beat_s: Option < Option < u32 > >, urgency_permille: Option < u16 >] mind_body::persona: struct Remember # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[sort: MemorySort, text: String, about: Vec < String >, salience_permille: u16] mind_body::persona: struct Say # [derive (Clone , Debug , PartialEq , Eq , Serialize , Deserialize)] pub-fields[room: String, text: String]