Desk wire tests: Libby's 53 replayable scenarios through Mind::desk_answer, with the named target deviation pinned

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-10 18:35:21 +00:00
commit e84c7130dd

View file

@ -979,4 +979,140 @@ mod tests {
wrong["schemaVersion"] = Value::String("gamecult.fork.answer_request.v1".into());
assert!(decode(&wrong).is_err());
}
// ---- Libby's scenarios through Mind::desk_answer ----
/// The scenarios whose card changes under their answers. A question is immutable in the Mind,
/// so there is no operation to replay a changed card with; the older-card rules are
/// desk.rs's `answers_under_an_older_card_neither_count_nor_can_be_retracted`.
const CHANGED_CARD: [&str; 2] = ["a_changed_card_stops_the_old_answers_counting_and_a_new_answer_is_counted", "a_changed_card_leaves_no_counted_answer_until_one_arrives"];
/// The scenarios whose refused retraction names a target in Libby's record as null and here as `n<seq>`.
const NAMED_TARGET: [&str; 8] = [
"V8_retraction_with_an_answer_is_bad",
"V8_retracts_on_a_plain_answer_is_bad",
"V8_retraction_naming_an_unknown_answer_is_bad",
"V8_retraction_naming_a_Refused_answer_is_bad",
"V8_retracting_twice_is_already_retracted",
"V8_retraction_under_a_stale_hash_is_stale_fork",
"V9_an_account_off_the_list_cannot_retract",
"V10a_a_ruling_that_closed_the_fork_makes_a_retraction_fork_closed",
];
/// The question a scenario's card shows, held as Q1.
fn question_of(card: &Value) -> PipelineQuestion {
let text = |value: &Value| value.as_str().unwrap().to_string();
let D::Question(mut question) = question("Q1", &["A", "B"], "A") else { unreachable!() };
question.title = text(&card["title"]).as_str().into();
question.question = text(&card["context"]).as_str().into();
question.recommended = l(&text(&card["recommendation"]));
question.options = card["options"].as_array().unwrap().iter().map(|option| QuestionOption { label: l(&text(&option["id"])), text: text(&option["text"]).as_str().into() }).collect();
question.depends = card["depends"].as_array().unwrap().iter().map(|line| text(line).as_str().into()).collect();
question
}
#[test]
fn libby_scenarios_replay_through_desk_answer() {
let file = vectors();
let (mut played, mut compared, mut refused_at_the_door, mut deviations, mut skipped) = (0, 0, 0, Vec::new(), Vec::new());
for scenario in file["scenarios"].as_array().unwrap() {
let name = scenario["name"].as_str().unwrap();
if CHANGED_CARD.contains(&name) {
skipped.push(name);
continue;
}
played += 1;
let card = &scenario["card"];
let (her_fork, her_hash) = (card["fork_id"].as_str().unwrap(), card["options_hash"].as_str().unwrap());
let question = question_of(card);
assert_eq!(shown_hash(&question).0, her_hash, "{name}: the card's hash");
let mut mind = seeded();
committed(admit(&mut mind, vec![D::Question(question)]));
let list: Vec<&str> = card["deciders"].as_array().unwrap().iter().map(|who| who.as_str().unwrap()).collect();
if card["status"] == "closed" {
close(&mut mind, FORK, "RQ1", "no");
}
// Her ids to the Mind's: her fixtures number answers as they please, the Mind's sequence is its own.
let mut ids: HashMap<String, String> = HashMap::new();
for step in scenario["steps"].as_array().unwrap() {
let (response, server) = (&step["response"], &step["server"]);
match step["kind"].as_str().unwrap() {
// A ruling closes the fork; a revised card changes nothing the Mind holds.
"card" => {
if step["card"]["status"] == "closed" {
close(&mut mind, FORK, "RQ1", "no");
}
continue;
}
// Chat events reach no Mind.
"matrix_event" => continue,
_ => {}
}
let code = response["code"].as_str();
// Refused at the door before the Mind: a body outside the closed document does not decode here either.
if matches!(code, Some("body_forbidden_field" | "body_unknown_field")) {
assert!(serde_json::from_value::<ForkAnswerRequest>(step["body"].clone()).is_err(), "{name}");
refused_at_the_door += 1;
continue;
}
if matches!(code, Some("forbidden" | "unauthenticated")) || (code == Some("unavailable") && server["stored"] != true) {
continue;
}
let mut request: ForkAnswerRequest = serde_json::from_value(step["body"].clone()).unwrap();
if request.fork_id == her_fork {
request.fork_id = FORK.into();
}
if let Some(mapped) = request.retracts.as_ref().and_then(|target| ids.get(target)) {
request.retracts = Some(mapped.clone());
}
let authority = serde_json::from_value(server["authority"].clone()).unwrap();
let at = DateTime::parse_from_rfc3339(server["received_at"].as_str().unwrap()).unwrap().to_utc();
let answerer = s(server["answerer"].as_str().unwrap());
let (mine, _) = mind.desk_answer(&deciders(&list), answerer, authority, request, at);
if code == Some("unavailable") {
continue; // the door went down after the Mind stored: its answer was never seen
}
if response["schemaVersion"].as_str().unwrap().ends_with("refusal.v0") {
assert_eq!(serde_json::to_value(not_stored(mine).code).unwrap(), code.unwrap(), "{name}");
continue;
}
let (mut mine, _, _) = answered(mine);
let mut hers: ForkDeskAnswer = serde_json::from_value(response["answer"].clone()).unwrap();
assert_eq!(ids.entry(hers.answer_id.clone()).or_insert_with(|| mine.answer_id.clone()), &mine.answer_id, "{name}: one answer, one id");
mine.fork_id = her_fork.into();
let through = |id: &mut Option<String>| *id = id.take().map(|id| ids.get(&id).cloned().unwrap_or(id));
(through(&mut hers.of), through(&mut hers.retracts));
hers.answer_id = mine.answer_id.clone();
if hers.retracts.is_none() && mine.retracts.is_some() {
deviations.push(name);
mine.retracts = None;
}
assert_eq!(mine, hers, "{name}");
assert_eq!(encode(&mine), encode(&hers), "{name}");
compared += 1;
}
let record = &scenario["record"];
assert_eq!(stored_count(&mind, FORK), record["answers"].as_array().unwrap().len(), "{name}: the stored answers");
let hers = record["counted_answer_id"].as_str().map(|id| ids.get(id).cloned().unwrap_or(id.to_string()));
assert_eq!(counted_wire(&mind, FORK), hers, "{name}: the answer that counts");
}
assert_eq!((played, skipped.len()), (53, 2));
assert_eq!(refused_at_the_door, 7);
assert_eq!(compared, 71);
let mut named: Vec<&str> = deviations.clone();
named.dedup();
assert_eq!(named, NAMED_TARGET);
}
#[test]
fn the_attribution_is_stamped_and_never_decides() {
let mut mind = desk();
let hash = hash_of(&mind, FORK);
// The operator's own account, off the list, is no decider; a member on the list is.
let off = mind.desk_answer(&deciders(&["x"]), s("op"), DeskAuthority::Operator, wire(FORK, &hash), now()).0;
assert_eq!(answered(off).0.code, Some(ForkAnswerCode::NotADecider));
let on = mind.desk_answer(&deciders(&["m"]), s("m"), DeskAuthority::Member, wire(FORK, &hash), now()).0;
let (answer, _, _) = answered(on);
assert_eq!((answer.outcome.as_str(), answer.authority, answer.answerer.as_str()), ("Counted", DeskAuthority::Member, "m"));
}
}