diff --git a/crates/huginn-mind/src/admission.rs b/crates/huginn-mind/src/admission.rs index 8847d46..121721c 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -645,11 +645,12 @@ fn outcome_name(outcome: &ResolutionOutcome) -> &'static str { /// The resolution matrix: which outcomes a subject kind admits, and of which /// kinds its referents must be. Admission's, never the leaf's. /// -/// A subject resolves at most once, and a resolution is not itself -/// resolvable: `resolution` joins `campaign`, `cut_report`, `verdict`, -/// `instance` and `hand_off` in admitting no outcome at all. The key grammar -/// can still spell the resolution of a resolution; this is where it is -/// refused. (Q17, Self's default ruling pending the operator.) +/// A resolution is itself resolvable, by withdrawal alone: the operator's +/// ruling on Q17 keeps a withdrawn resolution attached to its subject rather +/// than erasing it, and a subject whose resolution is withdrawn may be +/// resolved again. The second resolution needs a key the first does not +/// already hold, which is a sequence in the leaf's resolution key and is +/// mapped separately. fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool { use PipelineKind as K; use ResolutionOutcome as O; @@ -669,6 +670,7 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool { (K::FollowUp, O::Withdrawn { .. }) => true, (K::Stewardship, O::Superseded { by }) => all(by, K::Stewardship), (K::Stewardship, O::Withdrawn { .. }) => true, + (K::Resolution, O::Withdrawn { .. }) => true, _ => false, }; fits @@ -1035,10 +1037,14 @@ mod tests { committed(admit(&mut world(), vec![resolution(finding_ref.clone(), ResolutionOutcome::Deferred { to: follow_up_ref.clone() })])); committed(admit(&mut world(), vec![resolution(follow_up_ref.clone(), fixed())])); committed(admit(&mut world(), vec![resolution(stewardship_ref.clone(), withdrawn())])); + let mut mind = world(); + committed(admit(&mut mind, vec![resolution(question_ref.clone(), withdrawn())])); + let nested = r(K::Resolution, &id("resolution", "question.Q1")); + committed(admit(&mut mind, vec![resolution(nested.clone(), withdrawn())])); // Refused, one per kind, and every non-resolvable kind. assert_eq!(refusal(admit(&mut world(), vec![resolution(target_ref, withdrawn())])), incompatible(K::Target, "Withdrawn")); - assert_eq!(refusal(admit(&mut world(), vec![resolution(question_ref.clone(), fixed())])), incompatible(K::Question, "Fixed")); + assert_eq!(refusal(admit(&mut world(), vec![resolution(question_ref, fixed())])), incompatible(K::Question, "Fixed")); assert_eq!(refusal(admit(&mut world(), vec![resolution(ruling_ref, withdrawn())])), incompatible(K::Ruling, "Withdrawn")); assert_eq!( refusal(admit(&mut world(), vec![resolution(spec_ref.clone(), ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) })])), @@ -1063,16 +1069,8 @@ mod tests { refusal(admit(&mut world(), vec![resolution(stewardship_ref, ResolutionOutcome::Recorded { reason: "no".into() })])), incompatible(K::Stewardship, "Recorded") ); - // A resolution records that a subject resolved; it is not itself a - // subject. The nested key exists and is well formed, and admission - // refuses it anyway, whatever the outcome. let mut mind = world(); - committed(admit(&mut mind, vec![resolution(question_ref.clone(), withdrawn())])); - let nested = r(K::Resolution, &id("resolution", "question.Q1")); - assert_eq!( - refusal(admit(&mut mind, vec![resolution(nested.clone(), withdrawn())])), - incompatible(K::Resolution, "Withdrawn") - ); + committed(admit(&mut mind, vec![resolution(r(K::Question, &id("question", "Q1")), withdrawn())])); assert_eq!( refusal(admit(&mut mind, vec![resolution(nested.clone(), superseded(&[nested]))])), incompatible(K::Resolution, "Superseded")