From 84a7dec11026e6eec4244b28a48e096ca94d01c2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 22 Aug 2026 17:27:40 +0200 Subject: [PATCH] Persist resident operator objectives in Mind --- .../src/bin/epiphany-mvp-coordinator.rs | 82 +++++++++++++++++-- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index d10bd915..5bdf5c0f 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -365,15 +365,29 @@ fn run_coordinator(args: &Args) -> Result { claim, objective, )?; + let source_ref = format!("resident-self-grant://{}", claim.grant_id); + let intake = intake_operator_objective( + &runtime_store, + &thread_id, + objective, + &source_ref, + )?; startup_events.push(json!({ "type": "residentObjectiveBinding", "threadId": thread_id, "grantId": claim.grant_id, "preparationId": preparation_id, - "canonicalObjectiveChanged": false, + "mindProjectionDigest": intake.mind.projection_digest, + "commitReceiptId": intake.commit_receipt.receipt_id, + "canonicalObjectiveChanged": intake.changed, })); } else { - let intake = intake_operator_objective(&runtime_store, &thread_id, objective)?; + let intake = intake_operator_objective( + &runtime_store, + &thread_id, + objective, + "cli://epiphany-mvp-coordinator", + )?; startup_events.push(json!({ "type": "operatorObjectiveIntake", "threadId": thread_id, @@ -1516,6 +1530,7 @@ fn intake_operator_objective( runtime_store: &Path, thread_id: &str, objective: &str, + source_ref: &str, ) -> Result { epiphany_core::intake_user_objective( runtime_store, @@ -1523,7 +1538,7 @@ fn intake_operator_objective( thread_id: thread_id.to_string(), objective: objective.to_string(), source_actor: "operator".to_string(), - source_ref: "cli://epiphany-mvp-coordinator".to_string(), + source_ref: source_ref.to_string(), submitted_at: now(), }, ) @@ -2388,11 +2403,21 @@ mod tests { }, )?; - let first = intake_operator_objective(&store, "thread-1", "Map the machine")?; + let first = intake_operator_objective( + &store, + "thread-1", + "Map the machine", + "cli://epiphany-mvp-coordinator", + )?; assert!(first.changed); assert_eq!(first.mind.objective.as_deref(), Some("Map the machine")); - let repeated = intake_operator_objective(&store, "thread-1", " Map the machine ")?; + let repeated = intake_operator_objective( + &store, + "thread-1", + " Map the machine ", + "cli://epiphany-mvp-coordinator", + )?; assert!(!repeated.changed); assert_eq!( repeated.mind.projection_digest, @@ -2400,12 +2425,55 @@ mod tests { ); assert_eq!(repeated.commit_receipt, first.commit_receipt); - let error = intake_operator_objective(&store, "thread-1", "Replace the machine") - .expect_err("objective replacement must require a typed adoption flow"); + let error = intake_operator_objective( + &store, + "thread-1", + "Replace the machine", + "cli://epiphany-mvp-coordinator", + ) + .expect_err("objective replacement must require a typed adoption flow"); assert!(error.to_string().contains("refusing to replace")); Ok(()) } + #[test] + fn resident_operator_objective_uses_the_authenticated_grant_as_provenance() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("runtime.cc"); + epiphany_core::initialize_runtime_spine( + &store, + epiphany_core::RuntimeSpineInitOptions { + runtime_id: "resident-objective-intake-bin".into(), + display_name: "Resident objective intake bin".into(), + created_at: "2026-08-22T00:00:00Z".into(), + }, + )?; + + let applied = intake_operator_objective( + &store, + "resident-thread", + "Keep the objective in Mind", + "resident-self-grant://grant-1", + )?; + assert!(applied.changed); + assert_eq!( + applied.intake.source_ref, + "resident-self-grant://grant-1" + ); + assert_eq!( + applied.mind.objective.as_deref(), + Some("Keep the objective in Mind") + ); + assert!( + applied + .commit_receipt + .writes + .iter() + .any(|write| write.document_key == epiphany_core::MIND_OBJECTIVE_KEY) + ); + Ok(()) + } + #[test] fn operator_can_approve_only_a_self_derived_manual_regather() -> Result<()> { let mut coordinator = json!({