From 7218c4a9d7c603900902ce081187db93ef4e9a88 Mon Sep 17 00:00:00 2001 From: Eureka Date: Sun, 8 Nov 2026 19:32:01 +0000 Subject: [PATCH 1/2] admission: an import replays a cite of another root's closed follow-up A resolution's cited referent had to be in force in the receiver. When the receiver had already imported the other root (cultlib-gaps, aetheria-ew) and that referent closed later in the giver, the replay of the earlier Deferred cite was refused with CitesResolvedDocument and the import stopped. The cite was in force when the giver made it; an import now asks only that the foreign referent is held (A7), not that it is still open. Same-root cites replay in the giver's order and keep the rule; native writes keep it. Co-Authored-By: Claude Sonnet 5.5 --- crates/huginn-mind/src/admission.rs | 13 +++++++--- crates/huginn-mind/src/move_tests.rs | 36 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/crates/huginn-mind/src/admission.rs b/crates/huginn-mind/src/admission.rs index a316b34..a1a2b29 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -717,7 +717,7 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo Ok(()) } D::Resolution(resolution) => { - resolution_rule(docs, resolution)?; + resolution_rule(docs, resolution, import.is_some())?; match &resolution.outcome { ResolutionOutcome::Moved { to } if to == mind => Err(MindRefusal::MoveToSelf), // The campaign's root and the mind's own instance root are one namespace: the freeze would @@ -907,7 +907,13 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool { /// every referent in force, and the two /// coherence rules (an `Answered` ruling answers this question; a cut spec is /// superseded within its cut). -fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution) -> Result<(), MindRefusal> { +/// +/// An import replays the giver's judgement of the cite as the giver made it, and the receiver may +/// hold a referent of another root whose closing it imported earlier (`replay`): that referent was +/// in force when the giver cited it, and the replay of this root cannot reorder another root's +/// history. The referent must still be held (A7); only its force is not asked. A referent of the +/// subject's own root replays in the giver's order and is judged as ever. +fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution, replay: bool) -> Result<(), MindRefusal> { use PipelineDocument as D; use PipelineKind as K; let subject_kind = resolution.subject.kind; @@ -977,7 +983,8 @@ fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution) -> Result<( } for (role, referent) in outcome_citations(&resolution.outcome) { // The last memory a night was shown may have been consumed by that night's own batches. - if role != CitationRole::DayThrough && !docs.in_force(referent.kind, &referent.id.0) { + let foreign_replay = replay && root_and_local(&referent.id.0).0 != root_and_local(subject_id).0; + if role != CitationRole::DayThrough && !foreign_replay && !docs.in_force(referent.kind, &referent.id.0) { return Err(MindRefusal::CitesResolvedDocument { kind: referent.kind, id: referent.id.0 }); } } diff --git a/crates/huginn-mind/src/move_tests.rs b/crates/huginn-mind/src/move_tests.rs index 3bd200a..b2c7128 100644 --- a/crates/huginn-mind/src/move_tests.rs +++ b/crates/huginn-mind/src/move_tests.rs @@ -638,3 +638,39 @@ fn a_campaign_cannot_be_moved_out_of_its_minds_own_instance_root() { MindRefusal::ImportOutsideCampaign { root: RECEIVER.into() } ); } + +/// The giver cited another root's follow-up while it stood open and that follow-up closed later; +/// the receiver imported the other root first, so it holds the follow-up already closed. The +/// replay of this root's citation is not refused for that. A follow-up of the root itself is +/// replayed in the giver's order and is still judged in force, and a native write meets the rule. +#[test] +fn import_replays_a_cite_of_another_roots_follow_up_that_closed_since() { + let mut mind = receiver(); + committed(admit(&mut mind, vec![other_question("Q5"), other_follow_up("FU-9", "Q5")])); + let foreign = r(K::FollowUp, &format!("{OTHER_ROOT}:follow_up:FU-9")); + committed(admit(&mut mind, vec![resolution(foreign.clone(), ResolutionOutcome::Recorded { reason: "done".into() })])); + assert!(matches!(view_of(&mind, K::FollowUp, &foreign.id.0).status, PipelineStatus::Resolved { .. })); + + let note = idea("p", "note", "an idea"); + let note_ref = r(K::Idea, &pipeline_key(¬e).unwrap()); + let own = follow_up("FU-1", r(K::Question, &id("question", "Q1"))); + let own_ref = r(K::FollowUp, &pipeline_key(&own).unwrap()); + committed(import(&mut mind, vec![campaign(&[REPO]), question("Q1", &["A", "B"], "A"), own.clone(), note.clone()], vec![])); + committed(import(&mut mind, vec![resolution(own_ref.clone(), ResolutionOutcome::Recorded { reason: "done".into() })], vec![])); + + let defer = |to: &PipelineRef| resolution(note_ref.clone(), ResolutionOutcome::Deferred { to: to.clone() }); + // The same-root referent is closed in this receiver as it was in the giver: still refused. + assert_eq!( + refusal(import(&mut mind, vec![defer(&own_ref)], vec![])), + MindRefusal::CitesResolvedDocument { kind: K::FollowUp, id: own_ref.id.0.clone() } + ); + // A native write is not a replay, and meets the rule. + assert_eq!( + refusal(admit(&mut mind, vec![defer(&foreign)])), + MindRefusal::CitesResolvedDocument { kind: K::FollowUp, id: foreign.id.0.clone() } + ); + // The replay of the foreign cite lands, and the follow-up it cites is untouched. + let (_, writes) = committed(import(&mut mind, vec![defer(&foreign)], vec![])); + assert_eq!(writes.len(), 1); + assert!(matches!(view_of(&mind, K::Idea, ¬e_ref.id.0).status, PipelineStatus::Resolved { .. })); +} From 971d96212f5b3041af64d7e188d5e263508ab79d Mon Sep 17 00:00:00 2001 From: Eureka Date: Sun, 8 Nov 2026 20:02:05 +0000 Subject: [PATCH 2/2] move tests: pin the real Finding case and the kind-agnostic waiver The replay of a cite of another root's closed document is pinned with a Finding deferred to a closed follow-up (the thing2 and aetheria-release cases) and an Idea superseded by a recorded Idea of another root, which kills a waiver limited to follow-ups. Co-Authored-By: Claude Sonnet 5.5 --- crates/huginn-mind/src/move_tests.rs | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/huginn-mind/src/move_tests.rs b/crates/huginn-mind/src/move_tests.rs index b2c7128..298f27b 100644 --- a/crates/huginn-mind/src/move_tests.rs +++ b/crates/huginn-mind/src/move_tests.rs @@ -674,3 +674,44 @@ fn import_replays_a_cite_of_another_roots_follow_up_that_closed_since() { assert_eq!(writes.len(), 1); assert!(matches!(view_of(&mind, K::Idea, ¬e_ref.id.0).status, PipelineStatus::Resolved { .. })); } + +/// The real cases (thing2 ordinal 1815, aetheria-release ordinal 2247): a Finding of the imported +/// root deferred to another root's follow-up that the receiver holds already closed. +#[test] +fn import_replays_a_finding_deferred_to_another_roots_closed_follow_up() { + let mut mind = receiver(); + committed(admit(&mut mind, vec![other_question("Q5"), other_follow_up("FU-9", "Q5")])); + let foreign = r(K::FollowUp, &format!("{OTHER_ROOT}:follow_up:FU-9")); + committed(admit(&mut mind, vec![resolution(foreign.clone(), ResolutionOutcome::Fixed { commit: sha(), by: None })])); + let q1 = id("question", "Q1"); + let finding = id("finding", "cut-1.s1.F1"); + committed(import(&mut mind, vec![campaign(&[REPO]), target(1, &[INVARIANT]), question("Q1", &["A", "B"], "A"), answering(CAMPAIGN, "R1", &q1)], vec![])); + committed(import(&mut mind, vec![D::CutSpec(cut_spec("1", 1))], vec![])); + committed(import(&mut mind, vec![D::CutReport(cut_report("1", 1))], vec![])); + committed(import( + &mut mind, + vec![verdict("1", 1, vec![claim(ClaimOutcome::Falsified, &[&finding], Some("P1"), &["M1"])]), D::Finding(finding_doc())], + vec![], + )); + let (_, writes) = committed(import(&mut mind, vec![resolution(r(K::Finding, &finding), ResolutionOutcome::Deferred { to: foreign })], vec![])); + assert_eq!(writes.len(), 1); + assert!(matches!(view_of(&mind, K::Finding, &finding).status, PipelineStatus::Resolved { .. })); +} + +/// The replay does not ask a cited referent of another root to be open, whatever its kind or how +/// it closed: an idea superseded by another root's idea that is already recorded. +#[test] +fn import_replays_a_cite_of_another_roots_closed_document_of_any_kind() { + let mut mind = receiver(); + let mut other = idea("p", "other", "x"); + let D::Idea(value) = &mut other else { unreachable!() }; + value.campaign = slug(OTHER_ROOT); + let other_ref = r(K::Idea, &pipeline_key(&other).unwrap()); + committed(admit(&mut mind, vec![other])); + committed(admit(&mut mind, vec![resolution(other_ref.clone(), ResolutionOutcome::Recorded { reason: "x".into() })])); + let note = idea("p", "note", "an idea"); + let note_ref = r(K::Idea, &pipeline_key(¬e).unwrap()); + committed(import(&mut mind, vec![campaign(&[REPO]), note], vec![])); + committed(import(&mut mind, vec![resolution(note_ref.clone(), ResolutionOutcome::Superseded { by: vec![other_ref] })], vec![])); + assert!(matches!(view_of(&mind, K::Idea, ¬e_ref.id.0).status, PipelineStatus::Resolved { .. })); +}