Skip to main content

meerkat_runtime/meerkat_machine/
dispatch_ingress.rs

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    /// Preview generated admission feedback for shell capacity mechanics.
82    ///
83    /// The caller does not classify policy/defaults itself; it only observes
84    /// whether generated `ResolveAdmissionPlan` would ask the runtime to wake,
85    /// interrupt, or process immediately.
86    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    /// Preview generated admission feedback for no-wake shell capacity mechanics.
96    ///
97    /// This mirrors `accept_input_without_wake`: the shell supplies only the
98    /// command mode, while generated `ResolveAdmissionPlan` owns the semantic
99    /// pre-admission answer.
100    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                    // origin/main observability: surface the idle/running
254                    // disposition (idle, attached non-steer, or attached peer)
255                    // for ingress-admission diagnostics, including the
256                    // remote-comms / paired-TCP peer admission path. The
257                    // semantic decision itself is NOT taken here: it flows
258                    // through the canonical machine seam
259                    // (resolve_admission_with_active_turn_boundary), which
260                    // derives idle vs running internally from the recovered
261                    // authority plus active_turn_boundary_available (P0
262                    // Invariant 1 — no handwritten admission resolution).
263                    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}