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:
parent
3b775a23c8
commit
aeeb36ad2a
3 changed files with 53 additions and 50 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue