use super::*;
fn last_prompt(leaf: Option<&str>, explicit: Option<bool>) -> String {
let mut fields = String::from(r#""type":"last-prompt""#);
if let Some(l) = leaf {
fields.push_str(&format!(r#","leafUuid":"{l}""#));
}
if let Some(e) = explicit {
fields.push_str(&format!(r#","explicit":{e}"#));
}
format!("{{{fields},\"lastPrompt\":\"go\"}}")
}
fn side(uuid: &str, parent: &str, ts: &str) -> String {
format!(
r#"{{"type":"assistant","uuid":"{uuid}","parentUuid":"{parent}","isSidechain":true,"timestamp":"{ts}","message":{{"role":"assistant","id":"m-{uuid}","content":[{{"type":"text","text":"aside"}}]}}}}"#
)
}
#[test]
fn the_newest_fallback_skips_the_other_lane_and_the_replay_copies_and_keeps_the_first_of_a_tie() {
let r = recs(&[
&u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
&a("a1", "u0", "the winner", "2026-06-07T05:00:05.000Z"),
&a(
"a2",
"u0",
"ties with the winner",
"2026-06-07T05:00:05.000Z",
),
&side("sx", "u0", "2026-06-07T05:00:09.000Z"),
&a("dup", "u0", "a replay copy", "2026-06-07T05:00:08.000Z"),
&a("dup", "u0", "its survivor", "2026-06-07T05:00:01.000Z"),
&last_prompt(None, Some(true)),
]);
let c = Chain::build(&r, None);
assert_eq!(
c.leaf_source,
LeafSource::Cleared,
"the leaf set was emptied"
);
assert_eq!(
c.leaf_index,
Some(1),
"the newest eligible record, and the first of the tie"
);
}
#[test]
fn a_removed_record_never_wins_the_newest_fallback() {
let r = recs(&[
&u("u0", "", "dropped prompt", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "dropped reply", "2026-06-07T05:09:00.000Z"),
&u("k1", "a0", "kept prompt", "2026-06-07T05:01:00.000Z"),
r#"{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":null,"timestamp":"2026-06-07T05:02:00.000Z","compactMetadata":{"trigger":"auto","preservedMessages":{"anchorUuid":"a0","uuids":["k1"]}}}"#,
&u("u1", "k1", "new prompt", "2026-06-07T05:03:00.000Z"),
&last_prompt(None, Some(true)),
]);
let c = Chain::build(&r, None);
assert_eq!(c.leaf_source, LeafSource::Cleared);
assert_eq!(
c.leaf_index,
Some(4),
"the newest record still in the map, not the newest line in the file"
);
let s = survivals(&c, r.len());
assert_eq!(&s[0..2], ["pre-cut", "pre-cut"]);
}
#[test]
fn one_explicit_last_prompt_pins_the_leaf_and_a_repeat_keeps_it_pinned() {
let base = [
u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
a("a0", "u0", "charting", "2026-06-07T05:00:05.000Z"),
u("u1", "a0", "sound the reef", "2026-06-07T05:01:00.000Z"),
a("a1", "u1", "sounding", "2026-06-07T05:01:05.000Z"),
];
let build = |tail: &[String]| -> Chain {
let mut lines: Vec<&str> = base.iter().map(String::as_str).collect();
lines.extend(tail.iter().map(String::as_str));
let r: Vec<Record> = lines.iter().map(|l| parse(l)).collect();
Chain::build(&r, None)
};
let one = build(&[last_prompt(Some("a0"), Some(true))]);
assert_eq!(
one.leaf_source,
LeafSource::Explicit,
"a single line saying explicit is enough"
);
assert_eq!(one.leaf_index, Some(1), "and the tail does not replace it");
let repeated = build(&[
last_prompt(Some("a0"), Some(true)),
last_prompt(Some("a0"), None),
]);
assert_eq!(
repeated.leaf_source,
LeafSource::Explicit,
"a repeat of the same leaf keeps the pin"
);
let moved = build(&[
last_prompt(Some("a0"), Some(true)),
last_prompt(Some("u1"), None),
]);
assert_eq!(
moved.leaf_source,
LeafSource::LastPrompt,
"naming a different leaf drops the pin"
);
let never = build(&[last_prompt(Some("a0"), None), last_prompt(Some("a0"), None)]);
assert_eq!(
never.leaf_source,
LeafSource::LastPrompt,
"two ordinary lines never add up to a pin"
);
}
#[test]
fn an_empty_leaf_uuid_clears_the_set_and_a_bare_line_leaves_it_alone() {
let base = [
u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
a("a0", "u0", "charting", "2026-06-07T05:00:05.000Z"),
u("u1", "a0", "sound the reef", "2026-06-07T05:01:00.000Z"),
a("a1", "u1", "sounding", "2026-06-07T05:01:05.000Z"),
];
let build = |tail: &[String]| -> Chain {
let mut lines: Vec<&str> = base.iter().map(String::as_str).collect();
lines.extend(tail.iter().map(String::as_str));
let r: Vec<Record> = lines.iter().map(|l| parse(l)).collect();
Chain::build(&r, None)
};
let emptied = build(&[last_prompt(Some(""), Some(true))]);
assert_eq!(
emptied.leaf_source,
LeafSource::Cleared,
"an empty leafUuid is not a leaf"
);
let untouched = build(&[last_prompt(Some("a0"), Some(true)), last_prompt(None, None)]);
assert_eq!(
untouched.leaf_source,
LeafSource::Explicit,
"a bare last-prompt line says nothing and clears nothing"
);
assert_eq!(untouched.leaf_index, Some(1));
}
#[test]
fn only_a_compaction_boundary_wipes_the_recorded_leaf() {
let r = recs(&[
&u("u0", "", "chart the lagoon", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "charting", "2026-06-07T05:00:05.000Z"),
&last_prompt(Some("a0"), Some(true)),
r#"{"type":"system","subtype":"informational","uuid":"sy1","parentUuid":"a0","timestamp":"2026-06-07T05:00:50.000Z","level":"info","content":"a harness note"}"#,
&u("u1", "a0", "sound the reef", "2026-06-07T05:01:00.000Z"),
&a("a1", "u1", "sounding", "2026-06-07T05:01:05.000Z"),
]);
let c = Chain::build(&r, None);
assert_eq!(
c.leaf_source,
LeafSource::Explicit,
"a note is not a compaction"
);
assert_eq!(c.leaf_index, Some(1), "so the pinned leaf still stands");
}
#[test]
fn a_relink_anchor_skips_a_recorded_leaf_that_was_never_pinned() {
let r = recs(&[
&u("u0", "", "dropped prompt", "2026-06-07T05:00:00.000Z"),
&a("a0", "u0", "dropped reply", "2026-06-07T05:00:05.000Z"),
&u("k1", "a0", "kept prompt", "2026-06-07T05:05:00.000Z"),
&a("k2", "k1", "kept reply", "2026-06-07T05:05:05.000Z"),
r#"{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":null,"timestamp":"2026-06-07T05:10:00.000Z","compactMetadata":{"trigger":"auto","preservedMessages":{"anchorUuid":"a0","uuids":["k1","k2"]}}}"#,
&u(
"d1",
"k2",
"the branch left behind",
"2026-06-07T05:10:30.000Z",
),
&a("r1", "d1", "its reply", "2026-06-07T05:10:35.000Z"),
&u("u1", "k2", "new prompt", "2026-06-07T05:11:00.000Z"),
&a("a1", "u1", "new reply", "2026-06-07T05:11:05.000Z"),
&last_prompt(Some("d1"), None),
]);
let c = Chain::build(&r, None);
assert_eq!(
c.leaf_index,
Some(8),
"the tip the file ends on, not the recorded leaf"
);
}