address: a one-word repo name matches as the repo spells it, a split phrase in any case; a self-overlapping name is found; stagger test helpers search a bounded range

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-07 00:53:34 +01:00
commit 0d2f4ec216
3 changed files with 59 additions and 25 deletions

View file

@ -1,8 +1,8 @@
//! Whether a chat delivery is for a Mind: the one rule the heartbeat's off-beat wake and beat score and the Persona's `to_you` and
//! stagger rank all call. A delivery addresses a Mind when its `m.mentions` holds her Matrix id (a tag), or its text holds one of her
//! names as a whole word. Her names are her own (handle, public name; case does not matter) and her projects': the names her
//! stewardship records list, matched exactly as written, or, when a record lists none, the repo's name and its split form, matched in
//! any case. A deterministic identity match on names, not a reading of meaning.
//! stewardship records list, matched exactly as written, or, when a record lists none, the repo's name matched exactly as the repo
//! spells it and, for a multi-word name, its split form matched in any case. A deterministic identity match on names, not a reading of meaning.
use eureka_pipeline::{OrgRepo, Short};
@ -52,8 +52,9 @@ pub(crate) fn addresses(who: &Addressee, delivery: &ChatDelivery) -> bool {
}
/// The chat names of one stewarded repo. Listed names are exactly those, each matched exactly as the steward wrote it (capitalization
/// tells a project from a common word). With none listed: the repo's name after the org, and that name split at `-`, `_`, `.` and where a
/// lower-case letter or digit meets a capital, when the split reads differently; both match in any case.
/// tells a project from a common word). With none listed: the repo's name after the org, matched exactly as the repo spells it
/// (`Thing`, not `thing`), and that name split at `-`, `_`, `.` and where a lower-case letter or digit meets a capital, when the split
/// reads differently, matched in any case (a split phrase is not a common word).
pub(crate) fn project_names(repo: &OrgRepo, listed: &[Short]) -> Vec<Name> {
if !listed.is_empty() {
return listed.iter().map(|name| Name { text: name.0.clone(), exact: true }).collect();
@ -72,9 +73,9 @@ pub(crate) fn project_names(repo: &OrgRepo, listed: &[Short]) -> Vec<Name> {
}
previous = Some(ch);
}
let (whole, split) = (whole.to_lowercase(), split.to_lowercase());
let mut names = vec![Name { text: whole.clone(), exact: false }];
if split != whole {
let split = split.to_lowercase();
let mut names = vec![Name { text: whole.to_string(), exact: true }];
if split != whole.to_lowercase() {
names.push(Name { text: split, exact: false });
}
names
@ -87,11 +88,18 @@ pub(crate) fn names_whole_word(text: &str, name: &str, exact: bool) -> bool {
if name.is_empty() {
return false;
}
text.match_indices(&name).any(|(start, _)| {
let mut from = 0;
while let Some(found) = text[from..].find(&name) {
let start = from + found;
let before = text[..start].chars().next_back();
let after = text[start + name.len()..].chars().next();
!before.is_some_and(char::is_alphanumeric) && !after.is_some_and(char::is_alphanumeric)
})
if !before.is_some_and(char::is_alphanumeric) && !after.is_some_and(char::is_alphanumeric) {
return true;
}
// Step one character, not one name: matches may overlap ("ab ab" in "xab ab ab").
from = start + text[start..].chars().next().map_or(1, char::len_utf8);
}
false
}
#[cfg(test)]

View file

@ -1,4 +1,4 @@
//! The one addressing rule: a tag, or a name as a whole word; a listed name exactly as written, a derived or a Mind's own in any case.
//! The one addressing rule: a tag, or a name as a whole word; a listed name exactly as written, a derived one-word repo name as the repo spells it, a split phrase or a Mind's own in any case.
use super::*;
@ -42,28 +42,42 @@ fn a_tag_her_name_or_one_of_her_project_names_as_a_whole_word_addresses_her_and_
#[test]
fn project_names_derives_the_repo_name_and_its_split_form_and_listed_names_replace_them() {
let derived = |repo: &str| project_names(&OrgRepo(repo.into()), &[]).into_iter().map(|name| (name.text, name.exact)).collect::<Vec<_>>();
let folded = |texts: &[&str]| texts.iter().map(|text| ((*text).to_string(), false)).collect::<Vec<_>>();
assert_eq!(derived("GameCult/BadDaySimulator"), folded(&["baddaysimulator", "bad day simulator"]));
assert_eq!(derived("GameCult/moonlight-ios"), folded(&["moonlight-ios", "moonlight ios"]));
assert_eq!(derived("GameCult/Huginn"), folded(&["huginn"]));
assert_eq!(derived("GameCult/snake_case.rs"), folded(&["snake_case.rs", "snake case rs"]));
assert_eq!(derived("GameCult/Vili2D"), folded(&["vili2d", "vili2 d"]), "a digit before a capital splits");
assert_eq!(derived("GameCult/Vili2Dev"), folded(&["vili2dev", "vili2 dev"]));
let spelled = |whole: &str, split: &[&str]| {
let mut names = vec![(whole.to_string(), true)];
names.extend(split.iter().map(|text| ((*text).to_string(), false)));
names
};
assert_eq!(derived("GameCult/BadDaySimulator"), spelled("BadDaySimulator", &["bad day simulator"]));
assert_eq!(derived("GameCult/moonlight-ios"), spelled("moonlight-ios", &["moonlight ios"]));
assert_eq!(derived("GameCult/Huginn"), spelled("Huginn", &[]));
assert_eq!(derived("GameCult/Thing"), spelled("Thing", &[]));
assert_eq!(derived("GameCult/snake_case.rs"), spelled("snake_case.rs", &["snake case rs"]));
assert_eq!(derived("GameCult/Vili2D"), spelled("Vili2D", &["vili2 d"]), "a digit before a capital splits");
assert_eq!(derived("GameCult/Vili2Dev"), spelled("Vili2Dev", &["vili2 dev"]));
let listed = project_names(&OrgRepo("GameCult/BadDaySimulator".into()), &names(&["BDS", "Bad Day Simulator"]));
assert_eq!(listed.into_iter().map(|name| (name.text, name.exact)).collect::<Vec<_>>(), [("BDS".to_string(), true), ("Bad Day Simulator".to_string(), true)]);
}
#[test]
fn a_listed_name_matches_exactly_as_written_and_a_derived_name_in_any_case() {
fn a_listed_name_matches_exactly_as_written_a_derived_one_word_name_as_spelled_and_a_split_phrase_in_any_case() {
let libby = Addressee::new("@libby:chat", ["Libby"]).with_projects(project_names(&OrgRepo("GameCult/Thing".into()), &names(&["Thing"])));
assert!(addresses(&libby, &said("Thing is down", &[])));
assert!(!addresses(&libby, &said("one thing at a time", &[])));
assert!(!addresses(&libby, &said("THING", &[])));
let derived_thing = steward("GameCult/Thing", &[]);
assert!(addresses(&derived_thing, &said("Thing is down", &[])));
for text in ["one thing at a time", "THING", "a thing"] {
assert!(!addresses(&derived_thing, &said(text, &[])), "{text}");
}
let nibu = steward("GameCult/BadDaySimulator", &[]);
for text in ["BADDAYSIMULATOR is red", "Bad Day Simulator is red", "bad day simulator is red", "is BadDaySimulator red"] {
for text in ["BadDaySimulator is red", "BAD DAY SIMULATOR is red", "Bad Day Simulator is red", "bad day simulator is red"] {
assert!(addresses(&nibu, &said(text, &[])), "{text}");
}
assert!(!addresses(&nibu, &said("a bad day, simulated", &[])));
for text in ["baddaysimulator is red", "BADDAYSIMULATOR is red", "a bad day, simulated"] {
assert!(!addresses(&nibu, &said(text, &[])), "{text}");
}
let hyphen = steward("GameCult/moonlight-ios", &[]);
assert!(addresses(&hyphen, &said("moonlight-ios", &[])) && addresses(&hyphen, &said("Moonlight iOS", &[])) && !addresses(&hyphen, &said("Moonlight-iOS", &[])));
}
#[test]
@ -75,8 +89,17 @@ fn one_name_addresses_every_mind_that_lists_it_each_on_her_own_record() {
assert!(addresses(&lore, &both));
let two_stewards = [steward("GameCult/Thing", &["Thing"]), Addressee::new("@libby:chat", ["Libby"]).with_projects(project_names(&OrgRepo("GameCult/Thing".into()), &[]))];
assert!(two_stewards.iter().all(|who| addresses(who, &said("Thing is broken", &[]))), "a project with two stewards addresses each");
assert!(!addresses(&two_stewards[0], &said("a thing", &[])), "each by her own record: listed exact, derived in any case");
assert!(addresses(&two_stewards[1], &said("a thing", &[])));
assert!(!addresses(&two_stewards[0], &said("a thing", &[])), "listed Thing is exact");
assert!(!addresses(&two_stewards[1], &said("a thing", &[])), "derived Thing is exact as the repo spells it");
}
#[test]
fn a_name_that_overlaps_itself_is_found_where_a_whole_word_hit_follows_a_failed_one() {
assert!(names_whole_word("xab ab ab", "ab ab", false));
assert!(names_whole_word("xab ab ab", "AB AB", false));
assert!(names_whole_word("xab ab ab", "ab ab", true));
assert!(!names_whole_word("xab ab abx", "ab ab", true));
assert!(names_whole_word("é ab", "ab", true), "multibyte text steps by whole characters");
}
#[test]

View file

@ -353,7 +353,7 @@ fn config() -> Config {
/// ranks first among the Minds. The tests that are about the stagger name their events.
fn delivery(seq: u64, room: &str, sender: &str, text: &str) -> ChatDelivery {
let ranked_first = |event_id: &str| stagger_ms(&config(), &own_addressee(&config()), &ChatDelivery { event_id: event_id.into(), ..delivery_of(seq, room, sender, text, event_id) }) == 0;
let event_id = (0..).map(|n| if n == 0 { format!("$e{seq}") } else { format!("$e{seq}-{n}") }).find(|id| ranked_first(id)).unwrap();
let event_id = (0..1000).map(|n| if n == 0 { format!("$e{seq}") } else { format!("$e{seq}-{n}") }).find(|id| ranked_first(id)).expect("among 1000 events one ranks her first: the stagger ranks nobody first");
delivery_of(seq, room, sender, text, &event_id)
}
@ -6296,7 +6296,10 @@ fn three_minds(own: &str) -> Config {
/// An unaddressed swarm message whose event ranks `eureka` at `rank` of three.
fn unaddressed_at_rank(seq: u64, rank: u64, text: &str) -> ChatDelivery {
(0..).map(|n| delivery_of(seq, "swarm", "@op:chat", text, &format!("$r{seq}-{n}"))).find(|candidate| stagger_ms(&three_minds("@eureka:chat"), &own_addressee(&three_minds("@eureka:chat")), candidate) == rank * STAGGER_STEP_S * 1000).unwrap()
(0..1000)
.map(|n| delivery_of(seq, "swarm", "@op:chat", text, &format!("$r{seq}-{n}")))
.find(|candidate| stagger_ms(&three_minds("@eureka:chat"), &own_addressee(&three_minds("@eureka:chat")), candidate) == rank * STAGGER_STEP_S * 1000)
.unwrap_or_else(|| panic!("among 1000 events one ranks her at {rank}: the stagger never does"))
}
#[test]