1use super::*;
2
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CommsDrainPhase {
12 Inactive,
13 Starting,
14 Running,
15 ExitedRespawnable,
16 Stopped,
17}
18
19impl fmt::Display for CommsDrainPhase {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Self::Inactive => write!(f, "Inactive"),
23 Self::Starting => write!(f, "Starting"),
24 Self::Running => write!(f, "Running"),
25 Self::ExitedRespawnable => write!(f, "ExitedRespawnable"),
26 Self::Stopped => write!(f, "Stopped"),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum CommsDrainMode {
34 Timed,
36 AttachedSession,
38 PersistentHost,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum DrainExitReason {
45 IdleTimeout,
46 Dismissed,
47 Failed,
48 Aborted,
49 SessionShutdown,
50}
51
52impl From<DrainExitReason> for crate::meerkat_machine::dsl::DrainExitReason {
53 fn from(reason: DrainExitReason) -> Self {
54 match reason {
55 DrainExitReason::IdleTimeout => Self::IdleTimeout,
56 DrainExitReason::Dismissed => Self::Dismissed,
57 DrainExitReason::Failed => Self::Failed,
58 DrainExitReason::Aborted => Self::Aborted,
59 DrainExitReason::SessionShutdown => Self::SessionShutdown,
60 }
61 }
62}
63
64impl From<DrainExitReason> for meerkat_core::handles::DrainExitReason {
65 fn from(reason: DrainExitReason) -> Self {
66 match reason {
67 DrainExitReason::IdleTimeout => Self::IdleTimeout,
68 DrainExitReason::Dismissed => Self::Dismissed,
69 DrainExitReason::Failed => Self::Failed,
70 DrainExitReason::Aborted => Self::Aborted,
71 DrainExitReason::SessionShutdown => Self::SessionShutdown,
72 }
73 }
74}
75
76impl From<crate::meerkat_machine::dsl::DrainPhase> for CommsDrainPhase {
77 fn from(phase: crate::meerkat_machine::dsl::DrainPhase) -> Self {
78 match phase {
79 crate::meerkat_machine::dsl::DrainPhase::Inactive => Self::Inactive,
80 crate::meerkat_machine::dsl::DrainPhase::Running => Self::Running,
81 crate::meerkat_machine::dsl::DrainPhase::Stopped => Self::Stopped,
82 crate::meerkat_machine::dsl::DrainPhase::ExitedRespawnable => Self::ExitedRespawnable,
83 }
84 }
85}
86
87impl From<crate::meerkat_machine::dsl::DrainMode> for CommsDrainMode {
88 fn from(mode: crate::meerkat_machine::dsl::DrainMode) -> Self {
89 match mode {
90 crate::meerkat_machine::dsl::DrainMode::Timed => Self::Timed,
91 crate::meerkat_machine::dsl::DrainMode::AttachedSession => Self::AttachedSession,
92 crate::meerkat_machine::dsl::DrainMode::PersistentHost => Self::PersistentHost,
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum PeerIngressOwner {
106 Unattached,
107 SessionOwned {
108 comms_runtime_id: crate::meerkat_machine::dsl::CommsRuntimeId,
109 },
110 MobOwned {
111 comms_runtime_id: crate::meerkat_machine::dsl::CommsRuntimeId,
112 mob_id: crate::meerkat_machine::dsl::MobId,
113 },
114}
115
116impl PeerIngressOwner {
117 pub fn is_mob_owned(&self) -> bool {
119 matches!(self, PeerIngressOwner::MobOwned { .. })
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum SupervisorBinding {
133 Unbound,
136 Bound {
141 name: String,
142 peer_id: String,
143 address: String,
144 signing_public_key: String,
145 epoch: u64,
146 },
147}
148
149pub struct CommsDrainSlot {
150 handle: Option<tokio::task::JoinHandle<()>>,
151 task_runtime: Option<Arc<dyn meerkat_core::agent::CommsRuntime>>,
152}
153
154impl CommsDrainSlot {
155 pub fn new() -> Self {
156 Self {
157 handle: None,
158 task_runtime: None,
159 }
160 }
161
162 pub(crate) fn task_runtime_matches(
163 &self,
164 runtime: &Arc<dyn meerkat_core::agent::CommsRuntime>,
165 ) -> bool {
166 self.task_runtime
167 .as_ref()
168 .is_some_and(|current| Arc::ptr_eq(current, runtime))
169 }
170
171 pub(crate) fn task_runtime(&self) -> Option<Arc<dyn meerkat_core::agent::CommsRuntime>> {
172 self.task_runtime.clone()
173 }
174
175 pub(crate) fn handle_present(&self) -> bool {
176 self.handle.is_some()
177 }
178
179 pub(crate) fn install_task(
180 &mut self,
181 runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
182 handle: tokio::task::JoinHandle<()>,
183 ) {
184 if let Some(existing) = self.handle.take() {
185 existing.abort();
186 }
187 self.task_runtime = Some(runtime);
188 self.handle = Some(handle);
189 }
190
191 pub(crate) fn take_handle(&mut self) -> Option<tokio::task::JoinHandle<()>> {
192 self.handle.take()
193 }
194
195 pub(crate) fn clear_after_exit(&mut self, keep_runtime: bool) {
196 self.handle.take();
197 if !keep_runtime {
198 self.task_runtime = None;
199 }
200 }
201
202 pub(crate) fn abort(&mut self) {
203 self.task_runtime = None;
204 if let Some(handle) = self.handle.take() {
205 handle.abort();
206 }
207 }
208
209 pub(crate) fn abort_keeping_handle(&mut self) -> Option<tokio::task::JoinHandle<()>> {
215 self.task_runtime = None;
216 let handle = self.handle.take()?;
217 handle.abort();
218 Some(handle)
219 }
220}
221
222pub fn abort_slot(slot: &mut CommsDrainSlot) {
223 slot.abort();
224}
225
226#[derive(Debug, Clone)]
227pub(super) struct DrainAuthorityState {
228 pub phase: crate::meerkat_machine::dsl::DrainPhase,
229 pub mode: Option<crate::meerkat_machine::dsl::DrainMode>,
230 pub peer_owner_kind: crate::meerkat_machine::dsl::PeerIngressOwnerKind,
231 pub peer_runtime_id: Option<crate::meerkat_machine::dsl::CommsRuntimeId>,
232}
233
234impl DrainAuthorityState {
235 pub(super) fn can_spawn(&self) -> bool {
236 matches!(
237 self.phase,
238 crate::meerkat_machine::dsl::DrainPhase::Inactive
239 | crate::meerkat_machine::dsl::DrainPhase::Stopped
240 | crate::meerkat_machine::dsl::DrainPhase::ExitedRespawnable
241 )
242 }
243
244 pub(super) fn has_peer_runtime(
245 &self,
246 runtime_id: &crate::meerkat_machine::dsl::CommsRuntimeId,
247 ) -> bool {
248 self.peer_owner_kind != crate::meerkat_machine::dsl::PeerIngressOwnerKind::Unattached
249 && self.peer_runtime_id.as_ref() == Some(runtime_id)
250 }
251}
252
253impl MeerkatMachine {
254 async fn apply_supervisor_binding_input(
255 &self,
256 session_id: &SessionId,
257 input: crate::meerkat_machine::dsl::MeerkatMachineInput,
258 ) -> Result<crate::meerkat_machine::dsl::MeerkatMachineTransition, SupervisorBindingStageError>
259 {
260 #[cfg(target_arch = "wasm32")]
261 let mut sessions = self
262 .sessions
263 .try_write()
264 .map_err(|_| SupervisorBindingStageError::SessionRegistryBusy)?;
265 #[cfg(not(target_arch = "wasm32"))]
266 let mut sessions = self.sessions.write().await;
267
268 let entry = sessions
269 .get_mut(session_id)
270 .ok_or(SupervisorBindingStageError::SessionNotRegistered)?;
271
272 #[cfg(target_arch = "wasm32")]
273 let mut authority = entry
274 .dsl_authority
275 .try_lock()
276 .map_err(|_| SupervisorBindingStageError::SessionAuthorityBusy)?;
277 #[cfg(not(target_arch = "wasm32"))]
278 let mut authority = entry
279 .dsl_authority
280 .lock()
281 .unwrap_or_else(std::sync::PoisonError::into_inner);
282
283 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(&mut *authority, input)
284 .map_err(SupervisorBindingStageError::Dsl)
285 }
286
287 pub async fn update_peer_ingress_context(
288 self: &Arc<Self>,
289 session_id: &SessionId,
290 keep_alive: bool,
291 comms_runtime: Option<Arc<dyn meerkat_core::agent::CommsRuntime>>,
292 ) -> Result<bool, RuntimeDriverError> {
293 match self
294 .execute_meerkat_machine_drain_command(MeerkatMachineCommand::SetPeerIngressContext {
295 session_id: session_id.clone(),
296 keep_alive,
297 comms_runtime,
298 mob_id: None,
299 })
300 .await?
301 {
302 MeerkatMachineCommandResult::Spawned(spawned) => Ok(spawned),
303 other => Err(RuntimeDriverError::Internal(format!(
304 "update_peer_ingress_context: unexpected command result variant: {other:?}"
305 ))),
306 }
307 }
308
309 pub async fn maybe_spawn_comms_drain(
315 self: &Arc<Self>,
316 session_id: &SessionId,
317 keep_alive: bool,
318 comms_runtime: Option<Arc<dyn meerkat_core::agent::CommsRuntime>>,
319 ) -> Result<bool, RuntimeDriverError> {
320 match self
321 .execute_meerkat_machine_drain_command(MeerkatMachineCommand::SetPeerIngressContext {
322 session_id: session_id.clone(),
323 keep_alive,
324 comms_runtime,
325 mob_id: None,
326 })
327 .await?
328 {
329 MeerkatMachineCommandResult::Spawned(spawned) => Ok(spawned),
330 other => Err(RuntimeDriverError::Internal(format!(
331 "maybe_spawn_comms_drain: unexpected command result variant: {other:?}"
332 ))),
333 }
334 }
335
336 pub async fn refresh_session_owned_peer_ingress(
347 self: &Arc<Self>,
348 session_id: &SessionId,
349 ) -> Result<bool, RuntimeDriverError> {
350 if !self.sessions.read().await.contains_key(session_id) {
351 return Err(RuntimeDriverError::NotReady {
352 state: RuntimeState::Destroyed,
353 });
354 }
355 if matches!(
356 self.existing_session_runtime_state(session_id).await,
357 Some(RuntimeState::Destroyed)
358 ) {
359 return Err(RuntimeDriverError::Destroyed);
360 }
361
362 let gate = self.session_mutation_gate(session_id).await;
363 let _gate_guard = match gate {
364 Some(ref g) => Some(g.lock().await),
365 None => None,
366 };
367
368 let Some(comms_runtime) = self.session_owned_drain_runtime(session_id).await else {
369 return Ok(false);
370 };
371
372 self.update_peer_ingress_context_inner(session_id, true, Some(comms_runtime))
373 .await
374 }
375
376 pub async fn maybe_spawn_mob_comms_drain(
385 self: &Arc<Self>,
386 session_id: &SessionId,
387 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
388 mob_id: crate::meerkat_machine::dsl::MobId,
389 ) -> Result<bool, RuntimeDriverError> {
390 match self
391 .execute_meerkat_machine_drain_command(MeerkatMachineCommand::SetPeerIngressContext {
392 session_id: session_id.clone(),
393 keep_alive: true,
394 comms_runtime: Some(comms_runtime),
395 mob_id: Some(mob_id),
396 })
397 .await?
398 {
399 MeerkatMachineCommandResult::Spawned(spawned) => Ok(spawned),
400 other => Err(RuntimeDriverError::Internal(format!(
401 "maybe_spawn_mob_comms_drain: unexpected command result variant: {other:?}"
402 ))),
403 }
404 }
405
406 pub async fn peer_ingress_owner(&self, session_id: &SessionId) -> PeerIngressOwner {
418 let sessions = self.sessions.read().await;
419 let Some(entry) = sessions.get(session_id) else {
420 return PeerIngressOwner::Unattached;
421 };
422 let authority = entry
423 .dsl_authority
424 .lock()
425 .unwrap_or_else(std::sync::PoisonError::into_inner);
426 match authority.state().peer_ingress_owner_kind {
427 crate::meerkat_machine::dsl::PeerIngressOwnerKind::Unattached => {
428 PeerIngressOwner::Unattached
429 }
430 crate::meerkat_machine::dsl::PeerIngressOwnerKind::SessionOwned => {
431 match authority.state().peer_ingress_comms_runtime_id.clone() {
432 Some(comms_runtime_id) => PeerIngressOwner::SessionOwned { comms_runtime_id },
433 None => {
434 tracing::error!(
435 %session_id,
436 "peer_ingress_owner_consistency invariant violation: SessionOwned without comms_runtime_id"
437 );
438 PeerIngressOwner::Unattached
439 }
440 }
441 }
442 crate::meerkat_machine::dsl::PeerIngressOwnerKind::MobOwned => {
443 match (
444 authority.state().peer_ingress_comms_runtime_id.clone(),
445 authority.state().peer_ingress_mob_id.clone(),
446 ) {
447 (Some(comms_runtime_id), Some(mob_id)) => PeerIngressOwner::MobOwned {
448 comms_runtime_id,
449 mob_id,
450 },
451 _ => {
452 tracing::error!(
453 %session_id,
454 "peer_ingress_owner_consistency invariant violation: MobOwned without companion fields"
455 );
456 PeerIngressOwner::Unattached
457 }
458 }
459 }
460 }
461 }
462
463 async fn session_owned_drain_runtime(
464 &self,
465 session_id: &SessionId,
466 ) -> Option<Arc<dyn meerkat_core::agent::CommsRuntime>> {
467 let sessions = self.sessions.read().await;
468 let entry = sessions.get(session_id)?;
469 let authority = entry
470 .dsl_authority
471 .lock()
472 .unwrap_or_else(std::sync::PoisonError::into_inner);
473 let state = authority.state();
474 if state.peer_ingress_owner_kind
475 != crate::meerkat_machine::dsl::PeerIngressOwnerKind::SessionOwned
476 {
477 return None;
478 }
479 let expected_runtime_id = state.peer_ingress_comms_runtime_id.as_ref()?;
480 let runtime = entry.drain_slot.task_runtime()?;
481 let actual_runtime_id = crate::meerkat_machine::dsl::CommsRuntimeId::from_runtime(&runtime);
482 (expected_runtime_id == &actual_runtime_id).then_some(runtime)
483 }
484
485 pub(super) async fn drain_authority_state(
486 &self,
487 session_id: &SessionId,
488 ) -> Option<DrainAuthorityState> {
489 let sessions = self.sessions.read().await;
490 let entry = sessions.get(session_id)?;
491 let authority = entry
492 .dsl_authority
493 .lock()
494 .unwrap_or_else(std::sync::PoisonError::into_inner);
495 let state = authority.state();
496 Some(DrainAuthorityState {
497 phase: state.drain_phase,
498 mode: state.drain_mode,
499 peer_owner_kind: state.peer_ingress_owner_kind,
500 peer_runtime_id: state.peer_ingress_comms_runtime_id.clone(),
501 })
502 }
503
504 pub(super) async fn update_peer_ingress_context_inner(
505 self: &Arc<Self>,
506 session_id: &SessionId,
507 keep_alive: bool,
508 comms_runtime: Option<Arc<dyn meerkat_core::agent::CommsRuntime>>,
509 ) -> Result<bool, RuntimeDriverError> {
510 if !keep_alive {
511 self.execute_meerkat_machine_drain_local_command(MeerkatMachineCommand::Abort {
516 session_id: session_id.clone(),
517 })
518 .await?;
519 return Ok(false);
520 }
521
522 let mode = CommsDrainMode::PersistentHost;
523
524 let comms = match comms_runtime {
525 Some(c) => c,
526 None => return Ok(false),
527 };
528
529 let runtime_id = crate::meerkat_machine::dsl::CommsRuntimeId::from_runtime(&comms);
530 let Some(authority_state) = self.drain_authority_state(session_id).await else {
531 tracing::warn!(
532 %session_id,
533 "refusing to spawn comms drain without generated drain authority"
534 );
535 return Ok(false);
536 };
537 if !authority_state.has_peer_runtime(&runtime_id) {
538 tracing::warn!(
539 %session_id,
540 "refusing to spawn comms drain without matching generated peer-ingress authority"
541 );
542 return Ok(false);
543 }
544
545 let dsl_mode = crate::meerkat_machine::dsl::DrainMode::from(mode);
546 let needs_spawn = authority_state.can_spawn();
547 let needs_task_refresh = if needs_spawn {
548 false
549 } else if authority_state.phase == crate::meerkat_machine::dsl::DrainPhase::Running
550 && authority_state.mode == Some(dsl_mode)
551 {
552 let sessions = self.sessions.read().await;
553 let Some(entry) = sessions.get(session_id) else {
554 tracing::warn!(
555 %session_id,
556 "refusing to spawn comms drain for unregistered session"
557 );
558 return Ok(false);
559 };
560 !entry.drain_slot.handle_present() || !entry.drain_slot.task_runtime_matches(&comms)
561 } else {
562 false
563 };
564
565 if !needs_spawn && !needs_task_refresh {
566 return Ok(false);
567 }
568
569 if needs_spawn {
570 if let Err(err) = self
575 .stage_session_dsl_input(
576 session_id,
577 crate::meerkat_machine::dsl::MeerkatMachineInput::SpawnDrain { mode: dsl_mode },
578 "SpawnDrain",
579 )
580 .await
581 {
582 tracing::warn!(
583 %session_id,
584 error = %err,
585 "DSL rejected SpawnDrain; skipping drain spawn"
586 );
587 return Ok(false);
588 }
589 } else if needs_task_refresh {
590 tracing::warn!(
591 %session_id,
592 "refreshing persistent comms drain task from generated peer-ingress authority"
593 );
594 }
595
596 let idle_timeout = match mode {
597 CommsDrainMode::PersistentHost => Some(std::time::Duration::MAX),
598 CommsDrainMode::Timed | CommsDrainMode::AttachedSession => None,
599 };
600 let handle = crate::comms_drain::spawn_comms_drain(
601 Arc::clone(self),
602 session_id.clone(),
603 comms.clone(),
604 idle_timeout,
605 );
606 let mut sessions = self.sessions.write().await;
607 if let Some(entry) = sessions.get_mut(session_id) {
608 entry.drain_slot.install_task(comms.clone(), handle);
609 } else {
610 handle.abort();
611 return Ok(false);
612 }
613
614 Ok(true)
615 }
616
617 pub async fn notify_comms_drain_exited(
624 self: &Arc<Self>,
625 session_id: &SessionId,
626 reason: DrainExitReason,
627 ) -> Result<(), RuntimeDriverError> {
628 self.execute_meerkat_machine_command(
629 Some(Arc::clone(self)),
630 MeerkatMachineCommand::NotifyDrainExited {
631 session_id: session_id.clone(),
632 reason,
633 },
634 )
635 .await
636 .map_err(MeerkatMachine::driver_error_from_command_error)?;
637 Ok(())
638 }
639
640 pub(super) async fn notify_comms_drain_exited_inner(
641 &self,
642 session_id: &SessionId,
643 reason: DrainExitReason,
644 ) {
645 let keep_runtime = self
646 .drain_authority_state(session_id)
647 .await
648 .is_some_and(|state| {
649 state.phase == crate::meerkat_machine::dsl::DrainPhase::ExitedRespawnable
650 });
651 let mut sessions = self.sessions.write().await;
652 if let Some(entry) = sessions.get_mut(session_id) {
653 entry.drain_slot.clear_after_exit(keep_runtime);
654 }
655 if std::env::var_os("RKAT_TRACE_COMMS_DRAIN_BIND").is_some() {
656 tracing::info!(
657 %session_id,
658 ?reason,
659 respawnable = keep_runtime,
660 "comms drain exited"
661 );
662 }
663 }
664
665 pub(crate) async fn project_comms_drain_failed_safety_net(&self, session_id: &SessionId) {
666 let keep_runtime = match self.drain_authority_state(session_id).await {
667 Some(state) => {
668 state.phase == crate::meerkat_machine::dsl::DrainPhase::ExitedRespawnable
669 }
670 None => false,
671 };
672 let mut sessions = self.sessions.write().await;
673 if let Some(entry) = sessions.get_mut(session_id) {
674 entry.drain_slot.clear_after_exit(keep_runtime);
675 }
676 }
677
678 pub async fn abort_comms_drains(&self) -> Result<(), RuntimeDriverError> {
680 self.execute_meerkat_machine_command(None, MeerkatMachineCommand::AbortAll)
681 .await
682 .map_err(MeerkatMachine::driver_error_from_command_error)?;
683 Ok(())
684 }
685
686 pub async fn abort_comms_drain(
688 &self,
689 session_id: &SessionId,
690 ) -> Result<(), RuntimeDriverError> {
691 self.execute_meerkat_machine_command(
692 None,
693 MeerkatMachineCommand::Abort {
694 session_id: session_id.clone(),
695 },
696 )
697 .await
698 .map_err(MeerkatMachine::driver_error_from_command_error)?;
699 Ok(())
700 }
701
702 pub async fn wait_comms_drain(&self, session_id: &SessionId) -> Result<(), RuntimeDriverError> {
709 self.execute_meerkat_machine_command(
710 None,
711 MeerkatMachineCommand::Wait {
712 session_id: session_id.clone(),
713 },
714 )
715 .await
716 .map_err(MeerkatMachine::driver_error_from_command_error)?;
717 Ok(())
718 }
719
720 pub async fn supervisor_binding(&self, session_id: &SessionId) -> SupervisorBinding {
729 let sessions = self.sessions.read().await;
730 let Some(entry) = sessions.get(session_id) else {
731 return SupervisorBinding::Unbound;
732 };
733 let authority = entry
734 .dsl_authority
735 .lock()
736 .unwrap_or_else(std::sync::PoisonError::into_inner);
737 match authority.state().supervisor_binding_kind {
738 crate::meerkat_machine::dsl::SupervisorBindingKind::Unbound => {
739 SupervisorBinding::Unbound
740 }
741 crate::meerkat_machine::dsl::SupervisorBindingKind::Bound => {
742 match (
743 authority.state().supervisor_bound_name.clone(),
744 authority.state().supervisor_bound_peer_id.clone(),
745 authority.state().supervisor_bound_address.clone(),
746 authority
747 .state()
748 .supervisor_bound_signing_public_key
749 .clone(),
750 authority.state().supervisor_bound_epoch,
751 ) {
752 (
753 Some(name),
754 Some(peer_id),
755 Some(address),
756 Some(signing_public_key),
757 Some(epoch),
758 ) => SupervisorBinding::Bound {
759 name,
760 peer_id,
761 address,
762 signing_public_key,
763 epoch,
764 },
765 _ => {
766 tracing::error!(
767 %session_id,
768 "supervisor_binding_consistency invariant violation: Bound without all companion fields"
769 );
770 SupervisorBinding::Unbound
771 }
772 }
773 }
774 }
775 }
776
777 fn local_endpoint_for_comms_runtime(
778 comms_runtime: &dyn meerkat_core::agent::CommsRuntime,
779 ) -> Result<crate::meerkat_machine::dsl::PeerEndpoint, String> {
780 let peer_id = comms_runtime
781 .peer_id()
782 .ok_or_else(|| "runtime peer_id unavailable".to_string())?;
783 let name = comms_runtime
784 .comms_name()
785 .ok_or_else(|| "runtime comms_name unavailable".to_string())?;
786 let address = comms_runtime
787 .advertised_address()
788 .ok_or_else(|| "runtime advertised_address unavailable".to_string())?;
789 let pubkey = comms_runtime
790 .public_key_bytes()
791 .ok_or_else(|| "runtime public_key_bytes unavailable".to_string())?;
792 Ok(crate::meerkat_machine::dsl::PeerEndpoint::new(
793 name,
794 peer_id.to_string(),
795 address,
796 pubkey,
797 ))
798 }
799
800 pub async fn stage_local_endpoint_for_comms_runtime(
803 &self,
804 session_id: &SessionId,
805 comms_runtime: &dyn meerkat_core::agent::CommsRuntime,
806 ) -> Result<(), SupervisorBindingStageError> {
807 tracing::debug!(
808 %session_id,
809 "MeerkatMachine::stage_local_endpoint_for_comms_runtime building endpoint"
810 );
811 let endpoint = Self::local_endpoint_for_comms_runtime(comms_runtime)
812 .map_err(SupervisorBindingStageError::LocalEndpoint)?;
813 tracing::debug!(
814 %session_id,
815 "MeerkatMachine::stage_local_endpoint_for_comms_runtime built endpoint"
816 );
817 #[cfg(target_arch = "wasm32")]
818 let mut sessions = self
819 .sessions
820 .try_write()
821 .map_err(|_| SupervisorBindingStageError::SessionRegistryBusy)?;
822 #[cfg(not(target_arch = "wasm32"))]
823 let mut sessions = self.sessions.write().await;
824 let entry = sessions
825 .get_mut(session_id)
826 .ok_or(SupervisorBindingStageError::SessionNotRegistered)?;
827 #[cfg(target_arch = "wasm32")]
828 let mut authority = entry
829 .dsl_authority
830 .try_lock()
831 .map_err(|_| SupervisorBindingStageError::SessionAuthorityBusy)?;
832 #[cfg(not(target_arch = "wasm32"))]
833 let mut authority = entry
834 .dsl_authority
835 .lock()
836 .unwrap_or_else(std::sync::PoisonError::into_inner);
837 tracing::debug!(
838 %session_id,
839 "MeerkatMachine::stage_local_endpoint_for_comms_runtime applying endpoint"
840 );
841 if authority.state().local_endpoint.as_ref() == Some(&endpoint) {
842 tracing::debug!(
843 %session_id,
844 "MeerkatMachine::stage_local_endpoint_for_comms_runtime endpoint already applied"
845 );
846 return Ok(());
847 }
848 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
849 &mut *authority,
850 crate::meerkat_machine::dsl::MeerkatMachineInput::PublishLocalEndpoint { endpoint },
851 )
852 .map_err(SupervisorBindingStageError::Dsl)?;
853 tracing::debug!(
854 %session_id,
855 "MeerkatMachine::stage_local_endpoint_for_comms_runtime applied endpoint"
856 );
857 Ok(())
858 }
859
860 pub async fn stage_supervisor_bind(
867 &self,
868 session_id: &SessionId,
869 name: String,
870 peer_id: String,
871 address: String,
872 signing_public_key: String,
873 epoch: u64,
874 ) -> Result<crate::meerkat_machine::dsl::MeerkatMachineTransition, SupervisorBindingStageError>
875 {
876 self.apply_supervisor_binding_input(
877 session_id,
878 crate::meerkat_machine::dsl::MeerkatMachineInput::BindSupervisor {
879 name,
880 peer_id,
881 address,
882 signing_public_key,
883 epoch,
884 },
885 )
886 .await
887 }
888
889 pub async fn supervisor_trust_publish_freshness_authority(
890 &self,
891 session_id: &SessionId,
892 ) -> Result<
893 crate::protocol_supervisor_trust_publish::SupervisorTrustFreshnessAuthority,
894 SupervisorBindingStageError,
895 > {
896 let sessions = self.sessions.read().await;
897 let entry = sessions
898 .get(session_id)
899 .ok_or(SupervisorBindingStageError::SessionNotRegistered)?;
900 Ok(
901 crate::protocol_supervisor_trust_publish::SupervisorTrustFreshnessAuthority::from_authority(
902 Arc::clone(&entry.dsl_authority),
903 ),
904 )
905 }
906
907 pub async fn supervisor_trust_revoke_freshness_authority(
908 &self,
909 session_id: &SessionId,
910 ) -> Result<
911 crate::protocol_supervisor_trust_revoke::SupervisorTrustFreshnessAuthority,
912 SupervisorBindingStageError,
913 > {
914 let sessions = self.sessions.read().await;
915 let entry = sessions
916 .get(session_id)
917 .ok_or(SupervisorBindingStageError::SessionNotRegistered)?;
918 Ok(
919 crate::protocol_supervisor_trust_revoke::SupervisorTrustFreshnessAuthority::from_authority(
920 Arc::clone(&entry.dsl_authority),
921 ),
922 )
923 }
924
925 pub async fn stage_supervisor_authorize(
931 &self,
932 session_id: &SessionId,
933 name: String,
934 peer_id: String,
935 address: String,
936 signing_public_key: String,
937 epoch: u64,
938 ) -> Result<crate::meerkat_machine::dsl::MeerkatMachineTransition, SupervisorBindingStageError>
939 {
940 self.apply_supervisor_binding_input(
941 session_id,
942 crate::meerkat_machine::dsl::MeerkatMachineInput::AuthorizeSupervisor {
943 name,
944 peer_id,
945 address,
946 signing_public_key,
947 epoch,
948 },
949 )
950 .await
951 }
952
953 pub async fn stage_supervisor_trust_publish_request(
959 &self,
960 session_id: &SessionId,
961 name: String,
962 peer_id: String,
963 address: String,
964 signing_public_key: String,
965 epoch: u64,
966 ) -> Result<crate::meerkat_machine::dsl::MeerkatMachineTransition, SupervisorBindingStageError>
967 {
968 self.apply_supervisor_binding_input(
969 session_id,
970 crate::meerkat_machine::dsl::MeerkatMachineInput::RequestSupervisorTrustPublish {
971 name,
972 peer_id,
973 address,
974 signing_public_key,
975 epoch,
976 },
977 )
978 .await
979 }
980
981 pub async fn stage_supervisor_revoke(
987 &self,
988 session_id: &SessionId,
989 peer_id: String,
990 epoch: u64,
991 ) -> Result<crate::meerkat_machine::dsl::MeerkatMachineTransition, SupervisorBindingStageError>
992 {
993 self.apply_supervisor_binding_input(
994 session_id,
995 crate::meerkat_machine::dsl::MeerkatMachineInput::RevokeSupervisor { peer_id, epoch },
996 )
997 .await
998 }
999
1000 pub async fn stage_supervisor_trust_published(
1012 &self,
1013 session_id: &SessionId,
1014 peer_id: String,
1015 epoch: u64,
1016 ) -> Result<(), SupervisorBindingStageError> {
1017 self.apply_supervisor_binding_input(
1018 session_id,
1019 crate::meerkat_machine::dsl::MeerkatMachineInput::SupervisorTrustEdgePublished {
1020 peer_id,
1021 epoch,
1022 },
1023 )
1024 .await?;
1025 Ok(())
1026 }
1027
1028 pub async fn stage_supervisor_trust_publish_failed(
1036 &self,
1037 session_id: &SessionId,
1038 peer_id: String,
1039 epoch: u64,
1040 reason: String,
1041 ) -> Result<(), SupervisorBindingStageError> {
1042 self.apply_supervisor_binding_input(
1043 session_id,
1044 crate::meerkat_machine::dsl::MeerkatMachineInput::SupervisorTrustEdgePublishFailed {
1045 peer_id,
1046 epoch,
1047 reason,
1048 },
1049 )
1050 .await?;
1051 Ok(())
1052 }
1053
1054 pub async fn stage_supervisor_trust_revoked(
1060 &self,
1061 session_id: &SessionId,
1062 peer_id: String,
1063 epoch: u64,
1064 ) -> Result<(), SupervisorBindingStageError> {
1065 self.apply_supervisor_binding_input(
1066 session_id,
1067 crate::meerkat_machine::dsl::MeerkatMachineInput::SupervisorTrustEdgeRevoked {
1068 peer_id,
1069 epoch,
1070 },
1071 )
1072 .await?;
1073 Ok(())
1074 }
1075
1076 pub async fn stage_supervisor_trust_revoke_failed(
1082 &self,
1083 session_id: &SessionId,
1084 peer_id: String,
1085 epoch: u64,
1086 reason: String,
1087 ) -> Result<(), SupervisorBindingStageError> {
1088 self.apply_supervisor_binding_input(
1089 session_id,
1090 crate::meerkat_machine::dsl::MeerkatMachineInput::SupervisorTrustEdgeRevokeFailed {
1091 peer_id,
1092 epoch,
1093 reason,
1094 },
1095 )
1096 .await?;
1097 Ok(())
1098 }
1099}
1100
1101#[derive(Debug)]
1104pub enum SupervisorBindingStageError {
1105 SessionNotRegistered,
1107 SessionRegistryBusy,
1110 SessionAuthorityBusy,
1113 Dsl(crate::meerkat_machine::dsl::MeerkatMachineTransitionError),
1118 LocalEndpoint(String),
1121}
1122
1123impl std::fmt::Display for SupervisorBindingStageError {
1124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1125 match self {
1126 Self::SessionNotRegistered => write!(f, "session not registered with runtime"),
1127 Self::SessionRegistryBusy => {
1128 write!(f, "runtime session registry busy during supervisor binding")
1129 }
1130 Self::SessionAuthorityBusy => {
1131 write!(f, "session authority busy during supervisor binding")
1132 }
1133 Self::Dsl(err) => write!(f, "DSL rejected supervisor binding input: {err}"),
1134 Self::LocalEndpoint(err) => {
1135 write!(f, "local endpoint unavailable for supervisor trust: {err}")
1136 }
1137 }
1138 }
1139}
1140
1141impl std::error::Error for SupervisorBindingStageError {}
1142
1143#[derive(Debug, Clone, PartialEq, Eq)]
1147pub(crate) struct GeneratedSupervisorBinding {
1148 pub name: String,
1149 pub peer_id: String,
1150 pub address: String,
1151 pub signing_public_key: String,
1152 pub epoch: u64,
1153}
1154
1155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1156pub(crate) enum SupervisorBindAdmission {
1157 Bootstrap,
1158 IdempotentAck,
1159 Rejected(crate::meerkat_machine::dsl::SupervisorBindRejectionKind),
1160}
1161
1162#[derive(Debug, Clone, PartialEq, Eq)]
1163pub(crate) enum SupervisorAuthorizeAdmission {
1164 Proceed(GeneratedSupervisorBinding),
1165 IdempotentAck,
1166 Rejected(crate::meerkat_machine::dsl::SupervisorAuthorizeRejectionKind),
1167}
1168
1169#[derive(Debug)]
1170pub(crate) enum SupervisorAdmissionStageError {
1171 SessionNotRegistered,
1172 Dsl(crate::meerkat_machine::dsl::MeerkatMachineTransitionError),
1173 MissingAdmissionEffect(&'static str),
1174 MalformedAdmissionEffect(&'static str),
1175}
1176
1177impl std::fmt::Display for SupervisorAdmissionStageError {
1178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1179 match self {
1180 Self::SessionNotRegistered => write!(f, "session not registered with runtime"),
1181 Self::Dsl(err) => write!(f, "DSL rejected supervisor admission input: {err}"),
1182 Self::MissingAdmissionEffect(context) => write!(
1183 f,
1184 "{context} admission transition committed without admission feedback"
1185 ),
1186 Self::MalformedAdmissionEffect(context) => write!(
1187 f,
1188 "{context} admission feedback carried inconsistent result fields"
1189 ),
1190 }
1191 }
1192}
1193
1194impl std::error::Error for SupervisorAdmissionStageError {}
1195
1196impl MeerkatMachine {
1197 pub(crate) async fn resolve_supervisor_bind_admission(
1198 &self,
1199 session_id: &SessionId,
1200 supervisor_peer_id: String,
1201 supervisor_epoch: u64,
1202 sender_peer_id: Option<String>,
1203 ) -> Result<SupervisorBindAdmission, SupervisorAdmissionStageError> {
1204 let mut sessions = self.sessions.write().await;
1205 let entry = sessions
1206 .get_mut(session_id)
1207 .ok_or(SupervisorAdmissionStageError::SessionNotRegistered)?;
1208 let effects = {
1209 let mut authority = entry
1210 .dsl_authority
1211 .lock()
1212 .unwrap_or_else(std::sync::PoisonError::into_inner);
1213 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
1214 &mut *authority,
1215 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveSupervisorBindAdmission {
1216 supervisor_peer_id,
1217 supervisor_epoch,
1218 sender_peer_id,
1219 },
1220 )
1221 .map_err(SupervisorAdmissionStageError::Dsl)?
1222 .into_effects()
1223 };
1224 effects
1225 .iter()
1226 .find_map(|effect| {
1227 match effect {
1228 crate::meerkat_machine::dsl::MeerkatMachineEffect::SupervisorBindAdmissionResolved {
1229 result,
1230 rejection,
1231 } => Some((*result, *rejection)),
1232 _ => None,
1233 }
1234 })
1235 .ok_or(SupervisorAdmissionStageError::MissingAdmissionEffect(
1236 "bind supervisor",
1237 ))
1238 .and_then(|(result, rejection)| match (result, rejection) {
1239 (
1240 crate::meerkat_machine::dsl::SupervisorBindAdmissionResultKind::Bootstrap,
1241 None,
1242 ) => Ok(SupervisorBindAdmission::Bootstrap),
1243 (
1244 crate::meerkat_machine::dsl::SupervisorBindAdmissionResultKind::IdempotentAck,
1245 None,
1246 ) => Ok(SupervisorBindAdmission::IdempotentAck),
1247 (
1248 crate::meerkat_machine::dsl::SupervisorBindAdmissionResultKind::Reject,
1249 Some(rejection),
1250 ) => Ok(SupervisorBindAdmission::Rejected(rejection)),
1251 _ => Err(SupervisorAdmissionStageError::MalformedAdmissionEffect(
1252 "bind supervisor",
1253 )),
1254 })
1255 }
1256
1257 pub(crate) async fn resolve_supervisor_bind_material_admission(
1264 &self,
1265 session_id: &SessionId,
1266 address_matches: bool,
1267 sender_matches_supervisor: bool,
1268 expected_peer_id_matches: bool,
1269 bootstrap_token_matches: bool,
1270 ) -> Result<
1271 crate::meerkat_machine::dsl::SupervisorBindMaterialAdmissionKind,
1272 SupervisorAdmissionStageError,
1273 > {
1274 let mut sessions = self.sessions.write().await;
1275 let entry = sessions
1276 .get_mut(session_id)
1277 .ok_or(SupervisorAdmissionStageError::SessionNotRegistered)?;
1278 let effects = {
1279 let mut authority = entry
1280 .dsl_authority
1281 .lock()
1282 .unwrap_or_else(std::sync::PoisonError::into_inner);
1283 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
1284 &mut *authority,
1285 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveSupervisorBindMaterialAdmission {
1286 address_matches,
1287 sender_matches_supervisor,
1288 expected_peer_id_matches,
1289 bootstrap_token_matches,
1290 },
1291 )
1292 .map_err(SupervisorAdmissionStageError::Dsl)?
1293 .into_effects()
1294 };
1295 effects
1296 .iter()
1297 .find_map(|effect| match effect {
1298 crate::meerkat_machine::dsl::MeerkatMachineEffect::SupervisorBindMaterialAdmissionResolved {
1299 verdict,
1300 } => Some(*verdict),
1301 _ => None,
1302 })
1303 .ok_or(SupervisorAdmissionStageError::MissingAdmissionEffect(
1304 "bind supervisor material",
1305 ))
1306 }
1307
1308 pub(crate) async fn resolve_supervisor_authorize_admission(
1309 &self,
1310 session_id: &SessionId,
1311 supervisor_peer_id: String,
1312 supervisor_epoch: u64,
1313 sender_peer_id: Option<String>,
1314 ) -> Result<SupervisorAuthorizeAdmission, SupervisorAdmissionStageError> {
1315 let mut sessions = self.sessions.write().await;
1316 let entry = sessions
1317 .get_mut(session_id)
1318 .ok_or(SupervisorAdmissionStageError::SessionNotRegistered)?;
1319 let effects = {
1320 let mut authority = entry
1321 .dsl_authority
1322 .lock()
1323 .unwrap_or_else(std::sync::PoisonError::into_inner);
1324 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
1325 &mut *authority,
1326 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveSupervisorAuthorizeAdmission {
1327 supervisor_peer_id,
1328 supervisor_epoch,
1329 sender_peer_id,
1330 },
1331 )
1332 .map_err(SupervisorAdmissionStageError::Dsl)?
1333 .into_effects()
1334 };
1335 effects
1336 .iter()
1337 .find_map(|effect| match effect {
1338 crate::meerkat_machine::dsl::MeerkatMachineEffect::SupervisorAuthorizeAdmissionResolved {
1339 result,
1340 rejection,
1341 previous_name,
1342 previous_peer_id,
1343 previous_address,
1344 previous_signing_public_key,
1345 previous_epoch,
1346 } => Some((
1347 *result,
1348 *rejection,
1349 previous_name.clone(),
1350 previous_peer_id.clone(),
1351 previous_address.clone(),
1352 previous_signing_public_key.clone(),
1353 *previous_epoch,
1354 )),
1355 _ => None,
1356 })
1357 .ok_or(SupervisorAdmissionStageError::MissingAdmissionEffect(
1358 "authorize supervisor",
1359 ))
1360 .and_then(
1361 |(
1362 result,
1363 rejection,
1364 previous_name,
1365 previous_peer_id,
1366 previous_address,
1367 previous_signing_public_key,
1368 previous_epoch,
1369 )| {
1370 match (
1371 result,
1372 rejection,
1373 previous_name,
1374 previous_peer_id,
1375 previous_address,
1376 previous_signing_public_key,
1377 previous_epoch,
1378 ) {
1379 (
1380 crate::meerkat_machine::dsl::SupervisorAuthorizeAdmissionResultKind::Proceed,
1381 None,
1382 Some(name),
1383 Some(peer_id),
1384 Some(address),
1385 Some(signing_public_key),
1386 Some(epoch),
1387 ) => Ok(SupervisorAuthorizeAdmission::Proceed(
1388 GeneratedSupervisorBinding {
1389 name,
1390 peer_id,
1391 address,
1392 signing_public_key,
1393 epoch,
1394 },
1395 )),
1396 (
1397 crate::meerkat_machine::dsl::SupervisorAuthorizeAdmissionResultKind::IdempotentAck,
1398 None,
1399 None,
1400 None,
1401 None,
1402 None,
1403 None,
1404 ) => Ok(SupervisorAuthorizeAdmission::IdempotentAck),
1405 (
1406 crate::meerkat_machine::dsl::SupervisorAuthorizeAdmissionResultKind::Reject,
1407 Some(rejection),
1408 None,
1409 None,
1410 None,
1411 None,
1412 None,
1413 ) => Ok(SupervisorAuthorizeAdmission::Rejected(rejection)),
1414 _ => Err(SupervisorAdmissionStageError::MalformedAdmissionEffect(
1415 "authorize supervisor",
1416 )),
1417 }
1418 },
1419 )
1420 }
1421}
1422
1423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1426pub(crate) enum SupervisorBridgeCommandAdmission {
1427 Accepted,
1428 Rejected(crate::meerkat_machine::dsl::SupervisorBridgeCommandRejectionKind),
1429}
1430
1431#[derive(Debug)]
1432pub(crate) enum SupervisorBridgeCommandAdmissionStageError {
1433 SessionNotRegistered,
1434 Dsl(crate::meerkat_machine::dsl::MeerkatMachineTransitionError),
1435 MissingAdmissionEffect,
1436 MalformedAdmissionEffect,
1437}
1438
1439impl std::fmt::Display for SupervisorBridgeCommandAdmissionStageError {
1440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441 match self {
1442 Self::SessionNotRegistered => write!(f, "session not registered with runtime"),
1443 Self::Dsl(err) => write!(f, "DSL rejected supervisor bridge admission input: {err}"),
1444 Self::MissingAdmissionEffect => write!(
1445 f,
1446 "supervisor bridge admission transition committed without admission feedback"
1447 ),
1448 Self::MalformedAdmissionEffect => write!(
1449 f,
1450 "supervisor bridge admission feedback carried inconsistent result fields"
1451 ),
1452 }
1453 }
1454}
1455
1456impl std::error::Error for SupervisorBridgeCommandAdmissionStageError {}
1457
1458impl MeerkatMachine {
1459 pub async fn direct_peer_endpoints(
1462 &self,
1463 session_id: &SessionId,
1464 ) -> Result<BTreeSet<crate::meerkat_machine::dsl::PeerEndpoint>, PeerEndpointStageError> {
1465 let sessions = self.sessions.read().await;
1466 let entry = sessions
1467 .get(session_id)
1468 .ok_or(PeerEndpointStageError::SessionNotRegistered)?;
1469 let authority = entry
1470 .dsl_authority
1471 .lock()
1472 .unwrap_or_else(std::sync::PoisonError::into_inner);
1473 Ok(authority.state().direct_peer_endpoints.clone())
1474 }
1475
1476 pub(crate) async fn resolve_supervisor_bridge_command_admission(
1477 &self,
1478 session_id: &SessionId,
1479 supervisor_peer_id: String,
1480 supervisor_epoch: u64,
1481 sender_peer_id: Option<String>,
1482 ) -> Result<SupervisorBridgeCommandAdmission, SupervisorBridgeCommandAdmissionStageError> {
1483 let mut sessions = self.sessions.write().await;
1484 let entry = sessions
1485 .get_mut(session_id)
1486 .ok_or(SupervisorBridgeCommandAdmissionStageError::SessionNotRegistered)?;
1487 let effects = {
1488 let mut authority = entry
1489 .dsl_authority
1490 .lock()
1491 .unwrap_or_else(std::sync::PoisonError::into_inner);
1492 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
1493 &mut *authority,
1494 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveSupervisorBridgeCommandAdmission {
1495 supervisor_peer_id,
1496 supervisor_epoch,
1497 sender_peer_id,
1498 },
1499 )
1500 .map_err(SupervisorBridgeCommandAdmissionStageError::Dsl)?
1501 .into_effects()
1502 };
1503 effects
1504 .iter()
1505 .find_map(|effect| match effect {
1506 crate::meerkat_machine::dsl::MeerkatMachineEffect::SupervisorBridgeCommandAdmissionResolved {
1507 result,
1508 rejection,
1509 } => Some((*result, *rejection)),
1510 _ => None,
1511 })
1512 .ok_or(SupervisorBridgeCommandAdmissionStageError::MissingAdmissionEffect)
1513 .and_then(|(result, rejection)| match (result, rejection) {
1514 (
1515 crate::meerkat_machine::dsl::SupervisorBridgeCommandAdmissionResultKind::Accept,
1516 None,
1517 ) => Ok(SupervisorBridgeCommandAdmission::Accepted),
1518 (
1519 crate::meerkat_machine::dsl::SupervisorBridgeCommandAdmissionResultKind::Reject,
1520 Some(rejection),
1521 ) => Ok(SupervisorBridgeCommandAdmission::Rejected(rejection)),
1522 _ => Err(SupervisorBridgeCommandAdmissionStageError::MalformedAdmissionEffect),
1523 })
1524 }
1525
1526 pub async fn stage_add_direct_peer_endpoint(
1542 &self,
1543 session_id: &SessionId,
1544 endpoint: crate::meerkat_machine::dsl::PeerEndpoint,
1545 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
1546 ) -> Result<(), PeerEndpointStageError> {
1547 validate_peer_endpoint_for_stage(&endpoint)?;
1550 let (reconciler, reconcile_obligation) = self
1551 .stage_peer_projection_input(
1552 session_id,
1553 crate::meerkat_machine::dsl::MeerkatMachineInput::AddDirectPeerEndpoint {
1554 endpoint,
1555 },
1556 comms_runtime,
1557 )
1558 .await?;
1559 drive_reconciler(&reconciler, reconcile_obligation).await
1560 }
1561
1562 pub async fn stage_remove_direct_peer_endpoint(
1567 &self,
1568 session_id: &SessionId,
1569 endpoint: crate::meerkat_machine::dsl::PeerEndpoint,
1570 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
1571 ) -> Result<(), PeerEndpointStageError> {
1572 let (reconciler, reconcile_obligation) = self
1573 .stage_peer_projection_input(
1574 session_id,
1575 crate::meerkat_machine::dsl::MeerkatMachineInput::RemoveDirectPeerEndpoint {
1576 endpoint,
1577 },
1578 comms_runtime,
1579 )
1580 .await?;
1581 drive_reconciler(&reconciler, reconcile_obligation).await
1582 }
1583
1584 pub async fn stage_repair_remove_direct_peer_id(
1591 &self,
1592 session_id: &SessionId,
1593 peer_id: String,
1594 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
1595 ) -> Result<(), PeerEndpointStageError> {
1596 let endpoint = crate::meerkat_machine::dsl::PeerEndpoint::new(
1597 "generated-remove-repair",
1598 peer_id,
1599 "generated-repair://absent-direct-peer",
1600 [0; 32],
1601 );
1602 self.stage_remove_direct_peer_endpoint(session_id, endpoint, comms_runtime)
1603 .await
1604 }
1605
1606 #[allow(clippy::too_many_arguments)]
1609 pub async fn stage_authorized_supervisor_mob_peer_overlay(
1610 &self,
1611 session_id: &SessionId,
1612 supervisor_peer_id: String,
1613 supervisor_epoch: u64,
1614 recipient_peer_id: String,
1615 overlay_epoch: u64,
1616 endpoints: BTreeSet<crate::meerkat_machine::dsl::PeerEndpoint>,
1617 endpoint_count: u64,
1618 command_peer_id: String,
1619 command_endpoint: crate::meerkat_machine::dsl::PeerEndpoint,
1620 command_kind: crate::meerkat_machine::dsl::MobPeerOverlayCommandKind,
1621 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
1622 ) -> Result<(), PeerEndpointStageError> {
1623 for endpoint in &endpoints {
1626 validate_peer_endpoint_for_stage(endpoint)?;
1627 }
1628 validate_peer_endpoint_for_stage(&command_endpoint)?;
1629 let (reconciler, reconcile_obligation) = self
1630 .stage_peer_projection_input(
1631 session_id,
1632 crate::meerkat_machine::dsl::MeerkatMachineInput::AuthorizeSupervisorMobPeerOverlay {
1633 supervisor_peer_id,
1634 supervisor_epoch,
1635 recipient_peer_id,
1636 overlay_epoch,
1637 endpoints,
1638 endpoint_count,
1639 command_peer_id,
1640 command_endpoint,
1641 command_kind,
1642 },
1643 comms_runtime,
1644 )
1645 .await?;
1646 drive_reconciler(&reconciler, reconcile_obligation).await
1647 }
1648
1649 async fn stage_peer_projection_input(
1660 &self,
1661 session_id: &SessionId,
1662 input: crate::meerkat_machine::dsl::MeerkatMachineInput,
1663 comms_runtime: Arc<dyn meerkat_core::agent::CommsRuntime>,
1664 ) -> Result<
1665 (
1666 Arc<crate::comms_trust_reconcile::CommsTrustReconciler>,
1667 crate::protocol_comms_trust_reconcile::CommsTrustReconcileObligation,
1668 ),
1669 PeerEndpointStageError,
1670 > {
1671 let mut sessions = self.sessions.write().await;
1672 let entry = sessions
1673 .get_mut(session_id)
1674 .ok_or(PeerEndpointStageError::SessionNotRegistered)?;
1675 let local_endpoint = Self::local_endpoint_for_comms_runtime(comms_runtime.as_ref())
1676 .map_err(PeerEndpointStageError::LocalEndpoint)?;
1677
1678 let reconcile_obligation = {
1679 let freshness_authority =
1680 crate::protocol_comms_trust_reconcile::PeerProjectionFreshnessAuthority::from_authority(
1681 Arc::clone(&entry.dsl_authority),
1682 );
1683 let mut authority = entry
1684 .dsl_authority
1685 .lock()
1686 .unwrap_or_else(std::sync::PoisonError::into_inner);
1687 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
1688 &mut *authority,
1689 crate::meerkat_machine::dsl::MeerkatMachineInput::PublishLocalEndpoint {
1690 endpoint: local_endpoint,
1691 },
1692 )
1693 .map_err(PeerEndpointStageError::Dsl)?;
1694 let transition =
1695 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(&mut *authority, input)
1696 .map_err(PeerEndpointStageError::Dsl)?;
1697 crate::protocol_comms_trust_reconcile::extract_obligations_with_freshness(
1698 &transition,
1699 freshness_authority,
1700 )
1701 .into_iter()
1702 .next()
1703 .ok_or(PeerEndpointStageError::MissingReconcileEffect)?
1704 };
1705
1706 let reconciler = Arc::new(crate::comms_trust_reconcile::CommsTrustReconciler::new(
1707 comms_runtime,
1708 ));
1709
1710 Ok((reconciler, reconcile_obligation))
1711 }
1712}
1713
1714async fn drive_reconciler(
1715 reconciler: &crate::comms_trust_reconcile::CommsTrustReconciler,
1716 reconcile_obligation: crate::protocol_comms_trust_reconcile::CommsTrustReconcileObligation,
1717) -> Result<(), PeerEndpointStageError> {
1718 reconciler
1719 .reconcile(&reconcile_obligation)
1720 .await
1721 .map(|_report| ())
1722 .map_err(PeerEndpointStageError::Reconcile)
1723}
1724
1725#[derive(Debug)]
1728pub enum PeerEndpointStageError {
1729 SessionNotRegistered,
1731 Dsl(crate::meerkat_machine::dsl::MeerkatMachineTransitionError),
1734 MissingReconcileEffect,
1740 LocalEndpoint(String),
1743 Reconcile(crate::comms_trust_reconcile::CommsTrustReconcileError),
1746 InvalidEndpoint(crate::comms_trust_reconcile::CommsTrustReconcileError),
1751}
1752
1753impl std::fmt::Display for PeerEndpointStageError {
1754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1755 match self {
1756 Self::SessionNotRegistered => write!(f, "session not registered with runtime"),
1757 Self::Dsl(err) => write!(f, "DSL rejected peer-projection input: {err}"),
1758 Self::MissingReconcileEffect => write!(
1759 f,
1760 "peer-projection DSL transition committed without emitting CommsTrustReconcileRequested"
1761 ),
1762 Self::LocalEndpoint(err) => {
1763 write!(
1764 f,
1765 "local endpoint unavailable for trust reconciliation: {err}"
1766 )
1767 }
1768 Self::Reconcile(err) => write!(f, "trust reconciliation failed: {err}"),
1769 Self::InvalidEndpoint(err) => {
1770 write!(f, "peer endpoint rejected at ingress boundary: {err}")
1771 }
1772 }
1773 }
1774}
1775
1776impl std::error::Error for PeerEndpointStageError {}
1777
1778fn validate_peer_endpoint_for_stage(
1783 endpoint: &crate::meerkat_machine::dsl::PeerEndpoint,
1784) -> Result<(), PeerEndpointStageError> {
1785 crate::comms_trust_reconcile::endpoint_to_descriptor(endpoint)
1786 .map(|_| ())
1787 .map_err(PeerEndpointStageError::InvalidEndpoint)
1788}