111 lines
3.2 KiB
Rust
111 lines
3.2 KiB
Rust
//! A turn from before sync began goes nowhere (M5c spec, section 5): the first check, before
|
|
//! every other, by the time of the turn's `User` record against the ledger's `Began`. Do not
|
|
//! edit.
|
|
|
|
use bxcore::{DataClass, LogRecord, SessionStateReply, Timestamp};
|
|
use memoryd::honcho::decide::{BEFORE_SYNC, SECRET_CLASS, decide};
|
|
use memoryd::honcho::ledger::{Action, Workspace};
|
|
|
|
const AGENT: &str = "boxmaker0";
|
|
|
|
fn at(s: &str) -> Timestamp {
|
|
Timestamp::parse(s).unwrap()
|
|
}
|
|
|
|
/// One finished turn, its `User` record at `time`.
|
|
fn turn(time: &str, speaker: Option<&str>) -> Vec<LogRecord> {
|
|
vec![
|
|
LogRecord::User {
|
|
time: at(time),
|
|
content: "hello".to_string(),
|
|
speaker: speaker.map(str::to_string),
|
|
},
|
|
LogRecord::Assistant {
|
|
time: at(time),
|
|
content: Some("hi".to_string()),
|
|
reasoning_content: None,
|
|
tool_calls: Vec::new(),
|
|
},
|
|
]
|
|
}
|
|
|
|
fn state(class: DataClass) -> SessionStateReply {
|
|
SessionStateReply {
|
|
class,
|
|
untrusted: false,
|
|
}
|
|
}
|
|
|
|
fn skip(why: &str) -> Action {
|
|
Action::Skip {
|
|
why: why.to_string(),
|
|
}
|
|
}
|
|
|
|
const BEGAN: &str = "2026-09-28T10:00:00.000Z";
|
|
|
|
#[test]
|
|
fn the_text() {
|
|
assert_eq!(BEFORE_SYNC, "the turn is from before sync began");
|
|
}
|
|
|
|
#[test]
|
|
fn a_turn_from_before_sync_began_goes_nowhere() {
|
|
let began = Some(at(BEGAN));
|
|
let clean = state(DataClass::Private);
|
|
let before = turn("2026-09-28T09:59:59.999Z", Some("kyle"));
|
|
assert_eq!(
|
|
decide(&clean, &before, 1, AGENT, began),
|
|
Ok(skip(BEFORE_SYNC))
|
|
);
|
|
let same = turn(BEGAN, Some("kyle"));
|
|
let to_trusted = Action::Turn {
|
|
workspace: Workspace::Trusted,
|
|
speaker: "kyle".to_string(),
|
|
};
|
|
assert_eq!(
|
|
decide(&clean, &same, 1, AGENT, began),
|
|
Ok(to_trusted.clone())
|
|
);
|
|
let after = turn("2026-09-28T10:00:00.001Z", Some("kyle"));
|
|
assert_eq!(
|
|
decide(&clean, &after, 1, AGENT, began),
|
|
Ok(to_trusted.clone())
|
|
);
|
|
// Without a time sync began, no turn is too old.
|
|
assert_eq!(decide(&clean, &before, 1, AGENT, None), Ok(to_trusted));
|
|
}
|
|
|
|
#[test]
|
|
fn it_is_the_first_check() {
|
|
let began = Some(at(BEGAN));
|
|
// A turn from before that also breaks the class and the speaker rules: before-sync wins.
|
|
let before = turn("2026-09-28T09:00:00.000Z", None);
|
|
assert_eq!(
|
|
decide(&state(DataClass::Secret), &before, 1, AGENT, began),
|
|
Ok(skip(BEFORE_SYNC))
|
|
);
|
|
// The same turn after it: the next rule decides.
|
|
let after = turn("2026-09-28T11:00:00.000Z", None);
|
|
assert_eq!(
|
|
decide(&state(DataClass::Secret), &after, 1, AGENT, began),
|
|
Ok(skip(SECRET_CLASS))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn each_turn_by_its_own_time() {
|
|
let began = Some(at(BEGAN));
|
|
let mut records = turn("2026-09-28T09:00:00.000Z", Some("kyle"));
|
|
records.extend(turn("2026-09-28T11:00:00.000Z", Some("kyle")));
|
|
let clean = state(DataClass::Private);
|
|
assert_eq!(
|
|
decide(&clean, &records, 1, AGENT, began),
|
|
Ok(skip(BEFORE_SYNC))
|
|
);
|
|
assert!(matches!(
|
|
decide(&clean, &records, 2, AGENT, began),
|
|
Ok(Action::Turn { .. })
|
|
));
|
|
}
|