1use super::*;
2
3impl MeerkatMachine {
4 fn classify_ingress_dsl_rejection(state: RuntimeState, reason: String) -> RuntimeDriverError {
5 match crate::meerkat_machine::classify_runtime_lifecycle_state(state) {
6 Ok(facts) => match facts.ingress_admission {
7 crate::meerkat_machine::dsl::RuntimeIngressAdmission::Destroyed => {
8 RuntimeDriverError::Destroyed
9 }
10 crate::meerkat_machine::dsl::RuntimeIngressAdmission::NotReady => {
11 RuntimeDriverError::NotReady { state }
12 }
13 crate::meerkat_machine::dsl::RuntimeIngressAdmission::Open => {
14 RuntimeDriverError::ValidationFailed { reason }
15 }
16 },
17 Err(error) => RuntimeDriverError::Internal(error),
18 }
19 }
20
21 fn reject_visible_terminal_ingress(state: RuntimeState) -> Result<(), RuntimeDriverError> {
22 let facts =
23 crate::meerkat_machine::classify_runtime_lifecycle_state(state).map_err(|reason| {
24 RuntimeDriverError::Internal(format!(
25 "generated runtime ingress admission classification failed for {state}: {reason}"
26 ))
27 })?;
28 match facts.ingress_admission {
29 crate::meerkat_machine::dsl::RuntimeIngressAdmission::Destroyed => {
30 Err(RuntimeDriverError::Destroyed)
31 }
32 crate::meerkat_machine::dsl::RuntimeIngressAdmission::NotReady => {
33 Err(RuntimeDriverError::NotReady { state })
34 }
35 crate::meerkat_machine::dsl::RuntimeIngressAdmission::Open => Ok(()),
36 }
37 }
38
39 async fn reject_unregistration_drain_ingress(
40 &self,
41 session_id: &SessionId,
42 state: RuntimeState,
43 ) -> Result<(), RuntimeDriverError> {
44 let dsl_state = self.session_dsl_state(session_id).await.map_err(|reason| {
45 RuntimeDriverError::Internal(format!(
46 "failed to read generated registration phase for ingress admission: {reason}"
47 ))
48 })?;
49 if dsl_state.registration_phase == crate::meerkat_machine::dsl::RegistrationPhase::Draining
50 {
51 return Err(RuntimeDriverError::NotReady { state });
52 }
53 Ok(())
54 }
55
56 pub(super) async fn observe_active_turn_boundary_available(
57 session_id: &SessionId,
58 boundary_handle: Option<
59 &std::sync::Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>,
60 >,
61 ) -> Result<bool, RuntimeDriverError> {
62 if let Some(boundary_handle) = boundary_handle {
63 boundary_handle
64 .active_turn_boundary_available()
65 .await
66 .map_err(|error| {
67 tracing::debug!(
68 session_id = %session_id,
69 error = %error,
70 "active turn boundary availability check failed"
71 );
72 RuntimeDriverError::Internal(format!(
73 "active turn boundary availability check failed: {error}"
74 ))
75 })
76 } else {
77 Ok(false)
78 }
79 }
80
81 pub async fn input_requires_active_pre_admission(
87 &self,
88 session_id: &SessionId,
89 input: &Input,
90 ) -> Result<bool, RuntimeDriverError> {
91 self.input_requires_active_pre_admission_with_wake_policy(session_id, input, false)
92 .await
93 }
94
95 pub async fn input_requires_active_pre_admission_without_wake(
101 &self,
102 session_id: &SessionId,
103 input: &Input,
104 ) -> Result<bool, RuntimeDriverError> {
105 self.input_requires_active_pre_admission_with_wake_policy(session_id, input, true)
106 .await
107 }
108
109 async fn input_requires_active_pre_admission_with_wake_policy(
110 &self,
111 session_id: &SessionId,
112 input: &Input,
113 without_wake: bool,
114 ) -> Result<bool, RuntimeDriverError> {
115 let (driver, boundary_handle) = {
116 let sessions = self.sessions.read().await;
117 let entry = sessions
118 .get(session_id)
119 .ok_or(RuntimeDriverError::NotReady {
120 state: RuntimeState::Destroyed,
121 })?;
122 (entry.driver.clone(), entry.boundary_handle())
123 };
124
125 let gate = self.session_mutation_gate(session_id).await;
126 let _gate_guard = match gate {
127 Some(ref g) => Some(g.lock().await),
128 None => None,
129 };
130
131 let visible_state = self
132 .existing_session_visible_runtime_state(session_id)
133 .await
134 .unwrap_or(RuntimeState::Destroyed);
135 Self::reject_visible_terminal_ingress(visible_state)?;
136 let active_turn_boundary_available =
137 Self::observe_active_turn_boundary_available(session_id, boundary_handle.as_ref())
138 .await?;
139
140 let driver = driver.lock().await;
141 let resolved = if without_wake {
142 driver.resolve_admission_without_wake_with_active_turn_boundary(
143 input,
144 active_turn_boundary_available,
145 )?
146 } else {
147 driver.resolve_admission_with_active_turn_boundary(
148 input,
149 active_turn_boundary_available,
150 )?
151 };
152 Ok(resolved.requires_active_runtime_pre_admission())
153 }
154
155 pub(super) async fn execute_meerkat_machine_ingress_command(
156 &self,
157 command: MeerkatMachineCommand,
158 ) -> Result<MeerkatMachineCommandResult, RuntimeDriverError> {
159 match command {
160 MeerkatMachineCommand::AcceptWithCompletion {
161 session_id,
162 input,
163 register_completion,
164 } => {
165 tracing::debug!(
166 session_id = %session_id,
167 input_id = %input.id(),
168 register_completion,
169 "MeerkatMachine::AcceptWithCompletion loading session entry"
170 );
171 let (driver, completions, wake_tx, effect_tx, boundary_handle) = {
172 let sessions = self.sessions.read().await;
173 let entry = sessions
174 .get(&session_id)
175 .ok_or(RuntimeDriverError::NotReady {
176 state: RuntimeState::Destroyed,
177 })?;
178 (
179 entry.driver.clone(),
180 entry.completions.clone(),
181 entry.wake_sender(),
182 entry.effect_sender(),
183 entry.boundary_handle(),
184 )
185 };
186 tracing::debug!(
187 session_id = %session_id,
188 input_id = %input.id(),
189 "MeerkatMachine::AcceptWithCompletion loaded session entry"
190 );
191
192 tracing::debug!(
193 session_id = %session_id,
194 input_id = %input.id(),
195 "MeerkatMachine::AcceptWithCompletion resolving mutation gate"
196 );
197 let gate = self.session_mutation_gate(&session_id).await;
198 tracing::debug!(
199 session_id = %session_id,
200 input_id = %input.id(),
201 has_gate = gate.is_some(),
202 "MeerkatMachine::AcceptWithCompletion resolved mutation gate"
203 );
204 let _gate_guard = match gate {
205 Some(ref g) => Some(g.lock().await),
206 None => None,
207 };
208 tracing::debug!(
209 session_id = %session_id,
210 input_id = %input.id(),
211 "MeerkatMachine::AcceptWithCompletion acquired mutation gate"
212 );
213
214 tracing::debug!(
215 session_id = %session_id,
216 input_id = %input.id(),
217 "MeerkatMachine::AcceptWithCompletion reading runtime state"
218 );
219 let state = self
220 .existing_session_runtime_state(&session_id)
221 .await
222 .unwrap_or(RuntimeState::Destroyed);
223 let visible_state = self
224 .existing_session_visible_runtime_state(&session_id)
225 .await
226 .unwrap_or(RuntimeState::Destroyed);
227 tracing::debug!(
228 session_id = %session_id,
229 input_id = %input.id(),
230 runtime_state = ?state,
231 visible_state = ?visible_state,
232 "MeerkatMachine::AcceptWithCompletion read runtime state"
233 );
234 Self::reject_visible_terminal_ingress(visible_state)?;
235 self.reject_unregistration_drain_ingress(&session_id, state)
236 .await?;
237
238 let active_turn_boundary_available = Self::observe_active_turn_boundary_available(
239 &session_id,
240 boundary_handle.as_ref(),
241 )
242 .await?;
243 if active_turn_boundary_available {
244 tracing::debug!(
245 session_id = %session_id,
246 runtime_state = ?state,
247 "active turn boundary available during ingress admission"
248 );
249 }
250
251 let (flags, stages_run_boundary, outcome, handle, accepted_input_id, signal) = {
252 let mut driver = driver.lock().await;
253 let input_kind = input.kind();
264 let runtime_idle = !active_turn_boundary_available
265 && (state == RuntimeState::Idle
266 || (state == RuntimeState::Attached
267 && !matches!(
268 input.handling_mode(),
269 Some(meerkat_core::types::HandlingMode::Steer)
270 ))
271 || (state == RuntimeState::Attached
272 && matches!(input, Input::Peer(_))));
273 tracing::debug!(
274 session_id = %session_id,
275 input_kind = ?input_kind,
276 runtime_state = ?state,
277 visible_state = ?visible_state,
278 runtime_idle,
279 active_turn_boundary_available,
280 "resolving runtime ingress admission via canonical machine seam"
281 );
282 let resolved = driver.resolve_admission_with_active_turn_boundary(
283 &input,
284 active_turn_boundary_available,
285 )?;
286 let flags = resolved.coarse_flags();
287 let stages_run_boundary = resolved.stages_run_boundary();
288 self.preview_session_dsl_input(
289 &session_id,
290 crate::meerkat_machine::dsl::MeerkatMachineInput::AcceptWithCompletion {
291 input_id: crate::meerkat_machine::dsl::InputId::from_domain(
292 &InputId::new(),
293 ),
294 request_immediate_processing: flags.request_immediate_processing,
295 interrupt_yielding: flags.interrupt_yielding,
296 wake_if_idle: flags.wake_if_idle,
297 },
298 "AcceptWithCompletion",
299 )
300 .await
301 .map_err(|reason| {
302 let reason = format!(
303 "{reason}; input_kind={}; immediate={}; interrupt_yielding={}; wake_if_idle={}",
304 input.kind(),
305 flags.request_immediate_processing,
306 flags.interrupt_yielding,
307 flags.wake_if_idle,
308 );
309 Self::classify_ingress_dsl_rejection(state, reason)
310 })?;
311 let result = match driver
312 .accept_resolved_input(input, resolved)
313 .await
314 .map_err(Self::normalize_destroyed_error)
315 {
316 Ok(r) => r,
317 Err(err) => return Err(err),
318 };
319
320 match &result {
321 AcceptOutcome::Accepted { input_id, .. } => {
322 let accepted_input_id = input_id.clone();
323 let is_terminal =
324 driver.input_is_terminal_by_authority(&accepted_input_id)?;
325 let handle = if is_terminal || !register_completion {
326 None
327 } else {
328 Some({
329 let mut completions = completions.lock().await;
330 completions.register(accepted_input_id.clone())
331 })
332 };
333 (
334 flags,
335 stages_run_boundary,
336 result,
337 handle,
338 Some(accepted_input_id),
339 crate::driver::ephemeral::PostAdmissionSignal::None,
340 )
341 }
342 AcceptOutcome::Deduplicated { existing_id, .. } => {
343 let is_terminal = driver.input_is_terminal_by_authority(existing_id)?;
344
345 if is_terminal || !register_completion {
346 (
347 flags,
348 stages_run_boundary,
349 result,
350 None,
351 None,
352 crate::driver::ephemeral::PostAdmissionSignal::None,
353 )
354 } else {
355 let handle = {
356 let mut completions = completions.lock().await;
357 completions.register(existing_id.clone())
358 };
359 (
360 flags,
361 stages_run_boundary,
362 result,
363 Some(handle),
364 None,
365 crate::driver::ephemeral::PostAdmissionSignal::None,
366 )
367 }
368 }
369 AcceptOutcome::Rejected { reason } => {
370 return Err(RuntimeDriverError::ValidationFailed {
371 reason: reason.to_string(),
372 });
373 }
374 }
375 };
376 let accepted_input_id_for_live_boundary = accepted_input_id.clone();
377 let (signal, runtime_effect, effect_previous_dsl_state) = if let Some(input_id) =
378 accepted_input_id.clone()
379 {
380 let (previous_dsl_state, effects) = self
381 .apply_session_dsl_input(
382 &session_id,
383 crate::meerkat_machine::dsl::MeerkatMachineInput::AcceptWithCompletion {
384 input_id: crate::meerkat_machine::dsl::InputId::from_domain(
385 &input_id,
386 ),
387 request_immediate_processing: flags.request_immediate_processing,
388 interrupt_yielding: flags.interrupt_yielding,
389 wake_if_idle: flags.wake_if_idle,
390 },
391 "AcceptWithCompletion",
392 )
393 .await
394 .map_err(|reason| {
395 RuntimeDriverError::Internal(format!(
396 "canonical AcceptWithCompletion apply failed after admission: {reason}"
397 ))
398 })?;
399 {
400 let mut driver = driver.lock().await;
401 driver.absorb_post_admission_effects(&effects);
402 }
403 let signal = Self::post_admission_signal_from_effects(&effects);
404 let runtime_effect =
405 crate::effect::runtime_effect_projection_optional_from_dsl_effects(
406 &effects,
407 )
408 .map_err(|reason| {
409 RuntimeDriverError::Internal(format!(
410 "canonical AcceptWithCompletion emitted invalid runtime effect facts: {reason}"
411 ))
412 })?;
413 (signal, runtime_effect, Some(previous_dsl_state))
414 } else {
415 (signal, None, None)
416 };
417
418 if signal.should_wake()
419 && let Some(ref wake_tx) = wake_tx
420 {
421 let _ = wake_tx.try_send(());
422 }
423 if let Some(projected_effect) = runtime_effect
424 && let Err(err) = self
425 .dispatch_cancel_after_boundary_runtime_effect(
426 &session_id,
427 effect_tx,
428 boundary_handle.clone(),
429 projected_effect,
430 "AcceptWithCompletion",
431 )
432 .await
433 {
434 if let Some(previous_dsl_state) = effect_previous_dsl_state {
435 self.restore_session_dsl_state(&session_id, previous_dsl_state)
436 .await;
437 }
438 return Err(err);
439 }
440
441 let has_live_boundary_input = accepted_input_id_for_live_boundary.is_some();
442 let has_boundary_handle = boundary_handle.is_some();
443 if active_turn_boundary_available
444 && signal.should_interrupt_yielding()
445 && stages_run_boundary
446 && let (Some(input_id), Some(boundary_handle)) =
447 (accepted_input_id_for_live_boundary, boundary_handle)
448 {
449 let live_boundary_plan = {
450 let driver = driver.lock().await;
451 let run_id = driver.current_run_id();
452 let projection = driver.driver_ingress().primitive_projection(&input_id);
453 run_id.and_then(|run_id| {
454 let projection = projection?;
455 let appends =
456 crate::input::projection_to_pending_system_context_appends(
457 &input_id,
458 &projection,
459 );
460 if appends.is_empty() {
461 return None;
462 }
463 Some((run_id, appends))
464 })
465 };
466
467 if let Some((run_id, appends)) = live_boundary_plan {
468 let rollback_keys = appends
469 .iter()
470 .filter_map(|append| append.idempotency_key.clone())
471 .collect::<Vec<_>>();
472 tracing::debug!(
473 session_id = %session_id,
474 run_id = %run_id,
475 input_id = %input_id,
476 append_count = appends.len(),
477 "staging live boundary context for accepted steer input"
478 );
479 match boundary_handle
480 .stage_system_context_at_boundary(&run_id, appends)
481 .await
482 {
483 Ok(stage_output) => {
484 let commit_result = {
485 let mut driver = driver.lock().await;
486 driver
487 .machine_realize_live_boundary_context_injected(
488 &run_id,
489 std::slice::from_ref(&input_id),
490 stage_output.session_snapshot,
491 )
492 .await
493 };
494 if let Err(error) = commit_result {
495 let rollback_result = if rollback_keys.is_empty() {
496 Ok(())
497 } else {
498 boundary_handle
499 .discard_staged_system_context_at_boundary(
500 &run_id,
501 rollback_keys,
502 )
503 .await
504 };
505 match rollback_result {
506 Ok(()) => {
507 tracing::warn!(
508 session_id = %session_id,
509 run_id = %run_id,
510 input_id = %input_id,
511 error = %error,
512 "live boundary runtime commit failed; rolled back staged session context"
513 );
514 }
515 Err(rollback_error) => {
516 tracing::error!(
517 session_id = %session_id,
518 run_id = %run_id,
519 input_id = %input_id,
520 error = %error,
521 rollback_error = %rollback_error,
522 "live boundary runtime commit failed and staged session context rollback failed"
523 );
524 }
525 }
526 return Err(error);
527 }
528 let result_class =
529 crate::meerkat_machine::driver::machine_resolve_runtime_completed_without_result(
530 &driver,
531 &run_id,
532 )
533 .await?;
534 let mut completions = completions.lock().await;
535 completions
536 .resolve_without_result_authorized(&input_id, result_class);
537 }
538 Err(error) => {
539 tracing::warn!(
540 session_id = %session_id,
541 run_id = %run_id,
542 input_id = %input_id,
543 error = %error,
544 "live boundary context staging failed; leaving steer input queued for ordinary post-turn drain"
545 );
546 }
547 }
548 } else {
549 tracing::debug!(
550 session_id = %session_id,
551 input_id = %input_id,
552 runtime_state = ?state,
553 active_turn_boundary_available,
554 "accepted steer input had no live boundary plan; leaving input queued for ordinary post-turn drain"
555 );
556 }
557 } else if signal.should_interrupt_yielding() && stages_run_boundary {
558 tracing::debug!(
559 session_id = %session_id,
560 runtime_state = ?state,
561 active_turn_boundary_available,
562 has_boundary_handle,
563 has_input_id = has_live_boundary_input,
564 "accepted steer input did not meet live boundary staging preconditions"
565 );
566 }
567
568 Ok(MeerkatMachineCommandResult::AcceptWithCompletion {
569 outcome,
570 handle,
571 admission_signal: signal,
572 })
573 }
574 MeerkatMachineCommand::AcceptWithoutWake { session_id, input } => {
575 let (driver, boundary_handle) = {
576 let sessions = self.sessions.read().await;
577 let entry = sessions
578 .get(&session_id)
579 .ok_or(RuntimeDriverError::NotReady {
580 state: RuntimeState::Destroyed,
581 })?;
582 (entry.driver.clone(), entry.boundary_handle())
583 };
584
585 let gate = self.session_mutation_gate(&session_id).await;
586 let _gate_guard = match gate {
587 Some(ref g) => Some(g.lock().await),
588 None => None,
589 };
590
591 let state = self
592 .existing_session_runtime_state(&session_id)
593 .await
594 .unwrap_or(RuntimeState::Destroyed);
595 let visible_state = self
596 .existing_session_visible_runtime_state(&session_id)
597 .await
598 .unwrap_or(RuntimeState::Destroyed);
599 Self::reject_visible_terminal_ingress(visible_state)?;
600 self.reject_unregistration_drain_ingress(&session_id, state)
601 .await?;
602 let active_turn_boundary_available = Self::observe_active_turn_boundary_available(
603 &session_id,
604 boundary_handle.as_ref(),
605 )
606 .await?;
607
608 let (outcome, accepted_input_id) = {
609 let mut driver = driver.lock().await;
610 let resolved = driver
611 .resolve_admission_without_wake_with_active_turn_boundary(
612 &input,
613 active_turn_boundary_available,
614 )?;
615 self.preview_session_dsl_input(
616 &session_id,
617 crate::meerkat_machine::dsl::MeerkatMachineInput::AcceptWithoutWake {
618 input_id: crate::meerkat_machine::dsl::InputId::from_domain(
619 &InputId::new(),
620 ),
621 },
622 "AcceptWithoutWake",
623 )
624 .await
625 .map_err(|reason| Self::classify_ingress_dsl_rejection(state, reason))?;
626 let result = match driver
627 .accept_resolved_input(input, resolved)
628 .await
629 .map_err(Self::normalize_destroyed_error)
630 {
631 Ok(r) => r,
632 Err(err) => return Err(err),
633 };
634 if let AcceptOutcome::Rejected { reason } = &result {
635 return Err(RuntimeDriverError::ValidationFailed {
636 reason: reason.to_string(),
637 });
638 }
639 let accepted_input_id = match &result {
640 AcceptOutcome::Accepted { input_id, .. } => Some(input_id.clone()),
641 AcceptOutcome::Deduplicated { .. } => None,
642 AcceptOutcome::Rejected { .. } => unreachable!("handled above"),
643 };
644 (result, accepted_input_id)
645 };
646 if let Some(input_id) = accepted_input_id {
647 let (_, effects) = self
648 .apply_session_dsl_input(
649 &session_id,
650 crate::meerkat_machine::dsl::MeerkatMachineInput::AcceptWithoutWake {
651 input_id: crate::meerkat_machine::dsl::InputId::from_domain(
652 &input_id,
653 ),
654 },
655 "AcceptWithoutWake",
656 )
657 .await
658 .map_err(|reason| {
659 RuntimeDriverError::Internal(format!(
660 "canonical AcceptWithoutWake apply failed after admission: {reason}"
661 ))
662 })?;
663 {
664 let mut driver = driver.lock().await;
665 driver.absorb_post_admission_effects(&effects);
666 }
667 let signal = Self::post_admission_signal_from_effects(&effects);
668 debug_assert!(
669 !signal.should_wake()
670 && !signal.should_interrupt_yielding()
671 && !signal.should_process_immediately(),
672 "AcceptWithoutWake unexpectedly emitted a post-admission signal"
673 );
674 }
675
676 Ok(MeerkatMachineCommandResult::AcceptOutcome(outcome))
677 }
678 _ => unreachable!("non-ingress command routed to ingress handler"),
679 }
680 }
681}