1#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
25use std::any::Any;
26use std::collections::BTreeSet;
27use std::sync::Arc;
28
29use crate::LoopState;
30use crate::auth::RefreshFailureObservation;
31use crate::comms::InputSource;
32use crate::interaction::{
33 PeerIngressAdmission, PeerIngressDequeueAuthority, PeerIngressDequeueFacts,
34 PeerIngressEnvelopeFacts, PeerIngressPlainEventFacts, PeerIngressReceiveAuthority,
35 PeerIngressReceiveFacts,
36};
37use crate::lifecycle::run_primitive::ModelId;
38use crate::lifecycle::{InputId, RunId};
39use crate::ops::{AsyncOpRef, OperationId};
40use crate::peer_correlation::{
41 InboundPeerRequestState, InteractionStreamState, OutboundPeerRequestState, PeerCorrelationId,
42};
43use crate::retry::LlmRetrySchedule;
44use crate::tool_scope::{
45 ExternalToolSurfaceBaseState, ExternalToolSurfaceDeltaOperation, ExternalToolSurfaceDeltaPhase,
46 ExternalToolSurfaceFailureCause, ExternalToolSurfaceGlobalPhase, ExternalToolSurfacePendingOp,
47 ExternalToolSurfaceStagedOp,
48};
49use crate::turn_execution_authority::{
50 ContentShape, TurnExecutionEffect, TurnExecutionInput, TurnFailureReason, TurnFailureSource,
51 TurnPhase, TurnPrimitiveKind, TurnTerminalCauseKind, TurnTerminalOutcome,
52};
53use crate::types::{HandlingMode, SessionId};
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum DrainMode {
67 Timed,
69 AttachedSession,
71 PersistentHost,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum DrainExitReason {
78 IdleTimeout,
79 Dismissed,
80 Failed,
81 Aborted,
82 SessionShutdown,
83}
84
85pub trait ModelRoutingHandle: Send + Sync {
92 fn set_baseline(
94 &self,
95 baseline_model: ModelId,
96 realtime_capable: bool,
97 ) -> Result<(), DslTransitionError>;
98
99 fn hydrate_llm_capability_surface(
104 &self,
105 identity: &crate::SessionLlmIdentity,
106 profile: Option<&crate::model_profile::ModelProfile>,
107 capability_base_filter: &crate::ToolFilter,
108 ) -> Result<(), DslTransitionError>;
109}
110
111impl DrainExitReason {
112 pub const fn as_str(self) -> &'static str {
115 match self {
116 Self::IdleTimeout => "IdleTimeout",
117 Self::Dismissed => "Dismissed",
118 Self::Failed => "Failed",
119 Self::Aborted => "Aborted",
120 Self::SessionShutdown => "SessionShutdown",
121 }
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum AuthLeasePhase {
128 Valid,
129 Expiring,
130 Expired,
131 Refreshing,
132 ReauthRequired,
133 Released,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum CredentialUseIntent {
145 UseCredential,
147 HoldAuthority,
149 BeginRefresh,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum CredentialUseDisposition {
161 Authorized,
163 RefreshRequired,
165 RefreshDisallowed,
170 ReauthRequired,
172 LeaseAbsent,
174 AlreadyRefreshing,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub struct OAuthLoginCredentialFacts {
188 pub credential_present: bool,
191 pub force_refresh: bool,
193 pub refresh_allowed: bool,
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum DslRejectionKind {
208 NoMatchingTransition,
212 GuardRejected,
217 RecoveredStateInvariantRejected,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
229#[error("DSL transition rejected in {context}: {reason}")]
230pub struct DslTransitionError {
231 pub context: &'static str,
233 pub kind: DslRejectionKind,
235 pub reason: String,
238}
239
240impl DslTransitionError {
241 pub fn no_matching(context: &'static str, reason: impl Into<String>) -> Self {
243 Self {
244 context,
245 kind: DslRejectionKind::NoMatchingTransition,
246 reason: reason.into(),
247 }
248 }
249
250 pub fn guard_rejected(context: &'static str, reason: impl Into<String>) -> Self {
252 Self {
253 context,
254 kind: DslRejectionKind::GuardRejected,
255 reason: reason.into(),
256 }
257 }
258
259 pub fn recovered_state_invariant_rejected(
261 context: &'static str,
262 reason: impl Into<String>,
263 ) -> Self {
264 Self {
265 context,
266 kind: DslRejectionKind::RecoveredStateInvariantRejected,
267 reason: reason.into(),
268 }
269 }
270
271 pub fn is_guard_rejected(&self) -> bool {
273 self.kind == DslRejectionKind::GuardRejected
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum PeerResponseProgressProjectionPhase {
284 Accepted,
285 InProgress,
286 PartialResult,
287}
288
289impl PeerResponseProgressProjectionPhase {
290 fn label(self) -> &'static str {
291 match self {
292 Self::Accepted => "accepted",
293 Self::InProgress => "in_progress",
294 Self::PartialResult => "partial_result",
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
300#[serde(rename_all = "snake_case")]
301pub enum PeerResponseTerminalProjectionStatus {
302 Completed,
303 Failed,
304 Cancelled,
305}
306
307impl PeerResponseTerminalProjectionStatus {
308 pub fn label(self) -> &'static str {
309 match self {
310 Self::Completed => "completed",
311 Self::Failed => "failed",
312 Self::Cancelled => "cancelled",
313 }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
318pub enum PeerResponseTerminalFactError {
319 #[error("transport identity cannot be empty")]
320 EmptyTransportIdentity,
321 #[error("route identity cannot be empty")]
322 EmptyRouteIdentity,
323 #[error("route identity must be a canonical peer UUID")]
324 InvalidRouteIdentity,
325 #[error("display identity is required")]
326 MissingDisplayIdentity,
327 #[error("display identity cannot be empty")]
328 EmptyDisplayIdentity,
329 #[error("display identity cannot contain control characters")]
330 InvalidDisplayIdentity,
331 #[error("correlation id cannot be empty")]
332 EmptyCorrelationId,
333 #[error("correlation id must be a UUID: {input}")]
334 InvalidCorrelationId { input: String },
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
338#[serde(transparent)]
339pub struct PeerResponseTerminalTransportIdentity(String);
340
341impl PeerResponseTerminalTransportIdentity {
342 pub fn parse(raw: impl Into<String>) -> Result<Self, PeerResponseTerminalFactError> {
343 let raw = raw.into();
344 if raw.trim().is_empty() {
345 return Err(PeerResponseTerminalFactError::EmptyTransportIdentity);
346 }
347 Ok(Self(raw))
348 }
349
350 pub fn as_str(&self) -> &str {
351 &self.0
352 }
353}
354
355impl std::fmt::Display for PeerResponseTerminalTransportIdentity {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 self.0.fmt(f)
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
362#[serde(transparent)]
363pub struct PeerResponseTerminalRouteIdentity(crate::comms::PeerId);
364
365impl PeerResponseTerminalRouteIdentity {
366 pub fn parse(raw: impl Into<String>) -> Result<Self, PeerResponseTerminalFactError> {
367 let raw = raw.into();
368 if raw.trim().is_empty() {
369 return Err(PeerResponseTerminalFactError::EmptyRouteIdentity);
370 }
371 if raw.chars().any(char::is_control) {
372 return Err(PeerResponseTerminalFactError::InvalidRouteIdentity);
373 }
374 let peer_id = crate::comms::PeerId::parse(raw.trim())
375 .map_err(|_| PeerResponseTerminalFactError::InvalidRouteIdentity)?;
376 Ok(Self(peer_id))
377 }
378
379 pub fn peer_id(&self) -> crate::comms::PeerId {
381 self.0
382 }
383
384 pub fn as_str(&self) -> String {
385 self.0.as_str()
386 }
387}
388
389impl std::fmt::Display for PeerResponseTerminalRouteIdentity {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 self.0.fmt(f)
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
396#[serde(transparent)]
397pub struct PeerResponseTerminalDisplayIdentity(String);
398
399impl PeerResponseTerminalDisplayIdentity {
400 pub fn parse(raw: impl Into<String>) -> Result<Self, PeerResponseTerminalFactError> {
401 let raw = raw.into();
402 if raw.trim().is_empty() {
403 return Err(PeerResponseTerminalFactError::EmptyDisplayIdentity);
404 }
405 if raw.chars().any(char::is_control) {
406 return Err(PeerResponseTerminalFactError::InvalidDisplayIdentity);
407 }
408 Ok(Self(raw))
409 }
410
411 pub fn as_str(&self) -> &str {
412 &self.0
413 }
414}
415
416impl std::fmt::Display for PeerResponseTerminalDisplayIdentity {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 self.0.fmt(f)
419 }
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
423#[serde(transparent)]
424pub struct PeerResponseTerminalCorrelationId(PeerCorrelationId);
425
426impl PeerResponseTerminalCorrelationId {
427 pub fn parse(raw: impl AsRef<str>) -> Result<Self, PeerResponseTerminalFactError> {
428 let raw = raw.as_ref();
429 if raw.trim().is_empty() {
430 return Err(PeerResponseTerminalFactError::EmptyCorrelationId);
431 }
432 uuid::Uuid::parse_str(raw)
433 .map(|uuid| Self(PeerCorrelationId::from_uuid(uuid)))
434 .map_err(|_| PeerResponseTerminalFactError::InvalidCorrelationId {
435 input: raw.to_string(),
436 })
437 }
438
439 pub const fn from_peer_correlation_id(correlation_id: PeerCorrelationId) -> Self {
440 Self(correlation_id)
441 }
442
443 pub const fn as_peer_correlation_id(self) -> PeerCorrelationId {
444 self.0
445 }
446}
447
448impl std::fmt::Display for PeerResponseTerminalCorrelationId {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 self.0.fmt(f)
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
455#[serde(transparent)]
456pub struct PeerResponseTerminalRenderPayload(Option<serde_json::Value>);
457
458impl PeerResponseTerminalRenderPayload {
459 pub fn new(payload: Option<serde_json::Value>) -> Self {
460 Self(payload)
461 }
462
463 pub fn as_ref(&self) -> Option<&serde_json::Value> {
464 self.0.as_ref()
465 }
466}
467
468impl From<Option<serde_json::Value>> for PeerResponseTerminalRenderPayload {
469 fn from(payload: Option<serde_json::Value>) -> Self {
470 Self::new(payload)
471 }
472}
473
474#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
475pub struct PeerResponseTerminalSource {
476 #[serde(default, skip_serializing_if = "Option::is_none")]
477 pub transport_identity: Option<PeerResponseTerminalTransportIdentity>,
478 pub route_identity: PeerResponseTerminalRouteIdentity,
479 pub display_identity: PeerResponseTerminalDisplayIdentity,
480}
481
482impl PeerResponseTerminalSource {
483 pub fn new(
484 transport_identity: Option<PeerResponseTerminalTransportIdentity>,
485 route_identity: PeerResponseTerminalRouteIdentity,
486 display_identity: PeerResponseTerminalDisplayIdentity,
487 ) -> Self {
488 Self {
489 transport_identity,
490 route_identity,
491 display_identity,
492 }
493 }
494
495 pub fn parse(
496 transport_identity: Option<impl Into<String>>,
497 route_identity: impl Into<String>,
498 display_identity: impl Into<String>,
499 ) -> Result<Self, PeerResponseTerminalFactError> {
500 Ok(Self::new(
501 transport_identity
502 .map(PeerResponseTerminalTransportIdentity::parse)
503 .transpose()?,
504 PeerResponseTerminalRouteIdentity::parse(route_identity)?,
505 PeerResponseTerminalDisplayIdentity::parse(display_identity)?,
506 ))
507 }
508}
509
510#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
511pub struct PeerResponseTerminalFact {
512 pub source: PeerResponseTerminalSource,
513 pub correlation_id: PeerResponseTerminalCorrelationId,
514 pub status: PeerResponseTerminalProjectionStatus,
515 pub render_payload: PeerResponseTerminalRenderPayload,
516}
517
518impl PeerResponseTerminalFact {
519 pub fn new(
520 source: PeerResponseTerminalSource,
521 correlation_id: PeerResponseTerminalCorrelationId,
522 status: PeerResponseTerminalProjectionStatus,
523 render_payload: PeerResponseTerminalRenderPayload,
524 ) -> Self {
525 Self {
526 source,
527 correlation_id,
528 status,
529 render_payload,
530 }
531 }
532
533 pub fn prompt_text(&self) -> String {
534 format!(
535 "Peer terminal response from {}. Request ID: {}. Status: {}. Result: {}.",
536 self.source.display_identity,
537 self.correlation_id,
538 self.status.label(),
539 format_peer_projection_payload(self.render_payload.as_ref())
540 )
541 }
542
543 pub fn context_key(&self) -> String {
544 peer_response_terminal_context_key(&self.source.route_identity, self.correlation_id)
545 }
546
547 pub fn render_payload_value(&self) -> Option<&serde_json::Value> {
550 self.render_payload.as_ref()
551 }
552}
553
554#[derive(Debug, Clone, PartialEq)]
555pub enum PeerConversationProjection {
556 Message {
557 peer_id: String,
558 },
559 Request {
560 peer_id: crate::comms::PeerId,
561 display_name: Option<String>,
562 request_id: String,
563 intent: String,
564 payload: Option<serde_json::Value>,
565 },
566 ResponseProgress {
567 peer_id: String,
568 request_id: String,
569 phase: PeerResponseProgressProjectionPhase,
570 payload: Option<serde_json::Value>,
571 },
572 ResponseTerminal {
573 fact: PeerResponseTerminalFact,
574 },
575}
576
577impl PeerConversationProjection {
578 pub fn response_terminal(fact: PeerResponseTerminalFact) -> Self {
579 Self::ResponseTerminal { fact }
580 }
581
582 pub fn block_prefix_text(&self) -> Option<String> {
583 match self {
584 Self::Message { peer_id } => Some(format!("Peer message from {peer_id}")),
585 Self::Request { .. }
586 | Self::ResponseProgress { .. }
587 | Self::ResponseTerminal { .. } => None,
588 }
589 }
590
591 pub fn prompt_text(&self) -> String {
592 match self {
593 Self::Message { .. } => String::new(),
594 Self::Request {
595 peer_id,
596 display_name,
597 request_id,
598 intent,
599 payload,
600 } => {
601 let display_suffix = display_name
602 .as_deref()
603 .map(str::trim)
604 .filter(|name| !name.is_empty())
605 .map(|name| format!(" (display_name: {name})"))
606 .unwrap_or_default();
607 let response_call = crate::interaction::SendResponseCallProjection::new(
608 *peer_id,
609 display_name.as_deref(),
610 request_id.clone(),
611 );
612 format!(
613 "Peer request from peer_id {peer_id}{display_suffix}. Intent: {intent}. Request ID: {request_id}. Params: {}. This is not a normal user request and not a prompt for direct user-facing output. {} Do not use send_message for this reply.",
614 format_peer_projection_payload(payload.as_ref()),
615 response_call.instruction_text()
616 )
617 }
618 Self::ResponseProgress {
619 peer_id,
620 request_id,
621 phase,
622 payload,
623 } => format!(
624 "Peer response progress from {peer_id}. Request ID: {request_id}. Phase: {}. Payload: {}.",
625 phase.label(),
626 format_peer_projection_payload(payload.as_ref())
627 ),
628 Self::ResponseTerminal { fact } => fact.prompt_text(),
629 }
630 }
631
632 pub fn context_key(&self) -> Option<String> {
633 match self {
634 Self::ResponseTerminal { fact } => Some(fact.context_key()),
635 Self::Message { .. } | Self::Request { .. } | Self::ResponseProgress { .. } => None,
636 }
637 }
638}
639
640pub fn peer_response_terminal_context_key(
641 route_identity: &PeerResponseTerminalRouteIdentity,
642 correlation_id: PeerResponseTerminalCorrelationId,
643) -> String {
644 format!("peer_response_terminal:{route_identity}:{correlation_id}")
645}
646
647fn format_peer_projection_payload(payload: Option<&serde_json::Value>) -> String {
648 serde_json::to_string_pretty(payload.unwrap_or(&serde_json::Value::Null))
649 .unwrap_or_else(|_| "null".to_string())
650}
651
652#[derive(Debug, Clone, PartialEq, Eq)]
657pub struct TurnStateSnapshot {
658 pub active_run_id: Option<RunId>,
659 pub loop_state: LoopState,
665 pub turn_phase: TurnPhase,
666 pub turn_terminal: bool,
673 pub primitive_kind: Option<TurnPrimitiveKind>,
676 pub admitted_content_shape: Option<ContentShape>,
677 pub vision_enabled: bool,
678 pub image_tool_results_enabled: bool,
679 pub tool_calls_pending: u64,
680 pub pending_op_refs: BTreeSet<AsyncOpRef>,
681 pub barrier_operation_ids: BTreeSet<OperationId>,
682 pub has_barrier_ops: bool,
683 pub barrier_satisfied: bool,
684 pub boundary_count: u64,
685 pub cancel_after_boundary: bool,
686 pub terminal_outcome: Option<TurnTerminalOutcome>,
689 pub terminal_cause_kind: Option<TurnTerminalCauseKind>,
692 pub extraction_attempts: u64,
693 pub max_extraction_retries: u64,
694 pub extraction_active: bool,
700 pub llm_retry_attempt: u32,
701 pub llm_retry_max_retries: u32,
702 pub llm_retry_selected_delay_ms: u64,
703}
704
705pub trait TurnStateHandle: Send + Sync {
707 fn apply_turn_input(
710 &self,
711 input: TurnExecutionInput,
712 ) -> Result<Vec<TurnExecutionEffect>, DslTransitionError>;
713
714 fn start_conversation_run(
715 &self,
716 run_id: RunId,
717 primitive_kind: TurnPrimitiveKind,
718 admitted_content_shape: ContentShape,
719 vision_enabled: bool,
720 image_tool_results_enabled: bool,
721 max_extraction_retries: u64,
722 ) -> Result<(), DslTransitionError>;
723
724 fn start_immediate_append(&self, run_id: RunId) -> Result<(), DslTransitionError>;
725
726 fn start_immediate_context(&self, run_id: RunId) -> Result<(), DslTransitionError>;
727
728 fn primitive_applied(&self, run_id: RunId) -> Result<(), DslTransitionError>;
729
730 fn llm_returned_tool_calls(
731 &self,
732 run_id: RunId,
733 tool_count: u64,
734 ) -> Result<(), DslTransitionError>;
735
736 fn llm_returned_terminal(&self, run_id: RunId) -> Result<(), DslTransitionError>;
737
738 fn register_pending_ops(
739 &self,
740 run_id: RunId,
741 op_refs: BTreeSet<AsyncOpRef>,
742 barrier_operation_ids: BTreeSet<OperationId>,
743 ) -> Result<(), DslTransitionError>;
744
745 fn tool_calls_resolved(&self, run_id: RunId) -> Result<(), DslTransitionError>;
746
747 fn ops_barrier_satisfied(
748 &self,
749 run_id: RunId,
750 operation_ids: BTreeSet<OperationId>,
751 ) -> Result<(), DslTransitionError>;
752
753 fn boundary_continue(&self, run_id: RunId) -> Result<(), DslTransitionError>;
754
755 fn boundary_complete(&self, run_id: RunId) -> Result<(), DslTransitionError>;
756
757 fn enter_extraction(&self, run_id: RunId, max_retries: u32) -> Result<(), DslTransitionError>;
758
759 fn extraction_start(&self, run_id: RunId) -> Result<(), DslTransitionError>;
760
761 fn extraction_validation_passed(&self, run_id: RunId) -> Result<(), DslTransitionError>;
762
763 fn extraction_validation_failed(
764 &self,
765 run_id: RunId,
766 error: String,
767 ) -> Result<(), DslTransitionError>;
768
769 fn extraction_failed(&self, run_id: RunId, error: String) -> Result<(), DslTransitionError>;
770
771 fn recoverable_failure(
772 &self,
773 run_id: RunId,
774 retry: LlmRetrySchedule,
775 ) -> Result<(), DslTransitionError>;
776
777 fn fatal_failure(
778 &self,
779 run_id: RunId,
780 failure: TurnFailureSource,
781 ) -> Result<(), DslTransitionError>;
782
783 fn retry_requested(&self, run_id: RunId, retry_attempt: u32) -> Result<(), DslTransitionError>;
784
785 fn cancel_now(&self, run_id: RunId) -> Result<(), DslTransitionError>;
786
787 fn request_cancel_after_boundary(&self, run_id: RunId) -> Result<(), DslTransitionError>;
788
789 fn cancellation_observed(&self, run_id: RunId) -> Result<(), DslTransitionError>;
790
791 fn acknowledge_terminal(&self, run_id: RunId) -> Result<(), DslTransitionError>;
792
793 fn turn_limit_reached(
794 &self,
795 run_id: RunId,
796 turn_count: u64,
797 max_turns: u64,
798 ) -> Result<(), DslTransitionError>;
799
800 fn budget_exhausted(&self, run_id: RunId) -> Result<(), DslTransitionError>;
801
802 fn time_budget_exceeded(&self, run_id: RunId) -> Result<(), DslTransitionError>;
803
804 fn force_cancel_no_run(&self) -> Result<(), DslTransitionError>;
805
806 fn run_completed(&self, run_id: RunId) -> Result<(), DslTransitionError>;
807
808 fn run_failed(
809 &self,
810 run_id: RunId,
811 reason: TurnFailureReason,
812 ) -> Result<(), DslTransitionError>;
813
814 fn run_cancelled(&self, run_id: RunId) -> Result<(), DslTransitionError>;
815
816 fn snapshot(&self) -> TurnStateSnapshot;
817}
818
819pub trait CommsDrainHandle: Send + Sync {
829 fn ensure_drain_running(&self) -> Result<(), DslTransitionError>;
831
832 fn spawn_drain(&self, mode: DrainMode) -> Result<(), DslTransitionError>;
834
835 fn stop_drain(&self) -> Result<(), DslTransitionError>;
837
838 fn notify_drain_exited(&self, reason: DrainExitReason) -> Result<(), DslTransitionError>;
840}
841
842#[derive(Debug, Clone, PartialEq, Eq)]
847pub struct SurfaceSnapshot {
848 pub surface_id: String,
849 pub base_state: Option<ExternalToolSurfaceBaseState>,
852 pub pending_op: ExternalToolSurfacePendingOp,
853 pub staged_op: ExternalToolSurfaceStagedOp,
854 pub staged_intent_sequence: Option<u64>,
855 pub pending_task_sequence: Option<u64>,
856 pub pending_lineage_sequence: Option<u64>,
857 pub inflight_calls: u64,
858 pub last_delta_operation: Option<ExternalToolSurfaceDeltaOperation>,
860 pub last_delta_phase: Option<ExternalToolSurfaceDeltaPhase>,
862 pub removal_draining_since_ms: Option<u64>,
863 pub removal_timeout_at_ms: Option<u64>,
864 pub removal_applied_at_turn: Option<u64>,
865}
866
867#[derive(Debug, Clone, PartialEq, Eq)]
868pub struct SurfaceDiagnosticSnapshot {
869 pub surface_phase: ExternalToolSurfaceGlobalPhase,
870 pub known_surfaces: BTreeSet<String>,
871 pub visible_surfaces: BTreeSet<String>,
872 pub snapshot_epoch: u64,
873 pub snapshot_aligned_epoch: u64,
874 pub has_pending_or_staged: bool,
875 pub entries: Vec<SurfaceSnapshot>,
876}
877
878#[derive(Debug, Clone, PartialEq, Eq)]
879pub enum ExternalToolSurfaceInput {
880 SetRemovalTimeout {
881 timeout_ms: u64,
882 },
883 StageAdd {
884 surface_id: String,
885 now_ms: u64,
886 },
887 StageRemove {
888 surface_id: String,
889 now_ms: u64,
890 },
891 StageReload {
892 surface_id: String,
893 now_ms: u64,
894 },
895 ApplyBoundary {
896 surface_id: String,
897 now_ms: u64,
898 staged_intent_sequence: u64,
899 applied_at_turn: u64,
900 },
901 MarkPendingSucceeded {
902 surface_id: String,
903 pending_task_sequence: u64,
904 staged_intent_sequence: u64,
905 },
906 MarkPendingFailed {
907 surface_id: String,
908 pending_task_sequence: u64,
909 staged_intent_sequence: u64,
910 cause: ExternalToolSurfaceFailureCause,
911 },
912 CallStarted {
913 surface_id: String,
914 },
915 CallFinished {
916 surface_id: String,
917 },
918 FinalizeRemovalClean {
919 surface_id: String,
920 },
921 FinalizeRemovalForced {
922 surface_id: String,
923 },
924 SnapshotAligned {
925 epoch: u64,
926 },
927 Shutdown,
928}
929
930#[derive(Debug, Clone, PartialEq, Eq)]
931pub enum ExternalToolSurfaceEffect {
932 ScheduleSurfaceCompletion {
933 surface_id: String,
934 operation: ExternalToolSurfaceDeltaOperation,
935 pending_task_sequence: u64,
936 staged_intent_sequence: u64,
937 applied_at_turn: u64,
938 },
939 RefreshVisibleSurfaceSet {
940 snapshot_epoch: u64,
941 },
942 EmitExternalToolDelta {
943 surface_id: String,
944 operation: ExternalToolSurfaceDeltaOperation,
945 phase: ExternalToolSurfaceDeltaPhase,
946 cause: Option<ExternalToolSurfaceFailureCause>,
947 },
948 CloseSurfaceConnection {
949 surface_id: String,
950 },
951 RejectSurfaceCall {
952 surface_id: String,
953 cause: ExternalToolSurfaceFailureCause,
954 },
955}
956
957#[derive(Debug, Clone, PartialEq, Eq)]
958pub struct ExternalToolSurfaceTransition {
959 pub phase: ExternalToolSurfaceGlobalPhase,
960 pub effects: Vec<ExternalToolSurfaceEffect>,
961}
962
963pub trait ExternalToolSurfaceHandle: Send + Sync {
965 fn apply_surface_input(
966 &self,
967 input: ExternalToolSurfaceInput,
968 ) -> Result<ExternalToolSurfaceTransition, DslTransitionError>;
969
970 fn register(&self, surface_id: String) -> Result<(), DslTransitionError>;
971
972 fn stage_add(&self, surface_id: String, now_ms: u64) -> Result<(), DslTransitionError>;
973
974 fn stage_remove(&self, surface_id: String, now_ms: u64) -> Result<(), DslTransitionError>;
975
976 fn stage_reload(&self, surface_id: String, now_ms: u64) -> Result<(), DslTransitionError>;
977
978 fn apply_boundary(
979 &self,
980 surface_id: String,
981 now_ms: u64,
982 staged_intent_sequence: u64,
983 applied_at_turn: u64,
984 ) -> Result<(), DslTransitionError>;
985
986 fn mark_pending_succeeded(
987 &self,
988 surface_id: String,
989 pending_task_sequence: u64,
990 staged_intent_sequence: u64,
991 ) -> Result<(), DslTransitionError>;
992
993 fn mark_pending_failed(
994 &self,
995 surface_id: String,
996 pending_task_sequence: u64,
997 staged_intent_sequence: u64,
998 cause: ExternalToolSurfaceFailureCause,
999 ) -> Result<(), DslTransitionError>;
1000
1001 fn call_started(&self, surface_id: String) -> Result<(), DslTransitionError>;
1002
1003 fn call_finished(&self, surface_id: String) -> Result<(), DslTransitionError>;
1004
1005 fn finalize_removal_clean(&self, surface_id: String) -> Result<(), DslTransitionError>;
1006
1007 fn finalize_removal_forced(&self, surface_id: String) -> Result<(), DslTransitionError>;
1008
1009 fn snapshot_aligned(&self, epoch: u64) -> Result<(), DslTransitionError>;
1010
1011 fn shutdown_surface(&self) -> Result<(), DslTransitionError>;
1012
1013 fn surface_snapshot(&self, surface_id: &str) -> Option<SurfaceSnapshot>;
1014
1015 fn diagnostic_snapshot(&self) -> SurfaceDiagnosticSnapshot;
1016
1017 fn visible_surfaces(&self) -> BTreeSet<String>;
1018
1019 fn removing_surfaces(&self) -> BTreeSet<String>;
1020
1021 fn pending_surfaces(&self) -> BTreeSet<String>;
1022
1023 fn has_pending_or_staged(&self) -> bool;
1024
1025 fn snapshot_epoch(&self) -> u64;
1026
1027 fn snapshot_aligned_epoch(&self) -> u64;
1028}
1029
1030pub trait PeerCommsHandle: Send + Sync {
1044 fn classify_external_envelope(
1047 &self,
1048 facts: PeerIngressEnvelopeFacts,
1049 ) -> Result<PeerIngressAdmission, DslTransitionError>;
1050
1051 fn classify_plain_event(
1054 &self,
1055 facts: PeerIngressPlainEventFacts,
1056 ) -> Result<PeerIngressAdmission, DslTransitionError>;
1057
1058 fn resolve_peer_ingress_receive(
1061 &self,
1062 facts: PeerIngressReceiveFacts,
1063 ) -> Result<PeerIngressReceiveAuthority, DslTransitionError>;
1064
1065 fn resolve_peer_ingress_dequeue(
1068 &self,
1069 facts: PeerIngressDequeueFacts,
1070 ) -> Result<PeerIngressDequeueAuthority, DslTransitionError>;
1071
1072 fn set_peer_ingress_context(&self, keep_alive: bool) -> Result<(), DslTransitionError>;
1074
1075 fn install_generated_peer_comms_on_target(
1079 &self,
1080 _expected_owner: &crate::comms::GeneratedPeerCommsOwnerToken,
1081 _target: &(dyn PeerCommsInstallTarget + '_),
1082 ) -> Result<(), String> {
1083 Err("peer-comms handle does not expose generated install target authority".to_string())
1084 }
1085}
1086
1087#[derive(Clone)]
1088pub struct GeneratedPeerCommsInstallFactory {
1089 handle: std::sync::Arc<dyn PeerCommsHandle>,
1090 owner_token: crate::comms::GeneratedPeerCommsOwnerToken,
1091}
1092
1093impl std::fmt::Debug for GeneratedPeerCommsInstallFactory {
1094 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095 f.debug_struct("GeneratedPeerCommsInstallFactory")
1096 .field("handle", &"<dyn PeerCommsHandle>")
1097 .field("owner_token", &self.owner_token)
1098 .finish()
1099 }
1100}
1101
1102impl GeneratedPeerCommsInstallFactory {
1103 #[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1104 #[doc(hidden)]
1105 pub fn __from_runtime_generated_authority(
1106 token: &'static (dyn Any + Send + Sync),
1107 handle: std::sync::Arc<dyn PeerCommsHandle>,
1108 owner_token: std::sync::Arc<dyn Any + Send + Sync>,
1109 ) -> Result<Self, String> {
1110 validate_peer_comms_install_bridge_token(token)?;
1111 Ok(Self {
1112 handle,
1113 owner_token: crate::comms::GeneratedPeerCommsOwnerToken::from_generated_owner_token(
1114 owner_token,
1115 ),
1116 })
1117 }
1118
1119 pub fn peer_comms_handle(&self) -> &std::sync::Arc<dyn PeerCommsHandle> {
1120 &self.handle
1121 }
1122
1123 pub fn install_on_target(
1124 &self,
1125 target: &(dyn PeerCommsInstallTarget + '_),
1126 ) -> Result<(), String> {
1127 self.handle
1128 .install_generated_peer_comms_on_target(&self.owner_token, target)
1129 }
1130}
1131
1132#[derive(Clone)]
1133pub struct GeneratedPeerCommsInstall {
1134 handle: std::sync::Arc<dyn PeerCommsHandle>,
1135 owner_token: crate::comms::GeneratedPeerCommsOwnerToken,
1136 target_peer_id: crate::comms::PeerId,
1137}
1138
1139impl std::fmt::Debug for GeneratedPeerCommsInstall {
1140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1141 f.debug_struct("GeneratedPeerCommsInstall")
1142 .field("handle", &"<dyn PeerCommsHandle>")
1143 .field("owner_token", &self.owner_token)
1144 .field("target_peer_id", &self.target_peer_id)
1145 .finish()
1146 }
1147}
1148
1149impl GeneratedPeerCommsInstall {
1150 #[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1151 #[doc(hidden)]
1152 pub fn __from_runtime_generated_authority(
1153 token: &'static (dyn Any + Send + Sync),
1154 handle: std::sync::Arc<dyn PeerCommsHandle>,
1155 owner_token: std::sync::Arc<dyn Any + Send + Sync>,
1156 target_peer_id: crate::comms::PeerId,
1157 ) -> Result<Self, String> {
1158 validate_peer_comms_install_bridge_token(token)?;
1159 Ok(Self {
1160 handle,
1161 owner_token: crate::comms::GeneratedPeerCommsOwnerToken::from_generated_owner_token(
1162 owner_token,
1163 ),
1164 target_peer_id,
1165 })
1166 }
1167
1168 pub fn peer_comms_handle(&self) -> &std::sync::Arc<dyn PeerCommsHandle> {
1169 &self.handle
1170 }
1171
1172 pub fn owner_token(&self) -> crate::comms::GeneratedPeerCommsOwnerToken {
1173 self.owner_token.clone()
1174 }
1175
1176 pub fn target_peer_id(&self) -> crate::comms::PeerId {
1177 self.target_peer_id
1178 }
1179}
1180
1181#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1182#[allow(improper_ctypes_definitions, unsafe_code)]
1183unsafe extern "Rust" {
1184 #[link_name = concat!(
1185 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_comms_trust_reconcile_",
1186 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
1187 )]
1188 fn runtime_peer_comms_install_generated_authority_bridge_token_is_valid(
1189 token: &(dyn Any + Send + Sync),
1190 ) -> bool;
1191}
1192
1193#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1194fn validate_peer_comms_install_bridge_token(token: &(dyn Any + Send + Sync)) -> Result<(), String> {
1195 #[allow(unsafe_code)]
1196 let valid =
1197 unsafe { runtime_peer_comms_install_generated_authority_bridge_token_is_valid(token) };
1198 if valid {
1199 Ok(())
1200 } else {
1201 Err("generated peer-comms install requires the matching generated runtime protocol bridge token".into())
1202 }
1203}
1204
1205pub trait PeerCommsInstallTarget: crate::agent::CommsRuntime {
1212 fn generated_peer_comms_target_endpoint(
1213 &self,
1214 ) -> Result<crate::comms::TrustedPeerDescriptor, String> {
1215 let peer_id = self
1216 .peer_id()
1217 .ok_or_else(|| "runtime peer_id unavailable".to_string())?;
1218 let name = self
1219 .comms_name()
1220 .ok_or_else(|| "runtime comms_name unavailable".to_string())?;
1221 let address = self
1222 .advertised_address()
1223 .ok_or_else(|| "runtime advertised_address unavailable".to_string())?;
1224 let pubkey = self
1225 .public_key_bytes()
1226 .ok_or_else(|| "runtime public_key_bytes unavailable".to_string())?;
1227 crate::comms::TrustedPeerDescriptor::unsigned_with_pubkey(
1228 name,
1229 peer_id.to_string(),
1230 pubkey,
1231 address,
1232 )
1233 .map_err(|error| format!("runtime peer-comms install target endpoint invalid: {error}"))
1234 }
1235
1236 fn install_generated_peer_comms_handle(
1237 &self,
1238 install: GeneratedPeerCommsInstall,
1239 ) -> Result<(), String>;
1240}
1241
1242pub trait SessionAdmissionHandle: Send + Sync {
1257 fn ingest(
1264 &self,
1265 runtime_id: &str,
1266 work_id: &str,
1267 origin: InputSource,
1268 ) -> Result<(), DslTransitionError>;
1269
1270 fn accept_with_completion(
1281 &self,
1282 input_id: &InputId,
1283 request_immediate_processing: bool,
1284 interrupt_yielding: bool,
1285 wake_if_idle: bool,
1286 ) -> Result<(), DslTransitionError>;
1287
1288 fn accept_without_wake(&self, input_id: &InputId) -> Result<(), DslTransitionError>;
1290
1291 fn prepare(&self, run_id: &RunId) -> Result<(), DslTransitionError>;
1293}
1294
1295#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1301pub struct LeaseKey {
1302 pub realm: crate::connection::RealmId,
1303 pub binding: crate::connection::BindingId,
1304 pub profile: Option<crate::connection::ProfileId>,
1305}
1306
1307impl LeaseKey {
1308 pub fn new(
1309 realm: crate::connection::RealmId,
1310 binding: crate::connection::BindingId,
1311 profile: Option<crate::connection::ProfileId>,
1312 ) -> Self {
1313 Self {
1314 realm,
1315 binding,
1316 profile,
1317 }
1318 }
1319
1320 pub fn from_auth_binding(auth_binding: &crate::connection::AuthBindingRef) -> Self {
1321 Self {
1322 realm: auth_binding.realm.clone(),
1323 binding: auth_binding.binding.clone(),
1324 profile: auth_binding.profile.clone(),
1325 }
1326 }
1327}
1328
1329impl std::fmt::Display for LeaseKey {
1330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1331 match &self.profile {
1332 Some(profile) => write!(f, "{}:{}:{}", self.realm, self.binding, profile),
1333 None => write!(f, "{}:{}", self.realm, self.binding),
1334 }
1335 }
1336}
1337
1338#[derive(Debug, Clone, PartialEq, Eq)]
1355pub struct AuthLeaseSnapshot {
1356 pub phase: Option<AuthLeasePhase>,
1357 pub expires_at: Option<u64>,
1358 pub credential_present: bool,
1359 pub generation: u64,
1360 pub credential_published_at_millis: Option<u64>,
1361}
1362
1363#[derive(Debug, Clone, PartialEq, Eq)]
1370pub struct AuthLeaseRestoreSnapshot {
1371 lease_key: LeaseKey,
1372 snapshot: AuthLeaseSnapshot,
1373 captured_by: std::any::TypeId,
1374 captured_by_instance: usize,
1375}
1376
1377impl AuthLeaseRestoreSnapshot {
1378 fn capture(
1379 lease_key: LeaseKey,
1380 snapshot: AuthLeaseSnapshot,
1381 captured_by: std::any::TypeId,
1382 captured_by_instance: usize,
1383 ) -> Self {
1384 Self {
1385 lease_key,
1386 snapshot,
1387 captured_by,
1388 captured_by_instance,
1389 }
1390 }
1391
1392 pub fn lease_key(&self) -> &LeaseKey {
1393 &self.lease_key
1394 }
1395
1396 pub fn snapshot(&self) -> &AuthLeaseSnapshot {
1397 &self.snapshot
1398 }
1399
1400 #[doc(hidden)]
1401 pub fn captured_by_type_id(&self) -> std::any::TypeId {
1402 self.captured_by
1403 }
1404
1405 #[doc(hidden)]
1406 pub fn captured_by_instance_id(&self) -> usize {
1407 self.captured_by_instance
1408 }
1409}
1410
1411#[derive(Debug, Clone, PartialEq, Eq)]
1419pub struct AuthLeaseTransition {
1420 lease_key: LeaseKey,
1421 phase: AuthLeasePhase,
1422 expires_at: u64,
1423 generation: u64,
1424 credential_published_at_millis: Option<u64>,
1425}
1426
1427impl AuthLeaseTransition {
1428 pub fn lease_key(&self) -> &LeaseKey {
1429 &self.lease_key
1430 }
1431
1432 pub fn phase(&self) -> AuthLeasePhase {
1433 self.phase
1434 }
1435
1436 pub fn expires_at(&self) -> u64 {
1437 self.expires_at
1438 }
1439
1440 pub fn generation(&self) -> u64 {
1441 self.generation
1442 }
1443
1444 pub fn credential_published_at_millis(&self) -> Option<u64> {
1445 self.credential_published_at_millis
1446 }
1447
1448 #[cfg_attr(
1449 any(not(meerkat_internal_generated_authority_bridge), test),
1450 allow(dead_code)
1451 )]
1452 fn from_generated_auth_lease_publication_parts(
1453 lease_key: LeaseKey,
1454 phase: AuthLeasePhase,
1455 expires_at: u64,
1456 generation: u64,
1457 credential_published_at_millis: Option<u64>,
1458 ) -> Self {
1459 Self {
1460 lease_key,
1461 phase,
1462 expires_at,
1463 generation,
1464 credential_published_at_millis,
1465 }
1466 }
1467}
1468
1469#[derive(Clone)]
1476pub struct GeneratedAuthLeaseHandle {
1477 inner: Arc<dyn AuthLeaseHandle>,
1478}
1479
1480impl GeneratedAuthLeaseHandle {
1481 pub fn as_handle(&self) -> &dyn AuthLeaseHandle {
1482 self.inner.as_ref()
1483 }
1484
1485 pub fn clone_handle(&self) -> Arc<dyn AuthLeaseHandle> {
1486 Arc::clone(&self.inner)
1487 }
1488
1489 #[cfg_attr(
1490 any(not(meerkat_internal_generated_authority_bridge), test),
1491 allow(dead_code)
1492 )]
1493 fn from_generated_authority(inner: Arc<dyn AuthLeaseHandle>) -> Self {
1494 Self { inner }
1495 }
1496}
1497
1498impl std::fmt::Debug for GeneratedAuthLeaseHandle {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct("GeneratedAuthLeaseHandle")
1501 .finish_non_exhaustive()
1502 }
1503}
1504
1505impl std::ops::Deref for GeneratedAuthLeaseHandle {
1506 type Target = dyn AuthLeaseHandle;
1507
1508 fn deref(&self) -> &Self::Target {
1509 self.inner.as_ref()
1510 }
1511}
1512
1513impl AsRef<dyn AuthLeaseHandle> for GeneratedAuthLeaseHandle {
1514 fn as_ref(&self) -> &dyn AuthLeaseHandle {
1515 self.inner.as_ref()
1516 }
1517}
1518
1519#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1520#[allow(improper_ctypes_definitions, unsafe_code)]
1521unsafe extern "Rust" {
1522 #[link_name = concat!(
1523 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_auth_lease_lifecycle_publication_",
1524 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
1525 )]
1526 fn runtime_auth_lease_lifecycle_publication_generated_authority_bridge_token_is_valid(
1527 token: &(dyn std::any::Any + Send + Sync),
1528 ) -> bool;
1529}
1530
1531#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1532#[doc(hidden)]
1533#[allow(improper_ctypes_definitions, unsafe_code)]
1534#[unsafe(export_name = concat!(
1535 "__meerkat_core_runtime_generated_auth_lease_transition_build_v1_",
1536 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
1537))]
1538pub(crate) extern "Rust" fn runtime_generated_auth_lease_transition_build(
1539 token: &'static (dyn std::any::Any + Send + Sync),
1540 lease_key: LeaseKey,
1541 phase: AuthLeasePhase,
1542 expires_at: u64,
1543 generation: u64,
1544 credential_published_at_millis: Option<u64>,
1545) -> Result<AuthLeaseTransition, String> {
1546 validate_runtime_generated_authority_bridge_token(token)?;
1547 Ok(
1548 AuthLeaseTransition::from_generated_auth_lease_publication_parts(
1549 lease_key,
1550 phase,
1551 expires_at,
1552 generation,
1553 credential_published_at_millis,
1554 ),
1555 )
1556}
1557
1558#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1559#[doc(hidden)]
1560#[allow(improper_ctypes_definitions, unsafe_code)]
1561#[unsafe(export_name = concat!(
1562 "__meerkat_core_runtime_generated_auth_lease_handle_build_v1_",
1563 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
1564))]
1565pub(crate) extern "Rust" fn runtime_generated_auth_lease_handle_build(
1566 token: &'static (dyn std::any::Any + Send + Sync),
1567 handle: Arc<dyn AuthLeaseHandle>,
1568) -> Result<GeneratedAuthLeaseHandle, String> {
1569 validate_runtime_generated_authority_bridge_token(token)?;
1570 Ok(GeneratedAuthLeaseHandle::from_generated_authority(handle))
1571}
1572
1573#[cfg(all(meerkat_internal_generated_authority_bridge, not(test)))]
1574fn validate_runtime_generated_authority_bridge_token(
1575 token: &(dyn std::any::Any + Send + Sync),
1576) -> Result<(), String> {
1577 #[allow(unsafe_code)]
1578 let valid = unsafe {
1579 runtime_auth_lease_lifecycle_publication_generated_authority_bridge_token_is_valid(token)
1580 };
1581 if valid {
1582 Ok(())
1583 } else {
1584 Err(
1585 "generated auth lease transition requires the generated AuthMachine protocol bridge token"
1586 .into(),
1587 )
1588 }
1589}
1590
1591pub const AUTH_LEASE_TTL_REFRESH_WINDOW_SECS: u64 = 60;
1603
1604pub trait AuthLeaseHandle: Send + Sync + std::any::Any {
1606 fn acquire_lease(
1611 &self,
1612 lease_key: &LeaseKey,
1613 expires_at: u64,
1614 ) -> Result<AuthLeaseTransition, DslTransitionError>;
1615
1616 fn mark_expiring(&self, lease_key: &LeaseKey) -> Result<(), DslTransitionError>;
1618
1619 fn observe_credential_freshness(
1624 &self,
1625 lease_key: &LeaseKey,
1626 now: u64,
1627 refresh_window_secs: u64,
1628 ) -> Result<(), DslTransitionError>;
1629
1630 fn begin_refresh(&self, lease_key: &LeaseKey) -> Result<(), DslTransitionError>;
1638
1639 fn complete_refresh(
1643 &self,
1644 lease_key: &LeaseKey,
1645 new_expires_at: u64,
1646 now: u64,
1647 ) -> Result<AuthLeaseTransition, DslTransitionError>;
1648
1649 fn refresh_failed(
1653 &self,
1654 lease_key: &LeaseKey,
1655 observation: RefreshFailureObservation,
1656 ) -> Result<(), DslTransitionError>;
1657
1658 fn mark_reauth_required(&self, lease_key: &LeaseKey) -> Result<(), DslTransitionError>;
1660
1661 fn release_lease(&self, lease_key: &LeaseKey) -> Result<(), DslTransitionError>;
1664
1665 fn release_credential_lifecycle(&self, lease_key: &LeaseKey) -> Result<(), DslTransitionError> {
1672 self.release_lease(lease_key)
1673 }
1674
1675 fn capture_auth_lifecycle_restore_snapshot(
1681 &self,
1682 lease_key: &LeaseKey,
1683 ) -> AuthLeaseRestoreSnapshot {
1684 AuthLeaseRestoreSnapshot::capture(
1685 lease_key.clone(),
1686 self.snapshot(lease_key),
1687 self.type_id(),
1688 self.auth_lifecycle_restore_instance_id(),
1689 )
1690 }
1691
1692 #[doc(hidden)]
1693 fn auth_lifecycle_restore_instance_id(&self) -> usize {
1694 std::ptr::from_ref(self).cast::<()>() as usize
1695 }
1696
1697 fn restore_auth_lifecycle_snapshot(
1703 &self,
1704 snapshot: &AuthLeaseRestoreSnapshot,
1705 ) -> Result<Option<AuthLeaseTransition>, DslTransitionError> {
1706 let _ = snapshot;
1707 Err(DslTransitionError::no_matching(
1708 "AuthLeaseHandle::restore_auth_lifecycle_snapshot",
1709 "restoring auth lifecycle snapshots requires generated AuthMachine authority",
1710 ))
1711 }
1712
1713 fn restore_published_credential_lifecycle(
1721 &self,
1722 lease_key: &LeaseKey,
1723 publication: &crate::generated::auth_lease_durable_lifecycle_marker::AuthLeaseDurableRestorePublication,
1724 ) -> Result<AuthLeaseTransition, DslTransitionError> {
1725 let _ = (lease_key, publication);
1726 Err(DslTransitionError::no_matching(
1727 "AuthLeaseHandle::restore_published_credential_lifecycle",
1728 "restoring durable auth lifecycle publications requires generated AuthMachine authority",
1729 ))
1730 }
1731
1732 fn resolve_credential_use_admission(
1745 &self,
1746 lease_key: &LeaseKey,
1747 intent: CredentialUseIntent,
1748 ) -> Result<CredentialUseDisposition, DslTransitionError> {
1749 let _ = (lease_key, intent);
1750 Err(DslTransitionError::no_matching(
1751 "AuthLeaseHandle::resolve_credential_use_admission",
1752 "classifying credential-use admission requires generated AuthMachine authority",
1753 ))
1754 }
1755
1756 fn resolve_oauth_login_credential_disposition(
1772 &self,
1773 lease_key: &LeaseKey,
1774 facts: OAuthLoginCredentialFacts,
1775 ) -> Result<CredentialUseDisposition, DslTransitionError> {
1776 let _ = (lease_key, facts);
1777 Err(DslTransitionError::no_matching(
1778 "AuthLeaseHandle::resolve_oauth_login_credential_disposition",
1779 "classifying OAuth-login credential disposition requires generated AuthMachine authority",
1780 ))
1781 }
1782
1783 fn snapshot(&self, lease_key: &LeaseKey) -> AuthLeaseSnapshot;
1785}
1786
1787pub trait McpServerLifecycleHandle: Send + Sync {
1808 fn apply_connect_pending(&self, server_id: &str) -> Result<(), DslTransitionError>;
1811
1812 fn apply_connected(&self, server_id: &str) -> Result<(), DslTransitionError>;
1814
1815 fn apply_failed(&self, server_id: &str, error: &str) -> Result<(), DslTransitionError>;
1817
1818 fn apply_disconnected(&self, server_id: &str) -> Result<(), DslTransitionError>;
1820
1821 fn apply_reload(&self, server_id: &str) -> Result<(), DslTransitionError>;
1824
1825 fn pending_server_ids(&self) -> BTreeSet<String>;
1830}
1831
1832#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1841#[non_exhaustive]
1842pub enum PeerTerminalDisposition {
1843 Completed,
1845 Failed,
1847}
1848
1849pub trait PeerInteractionHandle: Send + Sync {
1863 fn request_sent(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1867
1868 fn response_progress(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1874
1875 fn response_terminal(
1882 &self,
1883 corr_id: PeerCorrelationId,
1884 disposition: PeerTerminalDisposition,
1885 ) -> Result<(), DslTransitionError>;
1886
1887 fn response_rejected(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1895
1896 fn request_timed_out(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1902
1903 fn request_send_failed(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1910
1911 fn request_received(
1915 &self,
1916 corr_id: PeerCorrelationId,
1917 handling_mode: HandlingMode,
1918 ) -> Result<(), DslTransitionError>;
1919
1920 fn classify_response_reply(
1923 &self,
1924 status: crate::ResponseStatus,
1925 ) -> Result<crate::TerminalityClass, DslTransitionError>;
1926
1927 fn response_replied(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
1931
1932 fn outbound_state(&self, corr_id: PeerCorrelationId) -> Option<OutboundPeerRequestState>;
1936
1937 fn inbound_state(&self, corr_id: PeerCorrelationId) -> Option<InboundPeerRequestState>;
1939
1940 fn inbound_handling_mode(&self, corr_id: PeerCorrelationId) -> Option<HandlingMode>;
1942
1943 fn install_cleanup_observer(&self, observer: Arc<dyn PeerInteractionCleanupObserver>);
1952}
1953
1954pub trait PeerInteractionCleanupObserver: Send + Sync {
1964 fn on_peer_interaction_cleanup(&self, corr_id: PeerCorrelationId);
1971}
1972
1973pub trait SessionContextHandle: Send + Sync {
1988 fn context_advanced(&self, updated_at_ms: u64) -> Result<bool, DslTransitionError>;
1996
1997 fn current_watermark_ms(&self) -> u64;
2005
2006 fn install_observer(&self, observer: Arc<dyn SessionContextAdvancedObserver>);
2010
2011 fn install_observer_with_baseline(
2025 &self,
2026 observer: Arc<dyn SessionContextAdvancedObserver>,
2027 ) -> u64;
2028}
2029
2030pub trait SessionContextAdvancedObserver: Send + Sync {
2040 fn on_session_context_advanced(&self, updated_at_ms: u64);
2044}
2045
2046#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
2052pub enum SessionClaimError {
2053 #[error("session identity already claimed: {0}")]
2055 SessionIdentityInUse(SessionId),
2056}
2057
2058pub struct SessionClaim {
2064 session_id: SessionId,
2065 handle: Arc<dyn SessionClaimHandle>,
2066}
2067
2068impl SessionClaim {
2069 pub fn new(session_id: SessionId, handle: Arc<dyn SessionClaimHandle>) -> Self {
2073 Self { session_id, handle }
2074 }
2075
2076 pub fn session_id(&self) -> &SessionId {
2078 &self.session_id
2079 }
2080}
2081
2082impl Drop for SessionClaim {
2083 fn drop(&mut self) {
2084 self.handle.release(&self.session_id);
2085 }
2086}
2087
2088impl std::fmt::Debug for SessionClaim {
2089 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2090 f.debug_struct("SessionClaim")
2091 .field("session_id", &self.session_id)
2092 .finish_non_exhaustive()
2093 }
2094}
2095
2096pub trait SessionClaimHandle: Send + Sync {
2105 fn try_acquire(
2113 self: Arc<Self>,
2114 session_id: &SessionId,
2115 ) -> Result<SessionClaim, SessionClaimError>;
2116
2117 fn release(&self, session_id: &SessionId);
2123}
2124
2125pub struct DefaultSessionClaimRegistry {
2131 claims: std::sync::Mutex<std::collections::HashSet<SessionId>>,
2132}
2133
2134impl DefaultSessionClaimRegistry {
2135 pub fn new() -> Self {
2137 Self {
2138 claims: std::sync::Mutex::new(std::collections::HashSet::new()),
2139 }
2140 }
2141
2142 pub fn global() -> Arc<Self> {
2144 use std::sync::OnceLock;
2145 static GLOBAL: OnceLock<Arc<DefaultSessionClaimRegistry>> = OnceLock::new();
2146 Arc::clone(GLOBAL.get_or_init(|| Arc::new(DefaultSessionClaimRegistry::new())))
2147 }
2148}
2149
2150impl Default for DefaultSessionClaimRegistry {
2151 fn default() -> Self {
2152 Self::new()
2153 }
2154}
2155
2156impl SessionClaimHandle for DefaultSessionClaimRegistry {
2157 fn try_acquire(
2158 self: Arc<Self>,
2159 session_id: &SessionId,
2160 ) -> Result<SessionClaim, SessionClaimError> {
2161 let mut claims = self
2162 .claims
2163 .lock()
2164 .unwrap_or_else(std::sync::PoisonError::into_inner);
2165 if !claims.insert(session_id.clone()) {
2166 return Err(SessionClaimError::SessionIdentityInUse(session_id.clone()));
2167 }
2168 drop(claims);
2169 Ok(SessionClaim::new(
2170 session_id.clone(),
2171 self as Arc<dyn SessionClaimHandle>,
2172 ))
2173 }
2174
2175 fn release(&self, session_id: &SessionId) {
2176 let mut claims = self
2177 .claims
2178 .lock()
2179 .unwrap_or_else(std::sync::PoisonError::into_inner);
2180 claims.remove(session_id);
2181 }
2182}
2183
2184pub trait InteractionStreamHandle: Send + Sync {
2202 fn reserved(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
2208
2209 fn attached(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
2214
2215 fn completed(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
2219
2220 fn expired(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
2224
2225 fn closed_early(&self, corr_id: PeerCorrelationId) -> Result<(), DslTransitionError>;
2229
2230 fn state(&self, corr_id: PeerCorrelationId) -> Option<InteractionStreamState>;
2237
2238 fn install_cleanup_observer(&self, observer: Arc<dyn InteractionStreamCleanupObserver>);
2243}
2244
2245pub trait InteractionStreamCleanupObserver: Send + Sync {
2254 fn on_interaction_stream_cleanup(&self, corr_id: PeerCorrelationId);
2260}
2261
2262#[cfg(test)]
2263#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
2264mod tests {
2265 use super::{
2266 DslRejectionKind, DslTransitionError, ExternalToolSurfaceEffect,
2267 ExternalToolSurfaceFailureCause, ExternalToolSurfaceInput, PeerConversationProjection,
2268 PeerResponseProgressProjectionPhase, PeerResponseTerminalCorrelationId,
2269 PeerResponseTerminalDisplayIdentity, PeerResponseTerminalFact,
2270 PeerResponseTerminalFactError, PeerResponseTerminalProjectionStatus,
2271 PeerResponseTerminalRenderPayload, PeerResponseTerminalRouteIdentity,
2272 PeerResponseTerminalSource, PeerResponseTerminalTransportIdentity,
2273 peer_response_terminal_context_key,
2274 };
2275 use crate::tool_scope::{ExternalToolSurfaceDeltaOperation, ExternalToolSurfaceDeltaPhase};
2276
2277 #[test]
2278 fn recovered_state_rejection_is_not_guard_noop() {
2279 let err =
2280 DslTransitionError::recovered_state_invariant_rejected("recover", "bad invariant");
2281 assert_eq!(err.kind, DslRejectionKind::RecoveredStateInvariantRejected);
2282 assert!(!err.is_guard_rejected());
2283 }
2284
2285 #[test]
2286 fn external_tool_surface_pending_failure_cause_projects_external_code() {
2287 let input = ExternalToolSurfaceInput::MarkPendingFailed {
2288 surface_id: "alpha".to_owned(),
2289 pending_task_sequence: 7,
2290 staged_intent_sequence: 11,
2291 cause: ExternalToolSurfaceFailureCause::PendingFailed,
2292 };
2293
2294 let ExternalToolSurfaceInput::MarkPendingFailed { cause, .. } = input else {
2295 panic!("constructed MarkPendingFailed input");
2296 };
2297 assert_eq!(cause, ExternalToolSurfaceFailureCause::PendingFailed);
2298 assert_eq!(cause.as_str(), "pending_failed");
2299 assert_eq!(
2300 serde_json::to_value(cause).expect("serialize failure cause"),
2301 serde_json::json!("pending_failed")
2302 );
2303
2304 let effect = ExternalToolSurfaceEffect::EmitExternalToolDelta {
2305 surface_id: "alpha".to_owned(),
2306 operation: ExternalToolSurfaceDeltaOperation::Add,
2307 phase: ExternalToolSurfaceDeltaPhase::Failed,
2308 cause: Some(cause),
2309 };
2310 assert!(matches!(
2311 effect,
2312 ExternalToolSurfaceEffect::EmitExternalToolDelta {
2313 cause: Some(ExternalToolSurfaceFailureCause::PendingFailed),
2314 ..
2315 }
2316 ));
2317 }
2318
2319 #[test]
2320 fn peer_terminal_projection_owns_prompt_and_context_key() {
2321 let route_id = "550e8400-e29b-41d4-a716-446655440000";
2322 let route_identity =
2323 PeerResponseTerminalRouteIdentity::parse(route_id).expect("route identity");
2324 let correlation_id =
2325 PeerResponseTerminalCorrelationId::parse("018f6f79-7a82-7c4e-a552-a3b86f9630f1")
2326 .expect("correlation id");
2327 let projection = PeerConversationProjection::ResponseTerminal {
2328 fact: PeerResponseTerminalFact::new(
2329 PeerResponseTerminalSource::new(
2330 Some(
2331 PeerResponseTerminalTransportIdentity::parse("transport-runtime-1")
2332 .expect("transport identity"),
2333 ),
2334 route_identity,
2335 PeerResponseTerminalDisplayIdentity::parse("Analyst")
2336 .expect("display identity"),
2337 ),
2338 correlation_id,
2339 PeerResponseTerminalProjectionStatus::Completed,
2340 PeerResponseTerminalRenderPayload::new(Some(serde_json::json!({
2341 "request_intent": "checksum_token",
2342 "request_subject": "alpha beta gamma",
2343 "token": "birch seventeen"
2344 }))),
2345 ),
2346 };
2347
2348 assert_eq!(
2349 projection.context_key().as_deref(),
2350 Some(
2351 "peer_response_terminal:550e8400-e29b-41d4-a716-446655440000:018f6f79-7a82-7c4e-a552-a3b86f9630f1"
2352 )
2353 );
2354 assert_eq!(
2355 projection.prompt_text(),
2356 "Peer terminal response from Analyst. Request ID: 018f6f79-7a82-7c4e-a552-a3b86f9630f1. Status: completed. Result: {\n \"request_intent\": \"checksum_token\",\n \"request_subject\": \"alpha beta gamma\",\n \"token\": \"birch seventeen\"\n}."
2357 );
2358 }
2359
2360 #[test]
2361 fn peer_terminal_fact_is_structural_projection_only() {
2362 let route_id = "550e8400-e29b-41d4-a716-446655440000";
2363 let route_identity =
2364 PeerResponseTerminalRouteIdentity::parse(route_id).expect("route identity");
2365 let correlation_id =
2366 PeerResponseTerminalCorrelationId::parse("018f6f79-7a82-7c4e-a552-a3b86f9630f1")
2367 .expect("correlation id");
2368
2369 let fact = PeerResponseTerminalFact::new(
2370 PeerResponseTerminalSource::new(
2371 None,
2372 route_identity,
2373 PeerResponseTerminalDisplayIdentity::parse("Analyst").expect("display identity"),
2374 ),
2375 correlation_id,
2376 PeerResponseTerminalProjectionStatus::Cancelled,
2377 PeerResponseTerminalRenderPayload::new(None),
2378 );
2379
2380 assert_eq!(
2381 fact.status,
2382 PeerResponseTerminalProjectionStatus::Cancelled,
2383 "status support is decided by generated admission authority, not fact construction"
2384 );
2385 }
2386
2387 #[test]
2388 fn peer_progress_projection_formats_phase_from_shared_seam() {
2389 let projection = PeerConversationProjection::ResponseProgress {
2390 peer_id: "operator-rt".into(),
2391 request_id: "req-789".into(),
2392 phase: PeerResponseProgressProjectionPhase::PartialResult,
2393 payload: Some(serde_json::json!({ "chunk": "alpha" })),
2394 };
2395
2396 assert_eq!(projection.context_key(), None);
2397 assert_eq!(
2398 projection.prompt_text(),
2399 "Peer response progress from operator-rt. Request ID: req-789. Phase: partial_result. Payload: {\n \"chunk\": \"alpha\"\n}."
2400 );
2401 }
2402
2403 #[test]
2404 fn peer_terminal_context_key_helper_stays_canonical() {
2405 let route_id = "550e8400-e29b-41d4-a716-446655440000";
2406 let route_identity =
2407 PeerResponseTerminalRouteIdentity::parse(route_id).expect("route identity");
2408 let correlation_id =
2409 PeerResponseTerminalCorrelationId::parse("018f6f79-7a82-7c4e-a552-a3b86f9630f1")
2410 .expect("correlation id");
2411 assert_eq!(
2412 peer_response_terminal_context_key(&route_identity, correlation_id),
2413 "peer_response_terminal:550e8400-e29b-41d4-a716-446655440000:018f6f79-7a82-7c4e-a552-a3b86f9630f1"
2414 );
2415 }
2416
2417 #[test]
2418 fn peer_terminal_route_identity_rejects_display_name_alias() {
2419 assert!(matches!(
2420 PeerResponseTerminalRouteIdentity::parse("analyst-rt"),
2421 Err(PeerResponseTerminalFactError::InvalidRouteIdentity)
2422 ));
2423 }
2424
2425 #[test]
2426 fn peer_terminal_fact_round_trips_through_serde() {
2427 let fact = PeerResponseTerminalFact::new(
2431 PeerResponseTerminalSource::parse(
2432 Some("inproc://analyst"),
2433 "550e8400-e29b-41d4-a716-446655440000",
2434 "analyst-rt",
2435 )
2436 .expect("source"),
2437 PeerResponseTerminalCorrelationId::parse("018f6f79-7a82-7c4e-a552-a3b86f9630f1")
2438 .expect("correlation id"),
2439 PeerResponseTerminalProjectionStatus::Completed,
2440 PeerResponseTerminalRenderPayload::new(Some(serde_json::json!({
2441 "request_intent": "checksum_token",
2442 "token": "birch seventeen",
2443 }))),
2444 );
2445
2446 let json = serde_json::to_string(&fact).expect("serialize fact");
2447 let decoded: PeerResponseTerminalFact =
2448 serde_json::from_str(&json).expect("deserialize fact");
2449 assert_eq!(decoded, fact);
2450 assert_eq!(
2451 decoded.context_key(),
2452 "peer_response_terminal:550e8400-e29b-41d4-a716-446655440000:018f6f79-7a82-7c4e-a552-a3b86f9630f1"
2453 );
2454 assert_eq!(
2455 decoded
2456 .render_payload_value()
2457 .and_then(|payload| payload.get("token"))
2458 .and_then(|token| token.as_str()),
2459 Some("birch seventeen")
2460 );
2461 }
2462}