Desk answer: delete the replayed flag nobody read; tests assert that a replay commits nothing

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-11 00:06:27 +00:00
commit aeeb36ad2a
3 changed files with 53 additions and 50 deletions

View file

@ -243,7 +243,7 @@ impl<S: MindStore, I: IndexSink<S>> Daemon<S, I> {
}
match request {
DeskRequest::Answer { subject, authority, deciders, request, .. } => {
self.mind.desk_answer(&deciders, subject, authority, request, now).0
self.mind.desk_answer(&deciders, subject, authority, request, now)
}
DeskRequest::Forks { deciders, status, cursor, limit, .. } => self.mind.desk_forks(&deciders, status, cursor, limit),
DeskRequest::Fork { deciders, fork_id, .. } => self.mind.desk_fork(&deciders, &fork_id),

View file

@ -483,9 +483,8 @@ fn showable(question: &PipelineQuestion, deciders: &[Short]) -> bool {
impl<S: MindStore> Mind<S> {
/// One desk answer from the door: the wire request converted, the question gated, the answer
/// decided and stored (or replayed) by `answer_desk`, and the stored answer projected with the
/// derived count at the head it was read at. The flag is `true` when nothing was committed (a
/// replay); it is for the door and is never encoded. `subject` and `authority` are the verified
/// decided and stored (or committed) by `answer_desk`, and the stored answer projected with the
/// derived count at the head it was read at. `subject` and `authority` are the verified
/// caller's, `deciders` the door's list.
pub fn desk_answer(
&mut self,
@ -494,10 +493,10 @@ impl<S: MindStore> Mind<S> {
authority: DeskAuthority,
request: ForkAnswerRequest,
now: DateTime<Utc>,
) -> (DeskResponse, bool) {
) -> DeskResponse {
match self.desk_response(deciders, subject, authority, &request, now) {
Ok(done) => done,
Err(refused) => (door_refusal(refused), false),
Err(refused) => door_refusal(refused),
}
}
@ -508,10 +507,10 @@ impl<S: MindStore> Mind<S> {
authority: DeskAuthority,
request: &ForkAnswerRequest,
now: DateTime<Utc>,
) -> Result<(DeskResponse, bool), MindRefusal> {
) -> Result<DeskResponse, MindRefusal> {
let sent = match request_of(request) {
Ok(sent) => sent,
Err(refused) => return Ok((DeskResponse::NotStored(refused), false)),
Err(refused) => return Ok(DeskResponse::NotStored(refused)),
};
let image = self.image()?;
let docs = Docs::at(image, image.head);
@ -520,12 +519,11 @@ impl<S: MindStore> Mind<S> {
let unshowable = bound_answer(&docs, &subject, &sent.request_id).is_none()
&& matches!(docs.find(PipelineKind::Question, &sent.fork.0), Some(PipelineDocument::Question(question)) if !showable(question, deciders));
if unshowable {
return Ok((DeskResponse::NotStored(fork_unknown()), false));
return Ok(DeskResponse::NotStored(fork_unknown()));
}
let stamp = DeskStamp { answerer: subject, authority, deciders: deciders.to_vec() };
let (key, replayed) = match self.answer_desk(stamp, sent, now) {
DeskAnswerOutcome::Stored { answer, .. } => (answer.0, false),
DeskAnswerOutcome::Replayed { answer, .. } => (answer.0, true),
let key = match self.answer_desk(stamp, sent, now) {
DeskAnswerOutcome::Stored { answer, .. } | DeskAnswerOutcome::Replayed { answer, .. } => answer.0,
DeskAnswerOutcome::Refused(refused) => return Err(refused),
};
let image = self.image()?;
@ -533,7 +531,7 @@ impl<S: MindStore> Mind<S> {
let Some(PipelineDocument::DeskAnswer(stored)) = docs.find(PipelineKind::DeskAnswer, &key) else { return Err(unreadable_key()) };
let fork = stored.fork.0.as_str();
let counted_answer_id = counted_answer(&docs, fork).map(|counted| answer_seq(fork, &counted).map(n_text).ok_or_else(unreadable_key)).transpose()?;
Ok((DeskResponse::Answered { answer: project(&key, stored)?, counted_answer_id, as_of: image.head }, replayed))
Ok((DeskResponse::Answered { answer: project(&key, stored)?, counted_answer_id, as_of: image.head }))
}
}
@ -621,8 +619,12 @@ mod tests {
list.iter().map(|who| s(who)).collect()
}
/// The response and whether the call committed anything: a replay, a refused body and a gate
/// refusal store nothing, so the head does not move.
fn ask_as(mind: &mut Mind<MemoryStore>, who: &str, list: &[&str], request: ForkAnswerRequest) -> (DeskResponse, bool) {
mind.desk_answer(&deciders(list), s(who), DeskAuthority::Member, request, now())
let before = head(mind);
let response = mind.desk_answer(&deciders(list), s(who), DeskAuthority::Member, request, now());
(response, head(mind) != before)
}
fn ask(mind: &mut Mind<MemoryStore>, who: &str, request: ForkAnswerRequest) -> (DeskResponse, bool) {
@ -883,9 +885,9 @@ mod tests {
assert_eq!((answer.outcome.as_str(), answer.retracts.as_deref(), counted.as_deref(), as_of), ("Retraction", Some("n1"), Some("n2"), head(&mind)));
// The first request replayed: its stored word is still Counted, the count is read now.
let before = head(&mind);
let (response, replayed) = ask(&mut mind, "x", first);
let (response, committed) = ask(&mut mind, "x", first);
let (answer, counted, as_of) = answered(response);
assert!(replayed);
assert!(!committed);
assert_eq!((answer.outcome.as_str(), answer.answer_id.as_str(), counted.as_deref(), as_of), ("Counted", "n1", Some("n2"), before));
assert_eq!(head(&mind), before);
// The second request replayed now counts, and its stored word is still Revision.
@ -1021,14 +1023,14 @@ mod tests {
];
for (form, request) in forms.into_iter().enumerate() {
let stored = stored_count(&mind, FORK);
let (response, replayed) = ask(&mut mind, "x", request.clone());
assert!(!replayed);
let (response, committed) = ask(&mut mind, "x", request.clone());
assert!(committed);
let (answer, _, _) = answered(response.clone());
assert_eq!((answer.outcome.as_str(), answer.code), ("Refused", Some(ForkAnswerCode::BadRetraction)), "form {form}");
assert_eq!(stored_count(&mind, FORK), stored + 1, "form {form}");
// The refusal is stored and bound: the same bytes replay, with no write.
let before = head(&mind);
assert_eq!(ask(&mut mind, "x", request), (response, true), "form {form}");
assert_eq!(ask(&mut mind, "x", request), (response, false), "form {form}");
assert_eq!(head(&mind), before);
}
// (i) and (iii) with equal answer and target differ only in `retracted`: one id, two bodies.
@ -1154,14 +1156,14 @@ mod tests {
fn a_stale_answer_carries_no_card() {
let mut mind = desk();
let stale = wire(FORK, &"e".repeat(64));
let (fresh, replayed) = ask(&mut mind, "x", stale.clone());
assert!(!replayed);
let (fresh, committed) = ask(&mut mind, "x", stale.clone());
assert!(committed);
let (answer, _, _) = answered(fresh.clone());
assert_eq!((answer.outcome.as_str(), answer.code), ("Refused", Some(ForkAnswerCode::StaleFork)));
// After the fork closed, the retry still answers the stored refusal, with nothing added.
close(&mut mind, FORK, "RQ1", "A");
let (again, replayed) = ask(&mut mind, "x", stale);
assert!(replayed);
let (again, committed) = ask(&mut mind, "x", stale);
assert!(!committed);
let (again_answer, _, _) = answered(again.clone());
assert_eq!(again_answer, answer);
for response in [&fresh, &again] {
@ -1176,7 +1178,7 @@ mod tests {
let request = wire(FORK, &hash);
let (fresh, first) = ask(&mut mind, "x", request.clone());
let (replay, second) = ask(&mut mind, "x", request);
assert_eq!((first, second), (false, true));
assert_eq!((first, second), (true, false));
assert_eq!(encode(&fresh), encode(&replay));
assert_eq!(stored_count(&mind, FORK), 1);
}
@ -1225,17 +1227,17 @@ mod tests {
let mut mind = desk();
let hash = hash_of(&mind, FORK);
let request = wire(FORK, &hash);
let (stored, replayed) = ask(&mut mind, "x", request.clone());
assert!(!replayed);
let (stored, committed) = ask(&mut mind, "x", request.clone());
assert!(committed);
let before = head(&mind);
// The list Bifrost passes is empty now; the stored request still gets its stored answer.
let (again, replayed) = ask_as(&mut mind, "x", &[], request.clone());
assert!(replayed);
let (again, committed) = ask_as(&mut mind, "x", &[], request.clone());
assert!(!committed);
assert_eq!(encode(&again), encode(&stored));
assert_eq!(head(&mind), before);
// Another subject with the same bytes has stored nothing: the gate refuses it.
let (other, replayed) = ask_as(&mut mind, "y", &[], request);
assert_eq!((not_stored(other).code, replayed), (ForkRefusalCode::ForkUnknown, false));
let (other, committed) = ask_as(&mut mind, "y", &[], request);
assert_eq!((not_stored(other).code, committed), (ForkRefusalCode::ForkUnknown, false));
// And a request nobody stored is fork_unknown while the list is empty.
let fresh = wire(FORK, &hash);
assert_eq!(not_stored(ask_as(&mut mind, "x", &[], fresh).0).code, ForkRefusalCode::ForkUnknown);
@ -1243,22 +1245,22 @@ mod tests {
// A stored refusal and a stored revision replay the same way: a stale hash is stored as a
// refusal, a second answer by the same subject as a revision.
let stale = wire(FORK, "0".repeat(hash.len()).as_str());
let (refused, replayed) = ask(&mut mind, "x", stale.clone());
assert!(!replayed);
let (refused, committed) = ask(&mut mind, "x", stale.clone());
assert!(committed);
assert_eq!(answered(refused.clone()).0.outcome, "Refused");
let revision = wire(FORK, &hash);
let (revised, replayed) = ask(&mut mind, "x", revision.clone());
assert!(!replayed);
let (revised, committed) = ask(&mut mind, "x", revision.clone());
assert!(committed);
assert_eq!(answered(revised.clone()).0.outcome, "Revision");
let before = head(&mind);
for (request, stored) in [(stale, refused), (revision, revised)] {
let (again, replayed) = ask_as(&mut mind, "x", &[], request.clone());
assert!(replayed);
let (again, committed) = ask_as(&mut mind, "x", &[], request.clone());
assert!(!committed);
// `as_of` is the head the replay was read at, so the stored answer is what is compared.
assert_eq!(encode(&answered(again).0), encode(&answered(stored).0));
assert_eq!(head(&mind), before);
let (other, replayed) = ask_as(&mut mind, "w", &[], request);
assert_eq!((not_stored(other).code, replayed), (ForkRefusalCode::ForkUnknown, false));
let (other, committed) = ask_as(&mut mind, "w", &[], request);
assert_eq!((not_stored(other).code, committed), (ForkRefusalCode::ForkUnknown, false));
assert_eq!(head(&mind), before);
}
}
@ -1322,16 +1324,16 @@ mod tests {
let (first, _) = ask(&mut mind, "x", request.clone());
// V12: the same bytes replay.
let before = head(&mind);
assert_eq!(ask(&mut mind, "x", request.clone()), (first, true));
assert_eq!(ask(&mut mind, "x", request.clone()), (first, false));
assert_eq!(head(&mind), before);
// V13: only answered_at changed is a conflict, and nothing is stored.
let moved = ForkAnswerRequest { answered_at: "2026-11-10T12:00:01Z".into(), ..request.clone() };
assert_eq!(not_stored(ask(&mut mind, "x", moved).0).code, ForkRefusalCode::RequestIdConflict);
assert_eq!((head(&mind), stored_count(&mind, FORK)), (before, 1));
// V14: another subject with identical bytes is its own request: its own answer id, stamped with it.
let (other, replayed) = ask(&mut mind, "y", request.clone());
let (other, committed) = ask(&mut mind, "y", request.clone());
let (other, _, _) = answered(other);
assert!(!replayed);
assert!(committed);
assert_eq!((other.answer_id.as_str(), other.answerer.as_str(), other.outcome.as_str(), other.request_id.as_str()), ("n2", "y", "Revision", request.request_id.as_str()));
let (original, _, _) = answered(ask(&mut mind, "x", request.clone()).0);
assert_eq!((original.answer_id.as_str(), original.answerer.as_str()), ("n1", "x"));
@ -1489,7 +1491,7 @@ mod tests {
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);
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
}
@ -1545,9 +1547,9 @@ mod tests {
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;
let off = mind.desk_answer(&deciders(&["x"]), s("op"), DeskAuthority::Operator, wire(FORK, &hash), now());
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 on = mind.desk_answer(&deciders(&["m"]), s("m"), DeskAuthority::Member, wire(FORK, &hash), now());
let (answer, _, _) = answered(on);
assert_eq!((answer.outcome.as_str(), answer.authority, answer.answerer.as_str()), ("Counted", DeskAuthority::Member, "m"));
}
@ -1587,8 +1589,9 @@ mod tests {
}
// 64 deciders through to the Mind: the subject is listed and counts.
let DeskRequest::Answer { subject, authority, deciders, request, .. } = through_the_door(&desk_requests(INSTANCE, &list_of(64), &hash)[0]).unwrap() else { panic!("not an answer") };
let (response, replayed) = mind.desk_answer(&deciders, subject, authority, request, now());
assert!(!replayed);
let before = head(&mind);
let response = mind.desk_answer(&deciders, subject, authority, request, now());
assert!(head(&mind) > before);
assert_eq!(answered(response).0.outcome, "Counted");
let (head_before, stored_before) = (head(&mind), stored_count(&mind, FORK));
@ -1622,7 +1625,7 @@ mod tests {
assert_eq!((head(&mind), stored_count(&mind, FORK)), before);
// The Mind's showability gate is the second line: an empty list handed to it directly
// still refuses the fork and stores nothing.
let refused = not_stored(mind.desk_answer(&[], s("d0"), DeskAuthority::Member, wire(FORK, &hash), now()).0);
let refused = not_stored(mind.desk_answer(&[], s("d0"), DeskAuthority::Member, wire(FORK, &hash), now()));
assert_eq!(refused.code, ForkRefusalCode::ForkUnknown);
assert_eq!((head(&mind), stored_count(&mind, FORK)), before);
}
@ -1643,6 +1646,6 @@ mod tests {
// The same request to the mind it names is answered.
let DeskRequest::Answer { mind: named, subject, authority, deciders, request } = through_the_door(&desk_requests(INSTANCE, &list_of(4), &hash)[0]).unwrap() else { panic!("not an answer") };
mind.require_instance(&named).unwrap();
assert_eq!(answered(mind.desk_answer(&deciders, subject, authority, request, now()).0).0.outcome, "Counted");
assert_eq!(answered(mind.desk_answer(&deciders, subject, authority, request, now())).0.outcome, "Counted");
}
}

View file

@ -461,7 +461,7 @@ mod tests {
answered_at: "2026-09-16T11:00:00Z".into(),
request_id: request_id(),
};
mind.desk_answer(&list(deciders), s(who), DeskAuthority::Member, request, now()).0
mind.desk_answer(&list(deciders), s(who), DeskAuthority::Member, request, now())
}
fn close(mind: &mut Mind<MemoryStore>, fork: &str, label: &str) {
@ -755,7 +755,7 @@ mod tests {
answered_at: "2026-09-16T11:00:00Z".into(),
request_id: request_id(),
};
let stored = mind.desk_answer(&list(&LIST), s("x"), DeskAuthority::Member, retract, now()).0;
let stored = mind.desk_answer(&list(&LIST), s("x"), DeskAuthority::Member, retract, now());
assert!(matches!(stored, DeskResponse::Answered { .. }));
let (after, _) = entry(mind.desk_fork(&list(&LIST), &fork(1)));
assert_eq!(after.record.counted_answer_id.as_deref(), Some("n2"));