1use std::collections::HashMap;
13use std::future::Future;
14
15use meerkat_core::lifecycle::InputId;
16#[cfg(test)]
17use meerkat_core::lifecycle::RunId;
18use meerkat_core::lifecycle::core_executor::CoreApplyTerminal;
19use meerkat_core::types::{RunResult, SessionId};
20use meerkat_core::{TurnErrorMetadata, TurnTerminalCauseKind, TurnTerminalOutcome};
21use serde_json::Value;
22
23use crate::meerkat_machine::driver::{
24 RuntimeCompletionResultAttempt, RuntimeCompletionResultAuthority,
25 RuntimeCompletionResultRealized,
26};
27use crate::meerkat_machine::dsl::RuntimeCompletionResultClass;
28use crate::tokio::sync::oneshot;
29
30#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
36pub enum CompletionWaitError {
37 #[error("completion channel closed without an authorized result")]
38 ChannelClosed,
39 #[error("{0}")]
40 AuthorityUnavailable(String),
41}
42
43impl CompletionWaitError {
44 pub fn wait_failure_observation(
45 &self,
46 ) -> crate::meerkat_machine::dsl::RuntimeCompletionWaitFailureObservation {
47 match self {
48 Self::ChannelClosed => {
49 crate::meerkat_machine::dsl::RuntimeCompletionWaitFailureObservation::ChannelClosed
50 }
51 Self::AuthorityUnavailable(_) => {
52 crate::meerkat_machine::dsl::RuntimeCompletionWaitFailureObservation::AuthorityUnavailable
53 }
54 }
55 }
56}
57
58#[derive(Debug)]
60pub enum CompletionOutcome {
61 Completed(Box<RunResult>),
63 CompletedWithoutResult,
65 CallbackPending { tool_name: String, args: Value },
68 Cancelled,
70 Abandoned {
74 reason: String,
75 error: TurnErrorMetadata,
76 },
77 AbandonedWithError {
79 reason: String,
80 error: TurnErrorMetadata,
81 },
82 CompletedWithFinalizationFailure { error: TurnErrorMetadata },
88 RuntimeTerminated {
91 reason: String,
92 error: TurnErrorMetadata,
93 },
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct CompletionCleanupObservation {
102 owner_session_id: SessionId,
103 owner_agent_runtime_id: Option<crate::meerkat_machine::dsl::AgentRuntimeId>,
104 owner_fence_token: Option<crate::meerkat_machine::dsl::FenceToken>,
105 owner_runtime_generation: Option<crate::meerkat_machine::dsl::Generation>,
106 owner_runtime_epoch_id: Option<crate::meerkat_machine::dsl::RuntimeEpochId>,
107 observed_outcome: crate::meerkat_machine::dsl::RuntimeCompletionObservedOutcome,
108}
109
110impl CompletionCleanupObservation {
111 fn from_realized_result(realized: RuntimeCompletionResultRealized) -> Self {
112 Self {
113 owner_session_id: realized.session_id().clone(),
114 owner_agent_runtime_id: realized.agent_runtime_id().cloned(),
115 owner_fence_token: realized.fence_token(),
116 owner_runtime_generation: realized.runtime_generation(),
117 owner_runtime_epoch_id: realized.runtime_epoch_id().cloned(),
118 observed_outcome: realized.cleanup_observation(),
119 }
120 }
121
122 #[must_use]
131 pub fn proves_runtime_termination_for(&self, session_id: &SessionId) -> bool {
132 self.owner_session_id == *session_id
133 && self.observed_outcome
134 == crate::meerkat_machine::dsl::RuntimeCompletionObservedOutcome::RuntimeTerminated
135 }
136
137 pub(crate) fn owner_session_id(&self) -> &SessionId {
138 &self.owner_session_id
139 }
140
141 pub(crate) fn owner_agent_runtime_id(
142 &self,
143 ) -> Option<&crate::meerkat_machine::dsl::AgentRuntimeId> {
144 self.owner_agent_runtime_id.as_ref()
145 }
146
147 pub(crate) fn owner_fence_token(&self) -> Option<crate::meerkat_machine::dsl::FenceToken> {
148 self.owner_fence_token
149 }
150
151 pub(crate) fn owner_runtime_generation(
152 &self,
153 ) -> Option<crate::meerkat_machine::dsl::Generation> {
154 self.owner_runtime_generation
155 }
156
157 pub(crate) fn owner_runtime_epoch_id(
158 &self,
159 ) -> Option<&crate::meerkat_machine::dsl::RuntimeEpochId> {
160 self.owner_runtime_epoch_id.as_ref()
161 }
162
163 pub(crate) fn observed_outcome(
164 &self,
165 ) -> crate::meerkat_machine::dsl::RuntimeCompletionObservedOutcome {
166 self.observed_outcome
167 }
168}
169
170#[derive(Debug)]
173struct CompletionDelivery {
174 outcome: CompletionOutcome,
175 cleanup_observation: CompletionCleanupObservation,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct CompletionWaiterEntrySnapshot {
184 pub input_id: InputId,
185 pub waiter_count: usize,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Default)]
193pub struct CompletionRegistrySnapshot {
194 pub input_count: usize,
195 pub waiter_count: usize,
196 pub waiting_inputs: Vec<CompletionWaiterEntrySnapshot>,
197}
198
199#[derive(Debug)]
201pub struct CompletionHandle {
202 rx: oneshot::Receiver<Result<CompletionDelivery, CompletionWaitError>>,
203}
204
205impl CompletionHandle {
206 async fn try_wait_delivery(self) -> Result<CompletionDelivery, CompletionWaitError> {
207 self.rx
208 .await
209 .unwrap_or(Err(CompletionWaitError::ChannelClosed))
210 }
211
212 pub async fn try_wait(self) -> Result<CompletionOutcome, CompletionWaitError> {
214 self.try_wait_delivery()
215 .await
216 .map(|delivery| delivery.outcome)
217 }
218
219 pub async fn try_wait_with_cleanup_observation(
222 self,
223 ) -> Result<(CompletionOutcome, CompletionCleanupObservation), CompletionWaitError> {
224 let delivery = self.try_wait_delivery().await?;
225 Ok((delivery.outcome, delivery.cleanup_observation))
226 }
227
228 pub async fn wait(self) -> Result<CompletionOutcome, CompletionWaitError> {
230 self.try_wait().await
231 }
232
233 #[cfg(test)]
235 pub(crate) async fn wait_authorized(self) -> CompletionOutcome {
236 self.wait()
237 .await
238 .expect("completion waiter closed without an authorized result")
239 }
240
241 pub fn with_cleanup<F, Fut>(self, cleanup: F) -> Self
245 where
246 F: FnOnce() -> Fut + Send + 'static,
247 Fut: Future<Output = ()> + Send + 'static,
248 {
249 let (tx, rx) = oneshot::channel();
250 crate::tokio::spawn(async move {
251 let outcome = self.try_wait_delivery().await;
252 cleanup().await;
253 let _ = tx.send(outcome);
254 });
255 Self { rx }
256 }
257
258 pub fn with_outcome_cleanup<F, Fut>(self, cleanup: F) -> Self
263 where
264 F: FnOnce(CompletionCleanupObservation) -> Fut + Send + 'static,
265 Fut: Future<Output = ()> + Send + 'static,
266 {
267 let (tx, rx) = oneshot::channel();
268 crate::tokio::spawn(async move {
269 let outcome = match self.try_wait_delivery().await {
270 Ok(delivery) => {
271 cleanup(delivery.cleanup_observation.clone()).await;
272 Ok(delivery)
273 }
274 Err(error) => Err(error),
275 };
276 let _ = tx.send(outcome);
277 });
278 Self { rx }
279 }
280
281 pub fn with_completion_cleanup<F, Fut>(self, cleanup: F) -> Self
284 where
285 F: FnOnce(Result<CompletionCleanupObservation, CompletionWaitError>) -> Fut
286 + Send
287 + 'static,
288 Fut: Future<Output = ()> + Send + 'static,
289 {
290 let (tx, rx) = oneshot::channel();
291 crate::tokio::spawn(async move {
292 let outcome = self.try_wait_delivery().await;
293 match &outcome {
294 Ok(delivery) => cleanup(Ok(delivery.cleanup_observation.clone())).await,
295 Err(error) => cleanup(Err(error.clone())).await,
296 }
297 let _ = tx.send(outcome);
298 });
299 Self { rx }
300 }
301
302 pub fn with_resultful_completion_cleanup<F, Fut>(self, cleanup: F) -> Self
306 where
307 F: FnOnce(Result<CompletionCleanupObservation, CompletionWaitError>) -> Fut
308 + Send
309 + 'static,
310 Fut: Future<Output = Result<(), CompletionWaitError>> + Send + 'static,
311 {
312 let (tx, rx) = oneshot::channel();
313 crate::tokio::spawn(async move {
314 let outcome = self.try_wait_delivery().await;
315 let cleanup_input = match &outcome {
316 Ok(delivery) => Ok(delivery.cleanup_observation.clone()),
317 Err(error) => Err(error.clone()),
318 };
319 let cleanup_result = cleanup(cleanup_input).await;
320 let gated = match (outcome, cleanup_result) {
321 (Ok(delivery), Ok(())) => Ok(delivery),
322 (Ok(_), Err(cleanup_error)) => Err(cleanup_error),
323 (Err(primary_error), Ok(())) => Err(primary_error),
324 (Err(primary_error), Err(cleanup_error)) => {
325 Err(CompletionWaitError::AuthorityUnavailable(format!(
326 "{primary_error}; additionally failed required completion cleanup: {cleanup_error}"
327 )))
328 }
329 };
330 let _ = tx.send(gated);
331 });
332 Self { rx }
333 }
334
335 #[cfg(test)]
336 fn already_resolved_internal(
337 outcome: CompletionOutcome,
338 realized: RuntimeCompletionResultRealized,
339 ) -> Self {
340 let (tx, rx) = oneshot::channel();
341 let _ = tx.send(Ok(CompletionDelivery {
342 outcome,
343 cleanup_observation: CompletionCleanupObservation::from_realized_result(realized),
344 }));
345 Self { rx }
346 }
347
348 #[cfg(test)]
349 pub(crate) fn already_resolved_with_generated_class(
350 outcome: CompletionOutcome,
351 expected_class: crate::meerkat_machine::dsl::RuntimeCompletionResultClass,
352 terminal: crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation,
353 finalization: crate::meerkat_machine::dsl::RuntimeCompletionFinalizationObservation,
354 ) -> Result<Self, crate::RuntimeDriverError> {
355 let run_id = if terminal
356 == crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation::RuntimeTerminated
357 {
358 None
359 } else {
360 Some(RunId::new())
361 };
362 let authority =
363 crate::meerkat_machine::driver::machine_resolve_pre_resolved_runtime_completion_result(
364 run_id.as_ref(),
365 terminal,
366 finalization,
367 )?;
368 let attempt = authority.begin_surface_resolution();
369 if !attempt.allows(expected_class) {
370 let generated_class = attempt.class();
371 attempt.fail();
372 return Err(crate::RuntimeDriverError::Internal(format!(
373 "generated runtime completion authority returned {generated_class:?}, expected {expected_class:?}",
374 )));
375 }
376 Ok(Self::already_resolved_internal(outcome, attempt.realize()))
377 }
378
379 #[cfg(test)]
380 pub(crate) fn already_completed_without_result() -> Result<Self, crate::RuntimeDriverError> {
381 Self::already_resolved_with_generated_class(
382 CompletionOutcome::CompletedWithoutResult,
383 crate::meerkat_machine::dsl::RuntimeCompletionResultClass::CompletedWithoutResult,
384 crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation::NoResult,
385 crate::meerkat_machine::dsl::RuntimeCompletionFinalizationObservation::Succeeded,
386 )
387 }
388
389 #[cfg(test)]
390 pub(crate) fn already_runtime_apply_failed(
391 reason: String,
392 error: TurnErrorMetadata,
393 ) -> Result<Self, crate::RuntimeDriverError> {
394 Self::already_resolved_with_generated_class(
395 CompletionOutcome::AbandonedWithError { reason, error },
396 crate::meerkat_machine::dsl::RuntimeCompletionResultClass::AbandonedWithError,
397 crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation::NoResult,
398 crate::meerkat_machine::dsl::RuntimeCompletionFinalizationObservation::Failed,
399 )
400 }
401
402 #[cfg(test)]
403 pub(crate) fn already_runtime_terminated(
404 reason: String,
405 ) -> Result<Self, crate::RuntimeDriverError> {
406 Self::already_resolved_with_generated_class(
407 CompletionOutcome::runtime_terminated(&reason),
408 crate::meerkat_machine::dsl::RuntimeCompletionResultClass::RuntimeTerminated,
409 crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation::RuntimeTerminated,
410 crate::meerkat_machine::dsl::RuntimeCompletionFinalizationObservation::Succeeded,
411 )
412 }
413
414 #[cfg(test)]
415 pub(crate) fn already_callback_pending(
416 tool_name: String,
417 args: Value,
418 ) -> Result<Self, crate::RuntimeDriverError> {
419 Self::already_resolved_with_generated_class(
420 CompletionOutcome::CallbackPending { tool_name, args },
421 crate::meerkat_machine::dsl::RuntimeCompletionResultClass::CallbackPending,
422 crate::meerkat_machine::dsl::RuntimeCompletionTerminalObservation::CallbackPending,
423 crate::meerkat_machine::dsl::RuntimeCompletionFinalizationObservation::Succeeded,
424 )
425 }
426}
427
428impl CompletionOutcome {
429 fn runtime_terminated(reason: &str) -> Self {
433 Self::RuntimeTerminated {
434 reason: reason.to_string(),
435 error: TurnErrorMetadata::terminal(
436 TurnTerminalCauseKind::FatalFailure,
437 TurnTerminalOutcome::Failed,
438 reason,
439 ),
440 }
441 }
442
443 pub fn abandoned_reason(&self) -> Option<&str> {
444 match self {
445 Self::Abandoned { reason, .. } | Self::AbandonedWithError { reason, .. } => {
446 Some(reason)
447 }
448 _ => None,
449 }
450 }
451
452 pub fn error_metadata(&self) -> Option<&TurnErrorMetadata> {
453 match self {
454 Self::Abandoned { error, .. }
455 | Self::AbandonedWithError { error, .. }
456 | Self::CompletedWithFinalizationFailure { error, .. }
457 | Self::RuntimeTerminated { error, .. } => Some(error),
458 _ => None,
459 }
460 }
461}
462
463#[derive(Default)]
468pub(crate) struct CompletionRegistry {
469 waiters:
470 HashMap<InputId, Vec<oneshot::Sender<Result<CompletionDelivery, CompletionWaitError>>>>,
471}
472
473impl CompletionRegistry {
474 pub(crate) fn new() -> Self {
475 Self::default()
476 }
477
478 fn take_waiters(
479 &mut self,
480 input_id: &InputId,
481 ) -> Option<Vec<oneshot::Sender<Result<CompletionDelivery, CompletionWaitError>>>> {
482 self.waiters.remove(input_id)
483 }
484
485 fn send_outcome(
486 senders: Vec<oneshot::Sender<Result<CompletionDelivery, CompletionWaitError>>>,
487 outcome: CompletionOutcome,
488 cleanup_observation: CompletionCleanupObservation,
489 ) {
490 for tx in senders {
491 let outcome = match &outcome {
492 CompletionOutcome::Completed(result) => {
493 CompletionOutcome::Completed(Box::new(result.as_ref().clone()))
494 }
495 CompletionOutcome::CompletedWithoutResult => {
496 CompletionOutcome::CompletedWithoutResult
497 }
498 CompletionOutcome::CallbackPending { tool_name, args } => {
499 CompletionOutcome::CallbackPending {
500 tool_name: tool_name.clone(),
501 args: args.clone(),
502 }
503 }
504 CompletionOutcome::Cancelled => CompletionOutcome::Cancelled,
505 CompletionOutcome::Abandoned { reason, error } => CompletionOutcome::Abandoned {
506 reason: reason.clone(),
507 error: error.clone(),
508 },
509 CompletionOutcome::AbandonedWithError { reason, error } => {
510 CompletionOutcome::AbandonedWithError {
511 reason: reason.clone(),
512 error: error.clone(),
513 }
514 }
515 CompletionOutcome::CompletedWithFinalizationFailure { error } => {
516 CompletionOutcome::CompletedWithFinalizationFailure {
517 error: error.clone(),
518 }
519 }
520 CompletionOutcome::RuntimeTerminated { reason, error } => {
521 CompletionOutcome::RuntimeTerminated {
522 reason: reason.clone(),
523 error: error.clone(),
524 }
525 }
526 };
527 let _ = tx.send(Ok(CompletionDelivery {
528 outcome,
529 cleanup_observation: cleanup_observation.clone(),
530 }));
531 }
532 }
533
534 fn send_error(
535 senders: Vec<oneshot::Sender<Result<CompletionDelivery, CompletionWaitError>>>,
536 error: CompletionWaitError,
537 ) {
538 for tx in senders {
539 let _ = tx.send(Err(error.clone()));
540 }
541 }
542
543 fn authority_mismatch_error(
544 authority: &RuntimeCompletionResultAttempt,
545 expected: RuntimeCompletionResultClass,
546 ) -> CompletionWaitError {
547 CompletionWaitError::AuthorityUnavailable(format!(
548 "generated runtime completion authority returned {:?}, expected {expected:?}",
549 authority.class()
550 ))
551 }
552
553 fn fail_input_authority_mismatch(
554 &mut self,
555 input_id: &InputId,
556 authority: RuntimeCompletionResultAttempt,
557 expected: RuntimeCompletionResultClass,
558 ) {
559 let error = Self::authority_mismatch_error(&authority, expected);
560 authority.fail();
561 if let Some(senders) = self.take_waiters(input_id) {
562 Self::send_error(senders, error);
563 }
564 }
565
566 #[cfg(test)]
567 fn fail_inputs_authority_mismatch<I>(
568 &mut self,
569 input_ids: I,
570 authority: RuntimeCompletionResultAttempt,
571 expected: RuntimeCompletionResultClass,
572 ) where
573 I: IntoIterator<Item = InputId>,
574 {
575 let error = Self::authority_mismatch_error(&authority, expected);
576 authority.fail();
577 self.fail_inputs(input_ids, error);
578 }
579
580 fn fail_inputs_authority_unavailable<I>(
581 &mut self,
582 input_ids: I,
583 authority: RuntimeCompletionResultAttempt,
584 reason: impl Into<String>,
585 ) where
586 I: IntoIterator<Item = InputId>,
587 {
588 authority.fail();
589 self.fail_inputs(
590 input_ids,
591 CompletionWaitError::AuthorityUnavailable(reason.into()),
592 );
593 }
594
595 fn cleanup_from_realized_attempt(
596 authority: RuntimeCompletionResultAttempt,
597 ) -> CompletionCleanupObservation {
598 CompletionCleanupObservation::from_realized_result(authority.realize())
599 }
600
601 pub(crate) fn register(&mut self, input_id: InputId) -> CompletionHandle {
606 let (tx, rx) = oneshot::channel();
607 self.waiters.entry(input_id).or_default().push(tx);
608 CompletionHandle { rx }
609 }
610
611 fn resolve_completed(
613 &mut self,
614 input_id: &InputId,
615 result: RunResult,
616 cleanup_observation: CompletionCleanupObservation,
617 ) {
618 if let Some(senders) = self.take_waiters(input_id) {
619 Self::send_outcome(
620 senders,
621 CompletionOutcome::Completed(Box::new(result)),
622 cleanup_observation,
623 );
624 }
625 }
626
627 #[cfg(test)]
628 pub(crate) fn resolve_completed_authorized(
629 &mut self,
630 input_id: &InputId,
631 result: RunResult,
632 authority: RuntimeCompletionResultAuthority,
633 ) {
634 let expected = RuntimeCompletionResultClass::Completed;
635 let attempt = authority.begin_surface_resolution();
636 if !attempt.allows(expected) {
637 self.fail_input_authority_mismatch(input_id, attempt, expected);
638 return;
639 }
640 self.resolve_completed(
641 input_id,
642 result,
643 Self::cleanup_from_realized_attempt(attempt),
644 );
645 }
646
647 pub(crate) fn resolve_runtime_completion_authorized<I>(
648 &mut self,
649 input_ids: I,
650 terminal: Option<&CoreApplyTerminal>,
651 authority: RuntimeCompletionResultAuthority,
652 finalization_error: Option<TurnErrorMetadata>,
653 ) where
654 I: IntoIterator<Item = InputId>,
655 {
656 let input_ids: Vec<InputId> = input_ids.into_iter().collect();
657 let attempt = authority.begin_surface_resolution();
658 match attempt.class() {
659 RuntimeCompletionResultClass::Completed => {
660 let Some(CoreApplyTerminal::RunResult(result)) = terminal else {
661 self.fail_inputs_authority_unavailable(
662 input_ids,
663 attempt,
664 "runtime completion authority resolved Completed without result payload",
665 );
666 return;
667 };
668 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
669 for input_id in input_ids {
670 self.resolve_completed(
671 &input_id,
672 result.as_ref().clone(),
673 cleanup_observation.clone(),
674 );
675 }
676 }
677 RuntimeCompletionResultClass::CompletedWithoutResult => {
678 if !matches!(terminal, Some(CoreApplyTerminal::NoPendingBoundary) | None) {
679 self.fail_inputs_authority_unavailable(
680 input_ids,
681 attempt,
682 "runtime completion authority resolved CompletedWithoutResult with terminal payload",
683 );
684 return;
685 }
686 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
687 for input_id in input_ids {
688 self.resolve_without_result(&input_id, cleanup_observation.clone());
689 }
690 }
691 RuntimeCompletionResultClass::CallbackPending => {
692 let Some(CoreApplyTerminal::CallbackPending { tool_name, args }) = terminal else {
693 self.fail_inputs_authority_unavailable(
694 input_ids,
695 attempt,
696 "runtime completion authority resolved CallbackPending without callback payload",
697 );
698 return;
699 };
700 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
701 for input_id in input_ids {
702 self.resolve_callback_pending(
703 &input_id,
704 tool_name.clone(),
705 args.clone(),
706 cleanup_observation.clone(),
707 );
708 }
709 }
710 RuntimeCompletionResultClass::Cancelled => {
711 if terminal.is_some() || finalization_error.is_some() {
712 self.fail_inputs_authority_unavailable(
713 input_ids,
714 attempt,
715 "runtime completion authority resolved Cancelled with payload",
716 );
717 return;
718 }
719 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
720 for input_id in input_ids {
721 self.resolve_cancelled(&input_id, cleanup_observation.clone());
722 }
723 }
724 RuntimeCompletionResultClass::AbandonedWithError => {
725 if matches!(terminal, Some(CoreApplyTerminal::RunResult(_))) {
726 self.fail_inputs_authority_unavailable(
727 input_ids,
728 attempt,
729 "runtime completion authority resolved AbandonedWithError with result payload",
730 );
731 return;
732 }
733 let Some(error) = finalization_error else {
734 self.fail_inputs_authority_unavailable(
735 input_ids,
736 attempt,
737 "runtime completion authority resolved AbandonedWithError without typed error",
738 );
739 return;
740 };
741 let reason = error
742 .detail
743 .clone()
744 .unwrap_or_else(|| "runtime finalization failed".to_string());
745 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
746 for input_id in input_ids {
747 self.resolve_abandoned_with_error(
748 &input_id,
749 reason.clone(),
750 error.clone(),
751 cleanup_observation.clone(),
752 );
753 }
754 }
755 RuntimeCompletionResultClass::CompletedWithFinalizationFailure => {
756 let Some(CoreApplyTerminal::RunResult(_result)) = terminal else {
757 self.fail_inputs_authority_unavailable(
758 input_ids,
759 attempt,
760 "runtime completion authority resolved CompletedWithFinalizationFailure without result payload",
761 );
762 return;
763 };
764 let Some(error) = finalization_error else {
765 self.fail_inputs_authority_unavailable(
766 input_ids,
767 attempt,
768 "runtime completion authority resolved finalization failure without typed error",
769 );
770 return;
771 };
772 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
773 for input_id in input_ids {
774 self.resolve_completed_with_finalization_failure(
775 &input_id,
776 error.clone(),
777 cleanup_observation.clone(),
778 );
779 }
780 }
781 RuntimeCompletionResultClass::RuntimeTerminated => {
782 if terminal.is_some() || finalization_error.is_some() {
783 self.fail_inputs_authority_unavailable(
784 input_ids,
785 attempt,
786 "runtime completion authority resolved RuntimeTerminated with payload",
787 );
788 return;
789 }
790 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
791 for input_id in input_ids {
792 if let Some(senders) = self.take_waiters(&input_id) {
793 Self::send_outcome(
794 senders,
795 CompletionOutcome::runtime_terminated("runtime terminated"),
796 cleanup_observation.clone(),
797 );
798 }
799 }
800 }
801 }
802 }
803
804 fn resolve_without_result(
806 &mut self,
807 input_id: &InputId,
808 cleanup_observation: CompletionCleanupObservation,
809 ) {
810 if let Some(senders) = self.take_waiters(input_id) {
811 Self::send_outcome(
812 senders,
813 CompletionOutcome::CompletedWithoutResult,
814 cleanup_observation,
815 );
816 }
817 }
818
819 pub(crate) fn resolve_without_result_authorized(
820 &mut self,
821 input_id: &InputId,
822 authority: RuntimeCompletionResultAuthority,
823 ) {
824 let expected = RuntimeCompletionResultClass::CompletedWithoutResult;
825 let attempt = authority.begin_surface_resolution();
826 if !attempt.allows(expected) {
827 self.fail_input_authority_mismatch(input_id, attempt, expected);
828 return;
829 }
830 self.resolve_without_result(input_id, Self::cleanup_from_realized_attempt(attempt));
831 }
832
833 fn resolve_callback_pending(
835 &mut self,
836 input_id: &InputId,
837 tool_name: String,
838 args: Value,
839 cleanup_observation: CompletionCleanupObservation,
840 ) {
841 if let Some(senders) = self.take_waiters(input_id) {
842 Self::send_outcome(
843 senders,
844 CompletionOutcome::CallbackPending { tool_name, args },
845 cleanup_observation,
846 );
847 }
848 }
849
850 #[cfg(test)]
851 pub(crate) fn resolve_callback_pending_authorized(
852 &mut self,
853 input_id: &InputId,
854 tool_name: String,
855 args: Value,
856 authority: RuntimeCompletionResultAuthority,
857 ) {
858 let expected = RuntimeCompletionResultClass::CallbackPending;
859 let attempt = authority.begin_surface_resolution();
860 if !attempt.allows(expected) {
861 self.fail_input_authority_mismatch(input_id, attempt, expected);
862 return;
863 }
864 self.resolve_callback_pending(
865 input_id,
866 tool_name,
867 args,
868 Self::cleanup_from_realized_attempt(attempt),
869 );
870 }
871
872 fn resolve_cancelled(
874 &mut self,
875 input_id: &InputId,
876 cleanup_observation: CompletionCleanupObservation,
877 ) {
878 if let Some(senders) = self.take_waiters(input_id) {
879 Self::send_outcome(senders, CompletionOutcome::Cancelled, cleanup_observation);
880 }
881 }
882
883 #[cfg(test)]
884 pub(crate) fn resolve_cancelled_authorized(
885 &mut self,
886 input_id: &InputId,
887 authority: RuntimeCompletionResultAuthority,
888 ) {
889 let expected = RuntimeCompletionResultClass::Cancelled;
890 let attempt = authority.begin_surface_resolution();
891 if !attempt.allows(expected) {
892 self.fail_input_authority_mismatch(input_id, attempt, expected);
893 return;
894 }
895 self.resolve_cancelled(input_id, Self::cleanup_from_realized_attempt(attempt));
896 }
897
898 fn resolve_abandoned_with_error(
900 &mut self,
901 input_id: &InputId,
902 reason: String,
903 error: TurnErrorMetadata,
904 cleanup_observation: CompletionCleanupObservation,
905 ) {
906 if let Some(senders) = self.take_waiters(input_id) {
907 Self::send_outcome(
908 senders,
909 CompletionOutcome::AbandonedWithError { reason, error },
910 cleanup_observation,
911 );
912 }
913 }
914
915 #[cfg(test)]
916 pub(crate) fn resolve_abandoned_with_error_authorized(
917 &mut self,
918 input_id: &InputId,
919 reason: String,
920 error: TurnErrorMetadata,
921 authority: RuntimeCompletionResultAuthority,
922 ) {
923 let expected = RuntimeCompletionResultClass::AbandonedWithError;
924 let attempt = authority.begin_surface_resolution();
925 if !attempt.allows(expected) {
926 self.fail_input_authority_mismatch(input_id, attempt, expected);
927 return;
928 }
929 self.resolve_abandoned_with_error(
930 input_id,
931 reason,
932 error,
933 Self::cleanup_from_realized_attempt(attempt),
934 );
935 }
936
937 fn resolve_completed_with_finalization_failure(
940 &mut self,
941 input_id: &InputId,
942 error: TurnErrorMetadata,
943 cleanup_observation: CompletionCleanupObservation,
944 ) {
945 if let Some(senders) = self.take_waiters(input_id) {
946 Self::send_outcome(
947 senders,
948 CompletionOutcome::CompletedWithFinalizationFailure { error },
949 cleanup_observation,
950 );
951 }
952 }
953
954 #[cfg(test)]
955 pub(crate) fn resolve_completed_with_finalization_failure_authorized(
956 &mut self,
957 input_id: &InputId,
958 error: TurnErrorMetadata,
959 authority: RuntimeCompletionResultAuthority,
960 ) {
961 let expected = RuntimeCompletionResultClass::CompletedWithFinalizationFailure;
962 let attempt = authority.begin_surface_resolution();
963 if !attempt.allows(expected) {
964 self.fail_input_authority_mismatch(input_id, attempt, expected);
965 return;
966 }
967 self.resolve_completed_with_finalization_failure(
968 input_id,
969 error,
970 Self::cleanup_from_realized_attempt(attempt),
971 );
972 }
973
974 pub(crate) fn resolve_all_runtime_terminated(
980 &mut self,
981 reason: &str,
982 authority: RuntimeCompletionResultAuthority,
983 ) {
984 let expected = RuntimeCompletionResultClass::RuntimeTerminated;
985 let attempt = authority.begin_surface_resolution();
986 if !attempt.allows(expected) {
987 let error = Self::authority_mismatch_error(&attempt, expected);
988 attempt.fail();
989 self.fail_all_waiters(error);
990 return;
991 }
992 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
993 for (_, senders) in self.waiters.drain() {
994 Self::send_outcome(
995 senders,
996 CompletionOutcome::runtime_terminated(reason),
997 cleanup_observation.clone(),
998 );
999 }
1000 }
1001
1002 #[cfg(test)]
1003 pub(crate) fn resolve_inputs_runtime_terminated<I>(
1004 &mut self,
1005 input_ids: I,
1006 reason: &str,
1007 authority: RuntimeCompletionResultAuthority,
1008 ) where
1009 I: IntoIterator<Item = InputId>,
1010 {
1011 let input_ids: Vec<InputId> = input_ids.into_iter().collect();
1012 let expected = RuntimeCompletionResultClass::RuntimeTerminated;
1013 let attempt = authority.begin_surface_resolution();
1014 if !attempt.allows(expected) {
1015 self.fail_inputs_authority_mismatch(input_ids, attempt, expected);
1016 return;
1017 }
1018 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
1019 for input_id in input_ids {
1020 if let Some(senders) = self.take_waiters(&input_id) {
1021 Self::send_outcome(
1022 senders,
1023 CompletionOutcome::runtime_terminated(reason),
1024 cleanup_observation.clone(),
1025 );
1026 }
1027 }
1028 }
1029
1030 pub(crate) fn fail_all_waiters(&mut self, error: CompletionWaitError) {
1031 for (_, senders) in self.waiters.drain() {
1032 Self::send_error(senders, error.clone());
1033 }
1034 }
1035
1036 pub(crate) fn fail_inputs<I>(&mut self, input_ids: I, error: CompletionWaitError)
1037 where
1038 I: IntoIterator<Item = InputId>,
1039 {
1040 for input_id in input_ids {
1041 if let Some(senders) = self.take_waiters(&input_id) {
1042 Self::send_error(senders, error.clone());
1043 }
1044 }
1045 }
1046
1047 pub(crate) fn resolve_not_pending_runtime_terminated<F>(
1050 &mut self,
1051 mut is_still_pending: F,
1052 reason: &str,
1053 authority: RuntimeCompletionResultAuthority,
1054 ) where
1055 F: FnMut(&InputId) -> bool,
1056 {
1057 let expected = RuntimeCompletionResultClass::RuntimeTerminated;
1058 let attempt = authority.begin_surface_resolution();
1059 if !attempt.allows(expected) {
1060 let error = Self::authority_mismatch_error(&attempt, expected);
1061 attempt.fail();
1062 self.waiters.retain(|input_id, senders| {
1063 if is_still_pending(input_id) {
1064 return true;
1065 }
1066
1067 Self::send_error(std::mem::take(senders), error.clone());
1068 false
1069 });
1070 return;
1071 }
1072
1073 let mut ready = Vec::new();
1074 self.waiters.retain(|input_id, senders| {
1075 if is_still_pending(input_id) {
1076 return true;
1077 }
1078
1079 ready.push(std::mem::take(senders));
1080 false
1081 });
1082 if ready.is_empty() {
1083 attempt.abandon();
1084 return;
1085 }
1086 let cleanup_observation = Self::cleanup_from_realized_attempt(attempt);
1087 for senders in ready {
1088 Self::send_outcome(
1089 senders,
1090 CompletionOutcome::runtime_terminated(reason),
1091 cleanup_observation.clone(),
1092 );
1093 }
1094 }
1095
1096 pub(crate) fn diagnostic_snapshot(&self) -> CompletionRegistrySnapshot {
1098 let mut waiting_inputs: Vec<_> = self
1099 .waiters
1100 .iter()
1101 .map(|(input_id, senders)| CompletionWaiterEntrySnapshot {
1102 input_id: input_id.clone(),
1103 waiter_count: senders.len(),
1104 })
1105 .collect();
1106 waiting_inputs
1107 .sort_by(|left, right| left.input_id.to_string().cmp(&right.input_id.to_string()));
1108
1109 CompletionRegistrySnapshot {
1110 input_count: waiting_inputs.len(),
1111 waiter_count: waiting_inputs.iter().map(|entry| entry.waiter_count).sum(),
1112 waiting_inputs,
1113 }
1114 }
1115
1116 #[cfg(test)]
1121 pub fn debug_has_waiters(&self) -> bool {
1122 !self.waiters.is_empty()
1123 }
1124
1125 #[cfg(test)]
1130 pub fn debug_waiter_count(&self) -> usize {
1131 self.waiters.values().map(Vec::len).sum()
1132 }
1133}
1134
1135#[cfg(test)]
1136#[allow(clippy::unwrap_used, clippy::panic)]
1137mod tests {
1138 use super::*;
1139 use crate::meerkat_machine::dsl::{
1140 RuntimeCompletionObservedOutcome, RuntimeCompletionResultClass,
1141 };
1142 use meerkat_core::types::{SessionId, Usage};
1143
1144 fn make_run_result() -> RunResult {
1145 RunResult {
1146 text: "hello".into(),
1147 session_id: SessionId::new(),
1148 usage: Usage::default(),
1149 turns: 1,
1150 tool_calls: 0,
1151 terminal_cause_kind: None,
1152 structured_output: None,
1153 extraction_error: None,
1154 schema_warnings: None,
1155 skill_diagnostics: None,
1156 }
1157 }
1158
1159 fn authority(
1160 result_class: RuntimeCompletionResultClass,
1161 cleanup_observation: RuntimeCompletionObservedOutcome,
1162 ) -> RuntimeCompletionResultAuthority {
1163 crate::meerkat_machine::driver::test_runtime_completion_authority(
1164 result_class,
1165 cleanup_observation,
1166 )
1167 }
1168
1169 #[tokio::test]
1170 async fn register_and_complete() {
1171 let mut registry = CompletionRegistry::new();
1172 let input_id = InputId::new();
1173 let handle = registry.register(input_id.clone());
1174
1175 assert!(registry.debug_has_waiters());
1176 assert_eq!(registry.debug_waiter_count(), 1);
1177
1178 let result = make_run_result();
1179 registry.resolve_completed_authorized(
1180 &input_id,
1181 result,
1182 authority(
1183 RuntimeCompletionResultClass::Completed,
1184 RuntimeCompletionObservedOutcome::Completed,
1185 ),
1186 );
1187
1188 match handle.wait_authorized().await {
1189 CompletionOutcome::Completed(r) => assert_eq!(r.text, "hello"),
1190 other => panic!("Expected Completed, got {other:?}"),
1191 }
1192 }
1193
1194 #[tokio::test]
1195 async fn register_and_fail_waiter() {
1196 let mut registry = CompletionRegistry::new();
1197 let input_id = InputId::new();
1198 let handle = registry.register(input_id.clone());
1199
1200 registry.fail_inputs(
1201 [input_id],
1202 CompletionWaitError::AuthorityUnavailable("retired".into()),
1203 );
1204
1205 match handle.try_wait().await {
1206 Err(CompletionWaitError::AuthorityUnavailable(reason)) => assert_eq!(reason, "retired"),
1207 other => panic!("Expected wait error, got {other:?}"),
1208 }
1209 }
1210
1211 #[tokio::test]
1212 async fn mismatched_result_authority_fails_waiter_closed() {
1213 let mut registry = CompletionRegistry::new();
1214 let input_id = InputId::new();
1215 let handle = registry.register(input_id.clone());
1216
1217 registry.resolve_completed_authorized(
1218 &input_id,
1219 make_run_result(),
1220 authority(
1221 RuntimeCompletionResultClass::Cancelled,
1222 RuntimeCompletionObservedOutcome::Cancelled,
1223 ),
1224 );
1225
1226 assert!(!registry.debug_has_waiters());
1227 match handle.try_wait().await {
1228 Err(CompletionWaitError::AuthorityUnavailable(reason)) => {
1229 assert!(reason.contains("Cancelled"));
1230 assert!(reason.contains("Completed"));
1231 }
1232 other => panic!("Expected authority mismatch wait error, got {other:?}"),
1233 }
1234 }
1235
1236 #[tokio::test]
1237 async fn resolve_all_runtime_terminated() {
1238 let mut registry = CompletionRegistry::new();
1239 let h1 = registry.register(InputId::new());
1240 let h2 = registry.register(InputId::new());
1241
1242 registry.resolve_all_runtime_terminated(
1243 "runtime stopped",
1244 authority(
1245 RuntimeCompletionResultClass::RuntimeTerminated,
1246 RuntimeCompletionObservedOutcome::RuntimeTerminated,
1247 ),
1248 );
1249
1250 assert!(!registry.debug_has_waiters());
1251
1252 match h1.wait_authorized().await {
1253 CompletionOutcome::RuntimeTerminated { reason, .. } => {
1254 assert_eq!(reason, "runtime stopped");
1255 }
1256 other => panic!("Expected RuntimeTerminated, got {other:?}"),
1257 }
1258 match h2.wait_authorized().await {
1259 CompletionOutcome::RuntimeTerminated { reason, .. } => {
1260 assert_eq!(reason, "runtime stopped");
1261 }
1262 other => panic!("Expected RuntimeTerminated, got {other:?}"),
1263 }
1264 }
1265
1266 #[tokio::test]
1267 async fn mismatched_runtime_terminated_authority_fails_all_waiters_closed() {
1268 let mut registry = CompletionRegistry::new();
1269 let h1 = registry.register(InputId::new());
1270 let h2 = registry.register(InputId::new());
1271
1272 registry.resolve_all_runtime_terminated(
1273 "runtime stopped",
1274 authority(
1275 RuntimeCompletionResultClass::CompletedWithoutResult,
1276 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1277 ),
1278 );
1279
1280 assert!(!registry.debug_has_waiters());
1281 for handle in [h1, h2] {
1282 match handle.try_wait().await {
1283 Err(CompletionWaitError::AuthorityUnavailable(reason)) => {
1284 assert!(reason.contains("CompletedWithoutResult"));
1285 assert!(reason.contains("RuntimeTerminated"));
1286 }
1287 other => panic!("Expected authority mismatch wait error, got {other:?}"),
1288 }
1289 }
1290 }
1291
1292 #[tokio::test]
1293 async fn cleanup_rejects_observation_from_another_session() {
1294 let adapter = crate::meerkat_machine::MeerkatMachine::ephemeral();
1295 let source_session_id = SessionId::new();
1296 let target_session_id = SessionId::new();
1297 adapter
1298 .prepare_bindings(source_session_id.clone())
1299 .await
1300 .expect("source session should prepare runtime bindings");
1301 adapter
1302 .prepare_bindings(target_session_id.clone())
1303 .await
1304 .expect("target session should prepare runtime bindings");
1305
1306 let input = crate::Input::Prompt(crate::PromptInput::new(
1307 "source session pending completion",
1308 None,
1309 ));
1310 let (_outcome, handle) = adapter
1311 .accept_input_with_completion(&source_session_id, input)
1312 .await
1313 .expect("source input should be accepted");
1314 let handle = handle.expect("source input should have a completion waiter");
1315 adapter
1316 .stop_runtime_executor(&source_session_id, "source stopped")
1317 .await
1318 .expect("source stop should resolve waiter");
1319 let (_outcome, observation) = handle
1320 .try_wait_with_cleanup_observation()
1321 .await
1322 .expect("waiter should resolve with generated cleanup observation");
1323
1324 assert_eq!(observation.owner_session_id(), &source_session_id);
1325 let err = adapter
1326 .resolve_runtime_completion_cleanup(
1327 &target_session_id,
1328 observation,
1329 false,
1330 crate::meerkat_machine::dsl::RuntimeCompletionLiveSessionObservation::Absent,
1331 )
1332 .await
1333 .expect_err("cleanup must reject an observation minted for another session");
1334 assert!(
1335 matches!(err, crate::RuntimeDriverError::ValidationFailed { .. }),
1336 "expected generated cleanup validation failure, got {err:?}"
1337 );
1338 }
1339
1340 #[tokio::test]
1341 async fn cleanup_rejects_stale_same_session_observation_after_rebinding() {
1342 let adapter = crate::meerkat_machine::MeerkatMachine::ephemeral();
1343 let session_id = SessionId::new();
1344 adapter
1345 .prepare_bindings(session_id.clone())
1346 .await
1347 .expect("session should prepare initial runtime bindings");
1348
1349 let input = crate::Input::Prompt(crate::PromptInput::new(
1350 "same session pending completion",
1351 None,
1352 ));
1353 let (_outcome, handle) = adapter
1354 .accept_input_with_completion(&session_id, input)
1355 .await
1356 .expect("input should be accepted");
1357 let handle = handle.expect("input should have a completion waiter");
1358 adapter
1359 .stop_runtime_executor(&session_id, "first runtime stopped")
1360 .await
1361 .expect("stop should resolve waiter");
1362 let (_outcome, stale_observation) = handle
1363 .try_wait_with_cleanup_observation()
1364 .await
1365 .expect("waiter should resolve with generated cleanup observation");
1366
1367 adapter
1368 .unregister_session(&session_id)
1369 .await
1370 .expect("initial runtime binding should unregister cleanly");
1371 adapter
1372 .prepare_bindings(session_id.clone())
1373 .await
1374 .expect("session should prepare replacement runtime bindings");
1375
1376 let err = adapter
1377 .resolve_runtime_completion_cleanup(
1378 &session_id,
1379 stale_observation,
1380 false,
1381 crate::meerkat_machine::dsl::RuntimeCompletionLiveSessionObservation::Absent,
1382 )
1383 .await
1384 .expect_err("cleanup must reject an observation minted for a prior runtime binding");
1385 assert!(
1386 matches!(err, crate::RuntimeDriverError::ValidationFailed { .. }),
1387 "expected generated cleanup validation failure, got {err:?}"
1388 );
1389 }
1390
1391 #[tokio::test]
1392 async fn wait_failure_authority_releases_pre_admission_and_classifies_public_reason() {
1393 let adapter = crate::meerkat_machine::MeerkatMachine::ephemeral();
1394 let session_id = SessionId::new();
1395 adapter
1396 .prepare_bindings(session_id.clone())
1397 .await
1398 .expect("session should prepare runtime bindings");
1399
1400 let authority = adapter
1401 .resolve_runtime_completion_wait_failure(
1402 &session_id,
1403 &CompletionWaitError::AuthorityUnavailable("missing generated result".into()),
1404 )
1405 .await
1406 .expect("wait-failure authority should resolve");
1407
1408 assert!(authority.releases_pre_admission());
1409 assert_eq!(
1410 authority.public_error_class,
1411 crate::meerkat_machine::dsl::RuntimeCompletionWaitFailurePublicErrorClass::InternalError
1412 );
1413 assert_eq!(
1414 authority.public_reason,
1415 crate::meerkat_machine::dsl::RuntimeCompletionWaitFailurePublicReason::CompletionAuthorityUnavailable
1416 );
1417 assert!(!authority.resumable);
1418 }
1419
1420 #[tokio::test]
1421 async fn wait_failure_authority_rejects_missing_session_authority() {
1422 let adapter = crate::meerkat_machine::MeerkatMachine::ephemeral();
1423 let session_id = SessionId::new();
1424
1425 let err = adapter
1426 .resolve_runtime_completion_wait_failure(
1427 &session_id,
1428 &CompletionWaitError::ChannelClosed,
1429 )
1430 .await
1431 .expect_err("wait-failure authority must fail closed without a session authority");
1432
1433 assert!(
1434 matches!(err, crate::RuntimeDriverError::ValidationFailed { .. }),
1435 "expected generated wait-failure validation failure, got {err:?}"
1436 );
1437 }
1438
1439 #[tokio::test]
1440 async fn resolve_nonexistent_is_a_noop() {
1441 let mut registry = CompletionRegistry::new();
1442 registry.resolve_completed_authorized(
1443 &InputId::new(),
1444 make_run_result(),
1445 authority(
1446 RuntimeCompletionResultClass::Completed,
1447 RuntimeCompletionObservedOutcome::Completed,
1448 ),
1449 );
1450 registry.fail_inputs(
1451 [InputId::new()],
1452 CompletionWaitError::AuthorityUnavailable("gone".into()),
1453 );
1454 assert!(!registry.debug_has_waiters());
1455 }
1456
1457 #[tokio::test]
1458 async fn dropped_sender_gives_wait_error() {
1459 let mut registry = CompletionRegistry::new();
1460 let input_id = InputId::new();
1461 let handle = registry.register(input_id);
1462
1463 drop(registry);
1465
1466 assert!(matches!(
1467 handle.try_wait().await,
1468 Err(CompletionWaitError::ChannelClosed)
1469 ));
1470 }
1471
1472 #[tokio::test]
1473 async fn multi_waiter_all_receive_result() {
1474 let mut registry = CompletionRegistry::new();
1475 let input_id = InputId::new();
1476
1477 let h1 = registry.register(input_id.clone());
1478 let h2 = registry.register(input_id.clone());
1479 let h3 = registry.register(input_id.clone());
1480
1481 assert_eq!(registry.debug_waiter_count(), 3);
1482
1483 let result = make_run_result();
1484 registry.resolve_completed_authorized(
1485 &input_id,
1486 result,
1487 authority(
1488 RuntimeCompletionResultClass::Completed,
1489 RuntimeCompletionObservedOutcome::Completed,
1490 ),
1491 );
1492
1493 assert!(!registry.debug_has_waiters());
1494
1495 for handle in [h1, h2, h3] {
1496 match handle.wait_authorized().await {
1497 CompletionOutcome::Completed(r) => assert_eq!(r.text, "hello"),
1498 other => panic!("Expected Completed, got {other:?}"),
1499 }
1500 }
1501 }
1502
1503 #[tokio::test]
1504 async fn resolve_without_result_sends_variant() {
1505 let mut registry = CompletionRegistry::new();
1506 let input_id = InputId::new();
1507 let handle = registry.register(input_id.clone());
1508
1509 registry.resolve_without_result_authorized(
1510 &input_id,
1511 authority(
1512 RuntimeCompletionResultClass::CompletedWithoutResult,
1513 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1514 ),
1515 );
1516
1517 match handle.wait_authorized().await {
1518 CompletionOutcome::CompletedWithoutResult => {}
1519 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1520 }
1521 }
1522
1523 #[tokio::test]
1524 async fn resolve_without_result_multi_waiter() {
1525 let mut registry = CompletionRegistry::new();
1526 let input_id = InputId::new();
1527 let h1 = registry.register(input_id.clone());
1528 let h2 = registry.register(input_id.clone());
1529
1530 registry.resolve_without_result_authorized(
1531 &input_id,
1532 authority(
1533 RuntimeCompletionResultClass::CompletedWithoutResult,
1534 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1535 ),
1536 );
1537
1538 for handle in [h1, h2] {
1539 match handle.wait_authorized().await {
1540 CompletionOutcome::CompletedWithoutResult => {}
1541 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1542 }
1543 }
1544 }
1545
1546 #[tokio::test]
1547 async fn resolve_callback_pending_sends_variant() {
1548 let mut registry = CompletionRegistry::new();
1549 let input_id = InputId::new();
1550 let handle = registry.register(input_id.clone());
1551
1552 registry.resolve_callback_pending_authorized(
1553 &input_id,
1554 "browser".to_string(),
1555 serde_json::json!({ "url": "https://example.com" }),
1556 authority(
1557 RuntimeCompletionResultClass::CallbackPending,
1558 RuntimeCompletionObservedOutcome::CallbackPending,
1559 ),
1560 );
1561
1562 match handle.wait_authorized().await {
1563 CompletionOutcome::CallbackPending { tool_name, args } => {
1564 assert_eq!(tool_name, "browser");
1565 assert_eq!(args, serde_json::json!({ "url": "https://example.com" }));
1566 }
1567 other => panic!("Expected CallbackPending, got {other:?}"),
1568 }
1569 }
1570
1571 #[tokio::test]
1572 async fn resolve_cancelled_sends_variant() {
1573 let mut registry = CompletionRegistry::new();
1574 let input_id = InputId::new();
1575 let handle = registry.register(input_id.clone());
1576
1577 registry.resolve_cancelled_authorized(
1578 &input_id,
1579 authority(
1580 RuntimeCompletionResultClass::Cancelled,
1581 RuntimeCompletionObservedOutcome::Cancelled,
1582 ),
1583 );
1584
1585 match handle.wait_authorized().await {
1586 CompletionOutcome::Cancelled => {}
1587 other => panic!("Expected Cancelled, got {other:?}"),
1588 }
1589 }
1590
1591 #[tokio::test]
1592 async fn already_resolved_handle() {
1593 let handle = CompletionHandle::already_completed_without_result()
1594 .expect("generated completion authority should classify no-result completion");
1595 match handle.wait_authorized().await {
1596 CompletionOutcome::CompletedWithoutResult => {}
1597 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1598 }
1599 }
1600
1601 #[tokio::test]
1602 async fn outcome_cleanup_observes_and_relays_result() {
1603 use std::sync::Arc;
1604 use std::sync::atomic::{AtomicBool, Ordering};
1605
1606 let mut registry = CompletionRegistry::new();
1607 let input_id = InputId::new();
1608 let handle = registry.register(input_id.clone());
1609 let observed = Arc::new(AtomicBool::new(false));
1610 let cleanup_observed = Arc::clone(&observed);
1611 let handle = handle.with_outcome_cleanup(move |observation| async move {
1612 if observation.observed_outcome()
1613 == crate::meerkat_machine::dsl::RuntimeCompletionObservedOutcome::CompletedWithoutResult
1614 {
1615 cleanup_observed.store(true, Ordering::Release);
1616 }
1617 });
1618
1619 registry.resolve_without_result_authorized(
1620 &input_id,
1621 authority(
1622 RuntimeCompletionResultClass::CompletedWithoutResult,
1623 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1624 ),
1625 );
1626 match handle.wait_authorized().await {
1627 CompletionOutcome::CompletedWithoutResult => {}
1628 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1629 }
1630 assert!(observed.load(Ordering::Acquire));
1631 }
1632
1633 #[tokio::test]
1634 async fn resultful_completion_cleanup_withholds_success_and_combines_failures() {
1635 let handle = CompletionHandle::already_completed_without_result()
1636 .expect("generated completion authority should classify no-result completion")
1637 .with_resultful_completion_cleanup(|_| async {
1638 Err(CompletionWaitError::AuthorityUnavailable(
1639 "required cleanup failed".to_string(),
1640 ))
1641 });
1642 let error = handle
1643 .wait()
1644 .await
1645 .expect_err("required cleanup failure must withhold success");
1646 assert!(error.to_string().contains("required cleanup failed"));
1647
1648 let (tx, rx) = oneshot::channel();
1649 drop(tx);
1650 let handle =
1651 CompletionHandle { rx }.with_resultful_completion_cleanup(|completion| async move {
1652 assert!(matches!(
1653 completion,
1654 Err(CompletionWaitError::ChannelClosed)
1655 ));
1656 Err(CompletionWaitError::AuthorityUnavailable(
1657 "cleanup authority failed".to_string(),
1658 ))
1659 });
1660 let error = handle
1661 .wait()
1662 .await
1663 .expect_err("two failures must remain a failure");
1664 let rendered = error.to_string();
1665 assert!(rendered.contains("completion channel closed"));
1666 assert!(rendered.contains("cleanup authority failed"));
1667 }
1668
1669 #[tokio::test]
1670 async fn multi_waiter_terminated_on_reset() {
1671 let mut registry = CompletionRegistry::new();
1672 let input_id = InputId::new();
1673 let h1 = registry.register(input_id.clone());
1674 let h2 = registry.register(input_id);
1675
1676 registry.resolve_all_runtime_terminated(
1677 "runtime reset",
1678 authority(
1679 RuntimeCompletionResultClass::RuntimeTerminated,
1680 RuntimeCompletionObservedOutcome::RuntimeTerminated,
1681 ),
1682 );
1683
1684 for handle in [h1, h2] {
1685 match handle.wait_authorized().await {
1686 CompletionOutcome::RuntimeTerminated { reason, .. } => {
1687 assert_eq!(reason, "runtime reset");
1688 }
1689 other => panic!("Expected RuntimeTerminated, got {other:?}"),
1690 }
1691 }
1692 }
1693
1694 #[tokio::test]
1695 async fn resolve_not_pending_keeps_pending_waiters() {
1696 let mut registry = CompletionRegistry::new();
1697 let keep_id = InputId::new();
1698 let drop_id = InputId::new();
1699
1700 let keep_handle = registry.register(keep_id.clone());
1701 let drop_handle = registry.register(drop_id.clone());
1702 registry.resolve_not_pending_runtime_terminated(
1703 |input_id| input_id == &keep_id,
1704 "runtime recycled",
1705 authority(
1706 RuntimeCompletionResultClass::RuntimeTerminated,
1707 RuntimeCompletionObservedOutcome::RuntimeTerminated,
1708 ),
1709 );
1710 assert_eq!(registry.debug_waiter_count(), 1);
1711
1712 match drop_handle.wait_authorized().await {
1713 CompletionOutcome::RuntimeTerminated { reason, .. } => {
1714 assert_eq!(reason, "runtime recycled");
1715 }
1716 other => panic!("Expected RuntimeTerminated, got {other:?}"),
1717 }
1718
1719 registry.resolve_without_result_authorized(
1720 &keep_id,
1721 authority(
1722 RuntimeCompletionResultClass::CompletedWithoutResult,
1723 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1724 ),
1725 );
1726 match keep_handle.wait_authorized().await {
1727 CompletionOutcome::CompletedWithoutResult => {}
1728 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1729 }
1730 }
1731
1732 #[tokio::test]
1733 async fn mismatched_not_pending_runtime_terminated_authority_fails_selected_waiters_closed() {
1734 let mut registry = CompletionRegistry::new();
1735 let keep_id = InputId::new();
1736 let drop_id = InputId::new();
1737
1738 let keep_handle = registry.register(keep_id.clone());
1739 let drop_handle = registry.register(drop_id);
1740 registry.resolve_not_pending_runtime_terminated(
1741 |input_id| input_id == &keep_id,
1742 "runtime recycled",
1743 authority(
1744 RuntimeCompletionResultClass::Completed,
1745 RuntimeCompletionObservedOutcome::Completed,
1746 ),
1747 );
1748
1749 assert_eq!(registry.debug_waiter_count(), 1);
1750 match drop_handle.try_wait().await {
1751 Err(CompletionWaitError::AuthorityUnavailable(reason)) => {
1752 assert!(reason.contains("Completed"));
1753 assert!(reason.contains("RuntimeTerminated"));
1754 }
1755 other => panic!("Expected authority mismatch wait error, got {other:?}"),
1756 }
1757
1758 registry.resolve_without_result_authorized(
1759 &keep_id,
1760 authority(
1761 RuntimeCompletionResultClass::CompletedWithoutResult,
1762 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1763 ),
1764 );
1765 match keep_handle.wait_authorized().await {
1766 CompletionOutcome::CompletedWithoutResult => {}
1767 other => panic!("Expected CompletedWithoutResult, got {other:?}"),
1768 }
1769 }
1770
1771 #[tokio::test]
1772 async fn resolve_without_result_nonexistent_is_a_noop() {
1773 let mut registry = CompletionRegistry::new();
1774 registry.resolve_without_result_authorized(
1775 &InputId::new(),
1776 authority(
1777 RuntimeCompletionResultClass::CompletedWithoutResult,
1778 RuntimeCompletionObservedOutcome::CompletedWithoutResult,
1779 ),
1780 );
1781 assert!(!registry.debug_has_waiters());
1782 }
1783
1784 #[test]
1785 fn abandoned_carries_typed_error_metadata() {
1786 let error = TurnErrorMetadata::runtime_apply_failure("apply blew up");
1787 let outcome = CompletionOutcome::Abandoned {
1788 reason: "abandoned".into(),
1789 error: error.clone(),
1790 };
1791 assert_eq!(outcome.abandoned_reason(), Some("abandoned"));
1792 assert_eq!(outcome.error_metadata(), Some(&error));
1793 }
1794
1795 #[test]
1796 fn runtime_terminated_carries_typed_error_metadata() {
1797 let outcome = CompletionOutcome::runtime_terminated("runtime stopped");
1798 match &outcome {
1799 CompletionOutcome::RuntimeTerminated { reason, .. } => {
1800 assert_eq!(reason, "runtime stopped");
1801 }
1802 other => panic!("Expected RuntimeTerminated, got {other:?}"),
1803 }
1804 let metadata = outcome
1805 .error_metadata()
1806 .expect("RuntimeTerminated must carry typed turn error metadata");
1807 assert_eq!(metadata.kind, TurnTerminalCauseKind::FatalFailure);
1808 assert_eq!(metadata.outcome, Some(TurnTerminalOutcome::Failed));
1809 assert!(metadata.terminal);
1810 assert_eq!(metadata.detail.as_deref(), Some("runtime stopped"));
1811 }
1812}