From fb0ae7540d0650d34da456070985763e6424cdad Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 31 Oct 2026 16:30:28 +0100 Subject: [PATCH] huginn-mind: a follow-up closes Recorded{reason}, as a finding and a run do Ruling eureka-body:ruling:brief-closure-recorded. Co-Authored-By: Claude Sonnet 5.5 --- crates/huginn-mind/src/admission.rs | 1 + crates/huginn-mind/src/query.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/crates/huginn-mind/src/admission.rs b/crates/huginn-mind/src/admission.rs index eb1b6bc..fc86246 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -711,6 +711,7 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool { (K::Finding, O::Deferred { to }) => matches!(to.kind, K::FollowUp | K::CutSpec), (K::Finding, O::Recorded { .. } | O::Withdrawn { .. }) => true, (K::FollowUp, O::Fixed { by, .. }) => by.as_ref().is_none_or(|report| report.kind == K::CutReport), + (K::FollowUp, O::Recorded { .. }) => true, (K::FollowUp, O::Superseded { by }) => all(by, K::FollowUp), (K::FollowUp, O::Withdrawn { .. }) => true, (K::Stewardship, O::Superseded { by }) => all(by, K::Stewardship), diff --git a/crates/huginn-mind/src/query.rs b/crates/huginn-mind/src/query.rs index c4d00cb..12996ce 100644 --- a/crates/huginn-mind/src/query.rs +++ b/crates/huginn-mind/src/query.rs @@ -1766,6 +1766,34 @@ mod tests { assert_eq!(open(standing(of_kinds(&[K::Question]))).len(), 2); } + /// A follow-up answered in the doing closes Recorded: it commits, and the + /// follow-up stops reading as open work. Outcomes the follow-up row does + /// not name stay refused. + #[test] + fn a_follow_up_recorded_is_no_longer_open() { + let mut mind = seeded(); + committed(admit(&mut mind, elsewhere("Q1"))); + committed(admit(&mut mind, vec![question_n(1)])); + let q1 = id("question", "Q1"); + committed(admit(&mut mind, vec![follow_up("FU-1", r(K::Question, &q1)), follow_up("FU-2", r(K::Question, &q1))])); + let open_follow_ups = |mind: &Mind| { + let selection = with(with(of_kinds(&[K::FollowUp]), any_of("root", &[CAMPAIGN])), any_of("in_force", &["true"])); + ids(&mind.query(&selection).unwrap()) + }; + assert_eq!(open_follow_ups(&mind), vec![id("follow_up", "FU-1"), id("follow_up", "FU-2")]); + + let fu1 = r(K::FollowUp, &id("follow_up", "FU-1")); + committed(admit(&mut mind, vec![resolution(fu1, ResolutionOutcome::Recorded { reason: "answered: done".into() })])); + assert_eq!(open_follow_ups(&mind), vec![id("follow_up", "FU-2")]); + + let fu2 = r(K::FollowUp, &id("follow_up", "FU-2")); + assert!( + admit(&mut mind, vec![resolution(fu2, ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) })]).is_err(), + "Answered stays a question's outcome" + ); + assert_eq!(open_follow_ups(&mind), vec![id("follow_up", "FU-2")]); + } + fn hit(kind: K, key: &str, score: f32) -> (PipelineRef, f32) { (r(kind, key), score) }