diff --git a/crates/huginn-mind/src/desk_wire.rs b/crates/huginn-mind/src/desk_wire.rs index a5521dd..2809998 100644 --- a/crates/huginn-mind/src/desk_wire.rs +++ b/crates/huginn-mind/src/desk_wire.rs @@ -294,10 +294,15 @@ fn project(key: &str, stored: &PipelineDeskAnswer) -> Result DeskResponse { match refused { MindRefusal::MissingReference { .. } | MindRefusal::CampaignMoved { .. } => DeskResponse::NotStored(fork_unknown()), - MindRefusal::DeskRequestInvalid { field } if field == "fork" || field == "desk_answer.fork" => DeskResponse::NotStored(fork_unknown()), + // Any bad fork id is `fork_unknown`. The Mind derives these four fields from the fork id alone + // (the id's shape, its campaign root, the record key built from it), so each one is the fork. + MindRefusal::DeskRequestInvalid { field } if FORK_FIELDS.contains(&field.as_str()) => DeskResponse::NotStored(fork_unknown()), MindRefusal::DeskRequestInvalid { field } => DeskResponse::NotStored(body_invalid(&field)), MindRefusal::DeskRequestIdConflict { .. } => { DeskResponse::NotStored(fork_refusal(ForkRefusalCode::RequestIdConflict, "request_id_conflict: this request_id was used for a different body".into())) @@ -924,7 +929,7 @@ mod tests { } assert_eq!(head(&mind), before); // The mapping is by exact field: a field that merely ends in "fork" is a bad body. - for field in ["desk_answer.fork", "fork"] { + for field in ["desk_answer.fork", "fork", "desk_answer.key", "desk_answer.campaign"] { assert_eq!(not_stored(door_refusal(MindRefusal::DeskRequestInvalid { field: field.into() })).code, ForkRefusalCode::ForkUnknown, "{field}"); } for field in ["notfork", "desk_answer.forks", "desk_answer.fork.x", "request"] { @@ -932,6 +937,34 @@ mod tests { } } + /// Any bad fork id is `fork_unknown`, whichever field the Mind faults: the id's shape, its + /// campaign root or the record key built from it. A bad field that is not the fork stays `body_invalid`. + #[test] + fn every_bad_fork_id_is_unknown_and_other_bad_fields_are_not() { + let mut mind = desk(); + let hash = hash_of(&mind, FORK); + let before = head(&mind); + let forks = [ + "", // fork + "eureka-state:epic:Q1", // fork: not a question id + "eureka-state:question:Q 1", // desk_answer.fork + ":question:Q1", // desk_answer.campaign + "Bad Root:question:Q1", // desk_answer.campaign + "a:question:Q1.2", // desk_answer.key + "eureka-state:question:Q99", // a well-formed id nothing holds + ]; + for fork in forks { + let refusal = not_stored(ask(&mut mind, "x", wire(fork, &hash)).0); + assert_eq!(refusal, fork_unknown(), "{fork:?}"); + assert_eq!(head(&mind), before, "{fork:?}"); + } + let bad_choice = ForkAnswerRequest { answer: Some(ForkAnswer { choice: Some(String::new()), note: None }), ..wire(FORK, &hash) }; + let refusal = not_stored(ask(&mut mind, "x", bad_choice).0); + assert_eq!(refusal.code, ForkRefusalCode::BodyInvalid); + assert!(refusal.error.contains("desk_answer.choice"), "{refusal:?}"); + assert_eq!(head(&mind), before); + } + #[test] fn wire_codes_are_snake_case() { let table = [