Desk wire: repin Libby's vectors to CultLib 6231ecad; replay compares counted_answer_id and the refused target exactly; digest goldens pinned from the file

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-10 18:50:19 +00:00
commit 0a9d69679c
3 changed files with 2158 additions and 217 deletions

View file

@ -932,7 +932,7 @@ mod tests {
assert_eq!(counted(&mind, FORK), Some(answer));
}
/// Libby's vectors (CultLib 8ca1d184, `contracts/cultnet/governance/fork-card-vectors.json`, the `optionsHash` array):
/// Libby's vectors (CultLib 6231ecad, `contracts/cultnet/governance/fork-card-vectors.json`, the `optionsHash` array):
/// the seven of the binding file, A to G, and the eight she adds. Each is recomputed from its shown members.
fn vectors() -> Vec<serde_json::Value> {
let file: serde_json::Value = serde_json::from_str(include_str!("../vectors/fork-card-vectors.json")).unwrap();
@ -1336,6 +1336,42 @@ mod tests {
}
/// The digest form is frozen: a fixed request digests to a fixed hex, computed outside this code.
/// Every `bodyDigests` golden of Libby's file that the frozen form still reproduces: the two plain
/// forms and the five whose illegal form carries `retracted`. Two older illegal goldens (no flag)
/// are the form desk-digest-retracted replaced on purpose, and are not asserted.
#[test]
fn body_digests_match_the_vendored_goldens() {
let file: serde_json::Value = serde_json::from_str(include_str!("../vectors/fork-card-vectors.json")).unwrap();
let mut checked = 0;
for golden in file["bodyDigests"].as_array().unwrap() {
let input = &golden["input"];
let text = |name: &str| input[name].as_str().map(s);
let body = match (input["illegal_retraction"].as_bool(), input["retracted"].as_bool()) {
(Some(true), None) => continue,
(Some(true), Some(retracted)) => DeskBody::IllegalRetraction {
choice: text("choice"),
note: input["note"].as_str().map(|note| Words(note.into())),
retracts: text("retracts"),
retracted,
},
_ => match text("retracts") {
Some(retracts) => DeskBody::Retraction { retracts },
None => DeskBody::Answer { choice: text("choice"), note: None },
},
};
let request = DeskAnswerRequest {
fork: text("fork").unwrap(),
options_hash: Sha256Hex(input["options_hash"].as_str().unwrap().into()),
body,
answered_at: text("answered_at").unwrap(),
request_id: RequestId(R1.into()),
};
assert_eq!(body_digest(&request).0, golden["sha256"].as_str().unwrap(), "{}", golden["name"]);
checked += 1;
}
assert_eq!(checked, 7);
}
#[test]
fn body_digest_is_pinned() {
let request = |body: DeskBody| DeskAnswerRequest {
@ -1360,6 +1396,9 @@ mod tests {
assert_eq!(carried.0, "935cab1a0ae34606b809d8ebca7cffa2889d20b3585d2622d32153a4d6898af1");
assert_eq!(plain_with_target.0, "82b24cccb4ed2535a549f95f844fcd935074c3712b5b832f0b2cbd89c71fd9a6");
assert_eq!(body_digest(&illegal(None, None, true)).0, "34ef86ed63b9318459965d4b85e794d2636ffc15736da866036b087c9c4a30eb");
assert_eq!(body_digest(&illegal(Some("A"), None, true)).0, "79eee623fd6712db6537a4867cedf54ca49ee4885416c2c8e4acec59f811b894");
// The legal retraction as the door spells its target (`.n<seq>`).
assert_eq!(body_digest(&request(DeskBody::Retraction { retracts: s("eureka-state:desk_answer:Q1.n1") })).0, "84576840247830b9b75754b460dc4ea52883ca1e410f9f011521e0f69b99eee1");
assert_ne!(carried, plain_with_target);
// The id is not part of the body; answered_at is.
let mut other_id = answer.clone();

View file

@ -1,5 +1,5 @@
//! The fork desk's answer wire, Huginn's half: Libby's answer-side documents
//! (CultLib `contracts/cultnet/governance/`, the contract file and its vectors at 8ca1d184)
//! (CultLib `contracts/cultnet/governance/`, the contract file and its vectors at 6231ecad)
//! as Rust types, the one conversion between her `n<seq>` answer ids and the
//! record keys, the projection of a stored answer, the refusal mapping and the
//! showability predicate, and `Mind::desk_answer`, the one call that runs them in order.
@ -362,7 +362,7 @@ mod tests {
const LIST: [&str; 4] = ["x", "y", "z", "op"];
const AT: &str = "2026-11-10T12:00:00Z";
// ---- Libby's vectors: CultLib 8ca1d184, `contracts/cultnet/governance/fork-card-vectors.json` ----
// ---- Libby's vectors: CultLib 6231ecad, `contracts/cultnet/governance/fork-card-vectors.json` ----
fn vectors() -> Value {
serde_json::from_str(include_str!("../vectors/fork-card-vectors.json")).unwrap()
@ -993,18 +993,6 @@ mod tests {
/// 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();
@ -1020,7 +1008,8 @@ mod tests {
#[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());
let (mut played, mut compared, mut refused_at_the_door, mut skipped) = (0, 0, 0, Vec::new());
let (mut with_count, mut with_target) = (std::collections::BTreeSet::new(), std::collections::BTreeSet::new());
for scenario in file["scenarios"].as_array().unwrap() {
let name = scenario["name"].as_str().unwrap();
if CHANGED_CARD.contains(&name) {
@ -1082,33 +1071,42 @@ mod tests {
assert_eq!(serde_json::to_value(not_stored(mine).code).unwrap(), code.unwrap(), "{name}");
continue;
}
let (mut mine, _, _) = answered(mine);
let (mut mine, mut counted, _) = 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();
// Her ids to the Mind's, once; everything else is compared exactly: the refused target as
// `n<seq>` and the count derived at the response's head.
let through = |id: &mut Option<String>| *id = id.take().map(|id| ids.get(&id).cloned().unwrap_or(id));
let mut hers_counted = response["counted_answer_id"].as_str().map(str::to_string);
through(&mut hers.of);
through(&mut hers.retracts);
through(&mut hers_counted);
hers.answer_id = mine.answer_id.clone();
if hers.retracts.is_none() && mine.retracts.is_some() {
deviations.push(name);
mine.retracts = None;
assert_eq!(counted.take(), hers_counted, "{name}: counted_answer_id");
if hers_counted.is_some() {
with_count.insert(name);
}
if mine.outcome == "Refused" && mine.retracts.is_some() {
with_target.insert(name);
}
assert_eq!(mine, hers, "{name}");
assert_eq!(encode(&mine), encode(&hers), "{name}");
compared += 1;
}
let record = &scenario["record"];
// Where her fixture numbers its answers as the Mind's sequence does, the map is the identity.
let sequential = record["answers"].as_array().unwrap().iter().enumerate().all(|(at, answer)| answer["answer_id"] == format!("n{}", at + 1));
assert!(!sequential || ids.iter().all(|(hers, mine)| hers == mine), "{name}: {ids:?}");
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!((played, skipped.len()), (61, 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);
assert_eq!(compared, 91);
// 43 replayed scenarios pin a non-null counted_answer_id on an answer; 12 pin a refused retraction's target as n<seq>.
assert_eq!((with_count.len(), with_target.len()), (43, 12));
}
#[test]

File diff suppressed because one or more lines are too long