diff --git a/crates/huginn-mind/src/docs.rs b/crates/huginn-mind/src/docs.rs index da817ca..71e08d3 100644 --- a/crates/huginn-mind/src/docs.rs +++ b/crates/huginn-mind/src/docs.rs @@ -216,9 +216,11 @@ impl<'i> Docs<'i> { }) } - /// The Moved resolution in force over the campaign rooted at `root`, among the held documents - /// visible at `as_of`: the one fact that freezes a root and closes everything under it. The - /// batch is not asked, so a Moved and the writes it lands beside are one receipt. + /// The Moved resolution over the campaign rooted at `root`, among the held documents visible at + /// `as_of`: the one fact that freezes a root and closes everything under it. A campaign takes no + /// other resolution and a Moved cannot be withdrawn (admission refuses both), so a campaign's + /// closers are this one alone and it is always in force. The batch is not asked, so a Moved and + /// the writes it lands beside are one receipt. pub(crate) fn moved_campaign(&self, root: &str) -> Option<(&'i str, &'i PipelineResolution)> { let image: &'i Image = self.image; image @@ -227,11 +229,7 @@ impl<'i> Docs<'i> { .flat_map(|(key, _)| image.closers_of(PipelineKind::Campaign, key)) .filter(|(_, row)| self.visible(*row)) .find_map(|(key, row)| match &image.row(row).view.document { - PipelineDocument::Resolution(resolution) - if matches!(resolution.outcome, ResolutionOutcome::Moved { .. }) && self.in_force(PipelineKind::Resolution, key) => - { - Some((key, resolution)) - } + PipelineDocument::Resolution(resolution @ PipelineResolution { outcome: ResolutionOutcome::Moved { .. }, .. }) => Some((key, resolution)), _ => None, }) } diff --git a/crates/huginn-mind/src/image.rs b/crates/huginn-mind/src/image.rs index 6d2819f..ca4c04c 100644 --- a/crates/huginn-mind/src/image.rs +++ b/crates/huginn-mind/src/image.rs @@ -204,10 +204,8 @@ impl Image { // A Moved closes every document under its campaign's root, none of which it names. let moved: Vec = (first..self.rows.len() as u32) .filter_map(|id| match &self.row(id).view.document { - PipelineDocument::Resolution(resolution) - if resolution.subject.kind == PipelineKind::Campaign && matches!(resolution.outcome, ResolutionOutcome::Moved { .. }) => - { - Some(root_and_local(&resolution.subject.id.0).0.to_string()) + PipelineDocument::Resolution(PipelineResolution { subject, outcome: ResolutionOutcome::Moved { .. }, .. }) => { + Some(root_and_local(&subject.id.0).0.to_string()) } _ => None, }) diff --git a/crates/huginn-mind/src/move_tests.rs b/crates/huginn-mind/src/move_tests.rs index 2fb2768..5037e22 100644 --- a/crates/huginn-mind/src/move_tests.rs +++ b/crates/huginn-mind/src/move_tests.rs @@ -332,6 +332,15 @@ fn import_admits_a_declared_external_and_refuses_an_undeclared_one() { assert!(matches!(view_of(&mind, K::FollowUp, &id("follow_up", "FU-1")).status, PipelineStatus::Resolved { .. })); assert!(mind.view(&report).unwrap().is_none(), "an external is stored nowhere"); + // A listing names the referent it stands in for and no other of its kind. + let mut elsewhere = receiver(); + committed(admit(&mut elsewhere, vec![campaign(&[REPO]), question("Q1", &["A", "B"], "A"), follow_up("FU-1", r(K::Question, &id("question", "Q1")))])); + let other = r(K::CutReport, &format!("{OTHER_ROOT}:cut_report:cut-4.h1")); + assert_eq!( + refusal(import(&mut elsewhere, vec![fixed.clone()], vec![other])), + MindRefusal::MissingReference { kind: K::CutReport, id: report.id.0.clone() } + ); + // Outside an import, the same document is refused: externals are an import's alone. let mut plain = receiver(); committed(admit(&mut plain, vec![campaign(&[REPO]), question("Q1", &["A", "B"], "A"), follow_up("FU-1", r(K::Question, &id("question", "Q1")))]));