huginn-mind: a moved campaign's closer is the Moved alone; the external listing names its referent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-08 18:07:27 +01:00
commit b65eaa1fa9
3 changed files with 17 additions and 12 deletions

View file

@ -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,
})
}

View file

@ -204,10 +204,8 @@ impl Image {
// A Moved closes every document under its campaign's root, none of which it names.
let moved: Vec<String> = (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,
})

View file

@ -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")))]));