An earlier line this session has not shown prints once, in a thread or a room alike
rooms_shown kept the room as the unit, so a thread in a shown room lost its exchange, a first message in a thread lost the room's tail, and a failed context fetch marked the room shown. Now one set of the lines a process told, keyed by room, sender, instant and the head of the text (the mouth clips a message's copy, so the whole text differs). Her own lines of this session do not come back. Who she may name follows what printed. Findings thread-in-a-shown-room-loses-the-exchange, people-bound-counts-unprinted-context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ae3b7ab018
commit
3eee12f600
2 changed files with 192 additions and 39 deletions
|
|
@ -867,7 +867,8 @@ fn one_process(
|
|||
rooms,
|
||||
conversation,
|
||||
plan: render_plan(lease.plan_quota(), started_ms),
|
||||
rooms_shown: BTreeSet::new(),
|
||||
started_ms,
|
||||
told: BTreeSet::new(),
|
||||
start: first,
|
||||
waiting: Vec::new(),
|
||||
told_through: 0,
|
||||
|
|
@ -973,6 +974,13 @@ struct Retell {
|
|||
refusals: u32,
|
||||
}
|
||||
|
||||
/// A rendered line: its text, what it lets a reply name, and the lines it tells.
|
||||
struct Rendered {
|
||||
input: String,
|
||||
shown: Shown,
|
||||
told: Vec<LineKey>,
|
||||
}
|
||||
|
||||
/// The Persona's side of the session: her chat, in one conversation, told to the live session
|
||||
/// by the Self's own driver. Its caches (what was told, what was echoed) are the process's;
|
||||
/// the mouth's ack is the truth.
|
||||
|
|
@ -987,8 +995,10 @@ struct PersonaInbox<'a, C: ControlSource, S: ControlSource> {
|
|||
conversation: Conversation,
|
||||
/// The plan windows the lease granted, as one line.
|
||||
plan: String,
|
||||
/// The rooms a line of this session has shown: a later message from another brings that room's tail once.
|
||||
rooms_shown: BTreeSet<String>,
|
||||
/// When this process began: a line of her own from then on she wrote in this session.
|
||||
started_ms: u64,
|
||||
/// Every message and `earlier` line a line of this process has told: an `earlier` line prints once, in a thread or a room alike.
|
||||
told: BTreeSet<LineKey>,
|
||||
/// The batch the first line carries; taken by it.
|
||||
start: Vec<ChatDelivery>,
|
||||
/// The deliveries derived at the last poll that no line has carried yet.
|
||||
|
|
@ -1016,8 +1026,13 @@ struct PersonaInbox<'a, C: ControlSource, S: ControlSource> {
|
|||
}
|
||||
|
||||
impl<C: ControlSource, S: ControlSource> PersonaInbox<'_, C, S> {
|
||||
/// The `earlier` lines a batch brings now.
|
||||
fn earlier<'d>(&self, deliveries: &'d [ChatDelivery]) -> Vec<Vec<&'d ContextLine>> {
|
||||
earlier_shown(deliveries, &self.told, &self.config.own_sender, self.started_ms)
|
||||
}
|
||||
|
||||
/// A session's first line: her whole machine. The gate was read by the caller.
|
||||
fn whole_line(&self, deliveries: &[ChatDelivery], note: Option<&str>) -> Result<(String, Shown)> {
|
||||
fn whole_line(&self, deliveries: &[ChatDelivery], note: Option<&str>) -> Result<Rendered> {
|
||||
let organs = self.organs;
|
||||
let instance = organs.mind.instance().clone();
|
||||
let tempo = render_tempo(organs.tempo, &instance);
|
||||
|
|
@ -1028,26 +1043,28 @@ impl<C: ControlSource, S: ControlSource> PersonaInbox<'_, C, S> {
|
|||
let cue = recall::Cue { senders, texts: deliveries.iter().rev().map(|delivery| delivery.text.as_str()).collect(), names: names.clone(), listed: &goals.listed, now: organs.clock.now() };
|
||||
let recalled = recall::select(organs.mind, &cue)?;
|
||||
let shown: Vec<&str> = goals.people.iter().map(String::as_str).chain(recalled.entries().flat_map(|entry| entry.people.iter().map(String::as_str))).collect();
|
||||
let people = people_she_may_name(self.config, deliveries, &shown);
|
||||
let input = render_input(self.config, deliveries, note, &history, &goals.text, &recall::render(&recalled), &format!("{}\n\n{tempo}", self.plan), &people);
|
||||
Ok((input, Shown { people, open: goals.open }))
|
||||
let earlier = self.earlier(deliveries);
|
||||
let people = people_she_may_name(self.config, deliveries, &earlier, &shown);
|
||||
let input = render_input(self.config, &message_lines(self.config, deliveries, &earlier), note, &history, &goals.text, &recall::render(&recalled), &format!("{}\n\n{tempo}", self.plan), &people);
|
||||
Ok(Rendered { input, shown: Shown { people, open: goals.open }, told: lines_told(deliveries, &earlier) })
|
||||
}
|
||||
|
||||
/// A later line: the messages, a refused batch's note before them, the notices of her ended runs, and nothing else read from her mind.
|
||||
/// What a reply to it may name is what the session showed, and whoever is on this line.
|
||||
/// The organ's notices of her ended runs come first: they are not messages and answer to no delivery.
|
||||
fn tell_line(&self, deliveries: &[ChatDelivery], note: Option<&str>, notices: &str) -> (String, Shown) {
|
||||
let lines = format!("{notices}{}", message_lines(self.config, deliveries, &self.rooms_shown));
|
||||
fn tell_line(&self, deliveries: &[ChatDelivery], note: Option<&str>, notices: &str) -> Rendered {
|
||||
let earlier = self.earlier(deliveries);
|
||||
let lines = format!("{notices}{}", message_lines(self.config, deliveries, &earlier));
|
||||
let input = note.map_or_else(|| lines.clone(), |note| format!("{note}\n\n{lines}"));
|
||||
(input, Shown { people: people_she_may_name(self.config, deliveries, &[]), open: Vec::new() })
|
||||
Rendered { input, shown: Shown { people: people_she_may_name(self.config, deliveries, &earlier, &[]), open: Vec::new() }, told: lines_told(deliveries, &earlier) }
|
||||
}
|
||||
|
||||
/// Notes what a rendered line carries, as it goes to the child.
|
||||
fn write(&mut self, batch: Vec<ChatDelivery>, (input, shown): (String, Shown), refusals: u32) -> String {
|
||||
fn write(&mut self, batch: Vec<ChatDelivery>, Rendered { input, shown, told }: Rendered, refusals: u32) -> String {
|
||||
if let Some(through) = batch.iter().map(|delivery| delivery.seq).max() {
|
||||
self.told_through = self.told_through.max(through);
|
||||
}
|
||||
self.rooms_shown.extend(batch.iter().map(|delivery| delivery.room_alias.clone()));
|
||||
self.told.extend(told);
|
||||
self.written = Some(Written { batch, shown, refusals });
|
||||
input
|
||||
}
|
||||
|
|
@ -1621,9 +1638,9 @@ fn other_senders<'a>(config: &Config, deliveries: &'a [ChatDelivery]) -> Vec<&'a
|
|||
}
|
||||
|
||||
/// Whom a memory may be about, each within `Short`'s bound, once, in this order: the batch's other
|
||||
/// senders, the senders of the earlier lines shown beside them, the Minds mentioned, the operators,
|
||||
/// senders, the senders of the earlier lines this line prints, the Minds mentioned, the operators,
|
||||
/// the swarm's Minds, then everyone a memory shown this turn names. Never her own id.
|
||||
fn people_she_may_name(config: &Config, deliveries: &[ChatDelivery], shown: &[&str]) -> Vec<String> {
|
||||
fn people_she_may_name(config: &Config, deliveries: &[ChatDelivery], earlier: &[Vec<&ContextLine>], shown: &[&str]) -> Vec<String> {
|
||||
let mut people: Vec<String> = Vec::new();
|
||||
let mut add = |id: &str| {
|
||||
if id != config.own_sender && id.len() <= SHORT_MAX_BYTES && !id.is_empty() && !people.iter().any(|known| known == id) {
|
||||
|
|
@ -1631,7 +1648,7 @@ fn people_she_may_name(config: &Config, deliveries: &[ChatDelivery], shown: &[&s
|
|||
}
|
||||
};
|
||||
other_senders(config, deliveries).into_iter().for_each(&mut add);
|
||||
deliveries.iter().flat_map(|delivery| delivery.context.iter().map(|line| line.sender.as_str())).for_each(&mut add);
|
||||
earlier.iter().flatten().map(|line| line.sender.as_str()).for_each(&mut add);
|
||||
deliveries.iter().flat_map(|delivery| delivery.mentions.iter().map(String::as_str)).for_each(&mut add);
|
||||
config.operators.iter().map(String::as_str).for_each(&mut add);
|
||||
config.minds.iter().map(String::as_str).for_each(&mut add);
|
||||
|
|
@ -1929,16 +1946,41 @@ const WAKE_QUESTION: &str = "No new messages. Where is your machine least perfec
|
|||
/// Last text of a line with deliveries, after the messages.
|
||||
const TRAFFIC_QUESTION: &str = "Answer what needs you. Then the question of every turn: where is your machine least perfect right now, by your values, and does anything here change your move? Put that in `intent` first.";
|
||||
|
||||
/// The `earlier` lines a batch's line shows, one list per delivery: each distinct line of a room once (the mouth stamps a
|
||||
/// message and its copy in a later context with one `at_unix_ms`, so a genuine repeat at another instant is another line),
|
||||
/// where it first comes in the batch, none the batch carries as a message, and of each room the newest lines whose rendered
|
||||
/// (quoted) text fits `EARLIER_MAX_BYTES_PER_ROOM`. A line too big for what is left is skipped, never a reason to drop the
|
||||
/// older lines that fit. A room this session has already shown brings none: its tail comes once.
|
||||
fn earlier_shown<'a>(deliveries: &'a [ChatDelivery], rooms_shown: &BTreeSet<String>) -> Vec<Vec<&'a ContextLine>> {
|
||||
let mut seen: BTreeSet<(&str, &str, &str, u64)> = deliveries.iter().map(|delivery| (delivery.room_alias.as_str(), delivery.sender.as_str(), delivery.text.as_str(), delivery.at_unix_ms)).collect();
|
||||
/// How much of a line's text its key holds: the mouth clips a message's copy in a later context at 1000 bytes and the message
|
||||
/// itself at 4000, so a long message and its copy agree only on their head. 200 chars are at most 800 bytes.
|
||||
const LINE_KEY_TEXT_CHARS: usize = 200;
|
||||
|
||||
/// A line of a room, as a session knows it: room, sender, instant and the head of its text. The mouth stamps a message and its copy
|
||||
/// in a later context with one `at_unix_ms`, so a genuine repeat at another instant is another line.
|
||||
type LineKey = (String, String, u64, String);
|
||||
|
||||
fn line_key(room: &str, sender: &str, at_unix_ms: u64, text: &str) -> LineKey {
|
||||
(room.to_owned(), sender.to_owned(), at_unix_ms, text.chars().take(LINE_KEY_TEXT_CHARS).collect())
|
||||
}
|
||||
|
||||
fn message_key(delivery: &ChatDelivery) -> LineKey {
|
||||
line_key(&delivery.room_alias, &delivery.sender, delivery.at_unix_ms, &delivery.text)
|
||||
}
|
||||
|
||||
/// What a line tells: its messages and the `earlier` lines it prints. A line the budget skipped is not among them.
|
||||
fn lines_told(deliveries: &[ChatDelivery], earlier: &[Vec<&ContextLine>]) -> Vec<LineKey> {
|
||||
let printed = deliveries.iter().zip(earlier).flat_map(|(delivery, lines)| lines.iter().map(|line| line_key(&delivery.room_alias, &line.sender, line.at_unix_ms, &line.text)));
|
||||
deliveries.iter().map(message_key).chain(printed).collect()
|
||||
}
|
||||
|
||||
/// The `earlier` lines a batch's line shows, one list per delivery: the one rule is that a line this session has not shown prints
|
||||
/// once, whether the mouth read it from a thread or the room. So it prints where it first comes in the batch, never if the
|
||||
/// batch carries it as a message or a line of this process told it, and never if it is her own from this session. Of each room
|
||||
/// the newest lines whose rendered (quoted) text fits `EARLIER_MAX_BYTES_PER_ROOM`: a line too big for what is left is skipped,
|
||||
/// never a reason to drop the older lines that fit, and it can still come with a later message.
|
||||
fn earlier_shown<'a>(deliveries: &'a [ChatDelivery], told: &BTreeSet<LineKey>, own_sender: &str, started_ms: u64) -> Vec<Vec<&'a ContextLine>> {
|
||||
let mut seen = told.clone();
|
||||
seen.extend(deliveries.iter().map(message_key));
|
||||
let mut shown: Vec<Vec<&ContextLine>> = deliveries
|
||||
.iter()
|
||||
.map(|delivery| delivery.context.iter().filter(|_| !rooms_shown.contains(&delivery.room_alias)).filter(|line| seen.insert((delivery.room_alias.as_str(), line.sender.as_str(), line.text.as_str(), line.at_unix_ms))).collect())
|
||||
.map(|delivery| {
|
||||
delivery.context.iter().filter(|line| line.sender != own_sender || line.at_unix_ms < started_ms).filter(|line| seen.insert(line_key(&delivery.room_alias, &line.sender, line.at_unix_ms, &line.text))).collect()
|
||||
})
|
||||
.collect();
|
||||
let mut left: BTreeMap<&str, usize> = BTreeMap::new();
|
||||
for (delivery, lines) in deliveries.iter().zip(&mut shown).rev() {
|
||||
|
|
@ -1961,7 +2003,7 @@ fn earlier_shown<'a>(deliveries: &'a [ChatDelivery], rooms_shown: &BTreeSet<Stri
|
|||
/// remembers, the usage and tempo, the rooms and people she may name, the messages (each a framed
|
||||
/// line whose role the organ derived from the sender id and not from anything the sender wrote),
|
||||
/// and the question.
|
||||
fn render_input(config: &Config, deliveries: &[ChatDelivery], note: Option<&str>, history: &[String], goals: &str, remembered: &str, steer: &str, people: &[String]) -> String {
|
||||
fn render_input(config: &Config, messages: &str, note: Option<&str>, history: &[String], goals: &str, remembered: &str, steer: &str, people: &[String]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(TURN_INSTRUCTION);
|
||||
if let Some(note) = note {
|
||||
|
|
@ -1982,13 +2024,13 @@ fn render_input(config: &Config, deliveries: &[ChatDelivery], note: Option<&str>
|
|||
out.push_str(&format!("People you may name in a memory's about: {}.\n\n", people.join(", ")));
|
||||
out.push_str(MESSAGES_HEADER);
|
||||
out.push('\n');
|
||||
if deliveries.is_empty() {
|
||||
if messages.is_empty() {
|
||||
out.push('\n');
|
||||
out.push_str(WAKE_QUESTION);
|
||||
out.push('\n');
|
||||
return out;
|
||||
}
|
||||
out.push_str(&message_lines(config, deliveries, &BTreeSet::new()));
|
||||
out.push_str(messages);
|
||||
out.push('\n');
|
||||
out.push_str(TRAFFIC_QUESTION);
|
||||
out.push('\n');
|
||||
|
|
@ -1996,10 +2038,10 @@ fn render_input(config: &Config, deliveries: &[ChatDelivery], note: Option<&str>
|
|||
}
|
||||
|
||||
/// A batch as framed lines: each message, after the `earlier` lines it brings.
|
||||
fn message_lines(config: &Config, deliveries: &[ChatDelivery], rooms_shown: &BTreeSet<String>) -> String {
|
||||
fn message_lines(config: &Config, deliveries: &[ChatDelivery], earlier: &[Vec<&ContextLine>]) -> String {
|
||||
let mut out = String::new();
|
||||
let names = sender_names(deliveries);
|
||||
for (delivery, shown) in deliveries.iter().zip(earlier_shown(deliveries, rooms_shown)) {
|
||||
for (delivery, shown) in deliveries.iter().zip(earlier) {
|
||||
for earlier in shown {
|
||||
out.push_str(&message_line(config, "earlier", &delivery.room_alias, &earlier.sender, None, &Addressees { to_you: false, others: vec![] }, earlier.at_unix_ms, &earlier.text));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1157,7 +1157,9 @@ fn input_for(mentions: &[&str], display: &str) -> String {
|
|||
aqua.sender_display = display.into();
|
||||
let mut said = delivery(2, "swarm", "@operator:chat", "a reply");
|
||||
said.mentions = mentions.iter().map(|m| (*m).to_string()).collect();
|
||||
render_input(&config(), &[aqua, said], None, &[], "", "", "usage", &[])
|
||||
let c = config();
|
||||
let batch = [aqua, said];
|
||||
render_input(&c, &message_lines(&c, &batch, &earlier_shown(&batch, &BTreeSet::new(), &c.own_sender, u64::MAX)), None, &[], "", "", "usage", &[])
|
||||
}
|
||||
|
||||
fn message_two(input: &str) -> &str {
|
||||
|
|
@ -1940,11 +1942,12 @@ fn the_people_she_may_name_come_in_order_once_each_never_her_own_never_past_a_sh
|
|||
let too_long = format!("@{}:chat", "z".repeat(220));
|
||||
let batch = [delivery(1, "swarm", "@eureka:chat", "me"), said, delivery(3, "swarm", &too_long, "long")];
|
||||
let shown = ["@named-in-memory:chat", "@op:chat", too_long.as_str()];
|
||||
let printed: Vec<Vec<&ContextLine>> = batch.iter().map(|delivery| delivery.context.iter().collect()).collect();
|
||||
assert_eq!(
|
||||
people_she_may_name(&c, &batch, &shown),
|
||||
people_she_may_name(&c, &batch, &printed, &shown),
|
||||
["@aqua:chat", "@earlier:chat", "@mentioned:chat", "@op:chat", "@libby:chat", "@named-in-memory:chat"].map(String::from)
|
||||
);
|
||||
assert_eq!(people_she_may_name(&c, &[], &[]), ["@op:chat", "@libby:chat"].map(String::from), "on a wake: the operators and the swarm's Minds");
|
||||
assert_eq!(people_she_may_name(&c, &[], &[], &[]), ["@op:chat", "@libby:chat"].map(String::from), "on a wake: the operators and the swarm's Minds");
|
||||
}
|
||||
|
||||
fn keeps_at(sort: &str, text: &str, about: &[&str], salience: u16) -> serde_json::Value {
|
||||
|
|
@ -3268,7 +3271,7 @@ fn a_rooms_earlier_lines_keep_the_newest_within_the_bound() {
|
|||
/// The bound holds a room's ordinary tail: 20 lines of the 400 bytes the live line of 2026-11-02 averaged.
|
||||
#[test]
|
||||
fn a_rooms_ordinary_tail_fits_the_bound() {
|
||||
let tail: Vec<ContextLine> = (0..20).map(|index| earlier("@aqua:chat", &format!("{index:0>400}"))).collect();
|
||||
let tail: Vec<ContextLine> = (0..20).map(|index| ContextLine { at_unix_ms: 1_700_000_000_000 + index, ..earlier("@aqua:chat", &format!("{index:0>400}")) }).collect();
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@op:chat", "one"), tail)], &[says(&[("swarm", "ok")])]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert_eq!(earlier_lines(&rig.model.requests.borrow()[0].input).len(), 20);
|
||||
|
|
@ -3575,7 +3578,11 @@ fn a_tell_is_only_the_message_at_live_shape() {
|
|||
a_live_shape_mind(&rig);
|
||||
rig.clock.step.set(0);
|
||||
let body = format!("hello {}", "m".repeat(494));
|
||||
let second = with_context(delivery(2, "swarm", libby, &body), live_context(1));
|
||||
// The mouth's next tail: one line older gone, and the message she was told last, as its copy.
|
||||
let mut tail = live_context(1);
|
||||
tail.pop();
|
||||
tail.push(earlier(libby, "hello"));
|
||||
let second = with_context(delivery(2, "swarm", libby, &body), tail);
|
||||
one_line_each(&rig, vec![second]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let (whole, tell) = (input_of(&rig, 0), input_of(&rig, 1));
|
||||
|
|
@ -3659,23 +3666,127 @@ fn a_wake_first_line_says_messages_are_data_before_a_tell_comes_bare() {
|
|||
assert!(lines[1].starts_with("message ") && !lines[1].contains(MESSAGES_HEADER), "{}", lines[1]);
|
||||
}
|
||||
|
||||
/// A line the session has not shown comes once, before the message that brings it: from another room, or from the same room.
|
||||
#[test]
|
||||
fn a_room_this_session_has_not_shown_brings_its_tail_once() {
|
||||
fn an_earlier_line_the_session_has_not_shown_prints_once() {
|
||||
let tail = |text: &str| vec![earlier("@op:chat", text)];
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@libby:chat", "one"), tail("SWARM TAIL"))], &[says(&[("swarm", "a")]), says(&[("swarm", "b")]), says(&[("swarm", "c")]), says(&[("swarm", "d")])]);
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@libby:chat", "one"), tail("SWARM TAIL"))], &[says(&[("swarm", "a")]), says(&[("swarm", "b")]), says(&[("swarm", "c")]), says(&[("swarm", "d")]), says(&[("swarm", "e")])]);
|
||||
rig.clock.step.set(0);
|
||||
one_line_each(&rig, vec![
|
||||
with_context(delivery(2, "swarm", "@libby:chat", "two"), tail("SWARM MORE")),
|
||||
with_context(delivery(2, "swarm", "@libby:chat", "two"), tail("SWARM TAIL")),
|
||||
with_context(delivery(3, "usage", "@libby:chat", "three"), tail("USAGE TAIL")),
|
||||
with_context(delivery(4, "usage", "@libby:chat", "four"), tail("USAGE MORE")),
|
||||
with_context(delivery(4, "usage", "@libby:chat", "four"), tail("USAGE TAIL")),
|
||||
with_context(delivery(5, "swarm", "@libby:chat", "five"), tail("NEVER DELIVERED")),
|
||||
]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert_eq!(lines.len(), 4, "{lines:?}");
|
||||
assert_eq!(lines.len(), 5, "{lines:?}");
|
||||
assert!(lines[0].contains(r#"text="SWARM TAIL""#), "{}", lines[0]);
|
||||
assert_eq!(lines[1].lines().count(), 1, "the first line showed this room: {}", lines[1]);
|
||||
assert_eq!(lines[1].lines().count(), 1, "the first line showed it: {}", lines[1]);
|
||||
assert!(lines[2].starts_with("earlier ") && lines[2].contains(r#"text="USAGE TAIL""#) && lines[2].lines().count() == 2, "a new room's tail, before its message: {}", lines[2]);
|
||||
assert_eq!(lines[3].lines().count(), 1, "once: {}", lines[3]);
|
||||
assert!(lines[4].starts_with("earlier ") && lines[4].contains(r#"text="NEVER DELIVERED""#) && lines[4].lines().count() == 2, "a room already shown still brings a line it has not shown: {}", lines[4]);
|
||||
}
|
||||
|
||||
/// Bifrost's `contextFor`: a message in a thread carries the thread, root first, and a message in the room carries the room outside threads.
|
||||
#[test]
|
||||
fn a_thread_in_a_room_the_session_has_shown_brings_its_exchange_once() {
|
||||
let at = |text: &str, ms: u64| ContextLine { sender: "@third:chat".into(), text: text.into(), at_unix_ms: 1_700_000_000_000 + ms };
|
||||
let (root, reply_in_it, room_line) = (at("THREAD ROOT", 10), at("THREAD REPLY", 20), at("ROOM LINE", 5));
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@libby:chat", "one"), vec![room_line.clone()])], &[says(&[("swarm", "a")]), says(&[("swarm", "b")]), says(&[("swarm", "c")])]);
|
||||
rig.clock.step.set(0);
|
||||
one_line_each(&rig, vec![
|
||||
with_context(delivery(2, "swarm", "@op:chat", "in the thread"), vec![root.clone(), reply_in_it.clone()]),
|
||||
with_context(delivery(3, "swarm", "@op:chat", "more in it"), vec![root, reply_in_it]),
|
||||
]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert_eq!(lines.len(), 3, "{lines:?}");
|
||||
let thread = &lines[1];
|
||||
assert_eq!(earlier_lines(thread).len(), 2, "{thread}");
|
||||
assert!(thread.find("THREAD ROOT").unwrap() < thread.find("THREAD REPLY").unwrap() && thread.find("THREAD REPLY").unwrap() < thread.find("in the thread").unwrap(), "the exchange, root first, before the message: {thread}");
|
||||
assert_eq!(lines[2].lines().count(), 1, "once: {}", lines[2]);
|
||||
}
|
||||
|
||||
/// A first message from a new room that sits in a thread shows the thread and not the room: the room's own tail still comes.
|
||||
#[test]
|
||||
fn a_first_message_in_a_thread_leaves_the_rooms_tail_to_come() {
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@op:chat", "in the thread"), vec![earlier("@third:chat", "THREAD ROOT")])], &[says(&[("swarm", "a")]), says(&[("swarm", "b")])]);
|
||||
rig.clock.step.set(0);
|
||||
one_line_each(&rig, vec![with_context(delivery(2, "swarm", "@op:chat", "in the room"), vec![earlier("@third:chat", "ROOM TAIL")])]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert!(lines[0].contains("THREAD ROOT") && !lines[0].contains("ROOM TAIL"), "{}", lines[0]);
|
||||
assert!(lines[1].starts_with("earlier ") && lines[1].contains("ROOM TAIL"), "{}", lines[1]);
|
||||
}
|
||||
|
||||
/// The mouth answers a failed context fetch with an empty context: that shows nothing, so the next message that has one brings the tail.
|
||||
#[test]
|
||||
fn a_failed_context_fetch_loses_nothing() {
|
||||
let rig = Rig::new(vec![delivery(1, "swarm", "@op:chat", "one")], &[says(&[("swarm", "a")]), says(&[("swarm", "b")])]);
|
||||
rig.clock.step.set(0);
|
||||
one_line_each(&rig, vec![with_context(delivery(2, "swarm", "@op:chat", "two"), vec![earlier("@third:chat", "THE TAIL")])]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert!(lines[1].starts_with("earlier ") && lines[1].contains("THE TAIL"), "{}", lines[1]);
|
||||
}
|
||||
|
||||
/// The mouth clips a context line at 1000 bytes and a message at 4000: a long message and its copy are one line.
|
||||
#[test]
|
||||
fn a_long_message_is_not_told_again_as_its_clipped_copy() {
|
||||
let long = format!("HEAD {}", "x".repeat(3000));
|
||||
let copy = ContextLine { sender: "@op:chat".into(), text: long.chars().take(1000).collect(), at_unix_ms: 1_700_000_000_000 };
|
||||
assert_ne!(copy.text, long);
|
||||
let rig = Rig::new(vec![delivery(1, "swarm", "@op:chat", &long)], &[says(&[("swarm", "a")]), says(&[("swarm", "b")])]);
|
||||
rig.clock.step.set(0);
|
||||
one_line_each(&rig, vec![with_context(delivery(2, "swarm", "@libby:chat", "two"), vec![copy])]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
assert_eq!(told(&rig)[1].lines().count(), 1, "{}", told(&rig)[1]);
|
||||
}
|
||||
|
||||
/// Hers from before this process she reads, as `reply-sees-full-exchange` asks; hers from this session she wrote, and it does not come back.
|
||||
#[test]
|
||||
fn her_own_lines_of_this_session_do_not_return_and_her_older_ones_do() {
|
||||
let hers = |text: &str, at: u64| ContextLine { sender: "@eureka:chat".into(), text: text.into(), at_unix_ms: at };
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@op:chat", "one"), vec![hers("BEFORE THE SESSION", 1)])], &[says(&[("swarm", "a")]), says(&[("swarm", "b")])]);
|
||||
rig.clock.step.set(0);
|
||||
let now = unix_ms(rig.clock.now());
|
||||
one_line_each(&rig, vec![with_context(delivery(2, "swarm", "@op:chat", "two"), vec![hers("BEFORE THE SESSION", 1), hers("WRITTEN IN IT", now), hers("BEFORE TOO", 2)])]);
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert!(lines[0].contains("BEFORE THE SESSION"), "{}", lines[0]);
|
||||
assert!(!lines[1].contains("WRITTEN IN IT") && lines[1].contains("BEFORE TOO") && !lines[1].contains("BEFORE THE SESSION"), "{}", lines[1]);
|
||||
}
|
||||
|
||||
/// What a process told dies with it: a later line of the next process, not only its first, brings the line again.
|
||||
#[test]
|
||||
fn what_an_earlier_process_told_is_told_again_by_the_next() {
|
||||
let tailed = |seq, text| with_context(delivery(seq, "swarm", "@libby:chat", text), vec![earlier("@op:chat", "THE TAIL")]);
|
||||
// A direct message ends the first process at idle; the second answers it and the public line starts a third, whose first line has no tail, and whose next does.
|
||||
let rig = Rig::new(vec![tailed(1, "one"), direct(2, "!dm:chat", "@op:chat", "aside"), delivery(3, "swarm", "@libby:chat", "three")], &[says(&[("swarm", "a")]), says(&[("!dm:chat", "b")]), says(&[("swarm", "c")]), says(&[("swarm", "d")])]);
|
||||
let (deliveries, now, echoes) = (rig.chat.deliveries.clone(), rig.clock.now.clone(), Cell::new(0));
|
||||
let next = tailed(4, "four");
|
||||
*rig.model.after_echo.borrow_mut() = Some(Box::new(move || {
|
||||
echoes.set(echoes.get() + 1);
|
||||
if echoes.get() == 3 {
|
||||
deliveries.borrow_mut().push(next.clone());
|
||||
now.set(now.get() + 6_000);
|
||||
}
|
||||
}));
|
||||
assert_eq!(failure_of(rig.go()), None);
|
||||
let lines = told(&rig);
|
||||
assert_eq!(rig.model.spawns.borrow().len(), 3, "{lines:?}");
|
||||
assert!(lines[3].starts_with("earlier ") && lines[3].contains("THE TAIL"), "{}", lines[3]);
|
||||
}
|
||||
|
||||
/// A sender whose line is in a context she was not shown is no one she may name: the bound follows what printed.
|
||||
#[test]
|
||||
fn a_sender_on_no_printed_line_is_not_nameable() {
|
||||
let over_budget = ContextLine { sender: "@big:chat".into(), text: "x".repeat(EARLIER_MAX_BYTES_PER_ROOM + 1), at_unix_ms: 1_700_000_000_000 };
|
||||
let names_big = reply(&[("swarm", "a")], json!([keeps("Did", "Noted", &["@big:chat"])]), json!([]), json!(null));
|
||||
let rig = Rig::new(vec![with_context(delivery(1, "swarm", "@libby:chat", "one"), vec![over_budget])], &[names_big.clone(), names_big.clone(), names_big]);
|
||||
rig.clock.step.set(1000);
|
||||
assert_eq!(failure_of(rig.go()), Some(Failure::OutOfBounds(ABOUT_FAULT)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue