diff --git a/crates/broker/src/pty_worker.rs b/crates/broker/src/pty_worker.rs index ca95365c9..8a08a0387 100644 --- a/crates/broker/src/pty_worker.rs +++ b/crates/broker/src/pty_worker.rs @@ -300,6 +300,24 @@ fn evaluate_startup_gate( } } +/// The startup gate's verdict. +/// +/// Three states, not two booleans: `Ready && Blocked` is not representable, +/// and the difference between "we could not recognise the prompt" and "the +/// harness is deliberately refusing work" decides whether the deadline may +/// release queued work at all. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum StartupGate { + /// A real input prompt was proven. + Ready, + /// No prompt recognised yet. Our heuristic may simply be blind, so the + /// deadline is allowed to release queued work. + Unrecognised, + /// A known blocking dialog is on screen — the harness is deliberately not + /// accepting input. Never released by any deadline. + Blocked, +} + /// Whether a KNOWN blocking dialog is on screen, as opposed to a prompt we /// simply failed to recognise. /// @@ -460,8 +478,7 @@ async fn try_emit_worker_ready( init_request_id: &mut Option, init_received_at: Option, readiness: &mut StartupReadinessState, - startup_ready: bool, - startup_blocked: bool, + gate: StartupGate, ) { // init_received_at is Some only after init_worker has been received. // We use it (not init_request_id) as the gate because the broker sends @@ -470,10 +487,11 @@ async fn try_emit_worker_ready( return; } + let startup_ready = gate == StartupGate::Ready; // A deliberate veto is not a blind spot: never time out past a known // blocking dialog, or the brief is typed into a trust prompt and answers a // security question nobody asked us to answer. - let timed_out = !startup_blocked + let timed_out = gate != StartupGate::Blocked && init_received_at.is_some_and(|started| started.elapsed() >= STARTUP_READY_TIMEOUT); if !startup_ready && !timed_out { @@ -842,7 +860,13 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &post_boot_output, &pty, ); - let startup_blocked = startup_gate_blocked(&pty); + let gate = if startup_ready { + StartupGate::Ready + } else if startup_gate_blocked(&pty) { + StartupGate::Blocked + } else { + StartupGate::Unrecognised + }; try_emit_worker_ready( &out_tx, &worker_name, @@ -850,8 +874,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &mut init_request_id, init_received_at, &mut startup_readiness, - startup_ready, - startup_blocked, + gate, ) .await; } @@ -1236,7 +1259,13 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &post_boot_output, &pty, ); - let startup_blocked = startup_gate_blocked(&pty); + let gate = if startup_ready { + StartupGate::Ready + } else if startup_gate_blocked(&pty) { + StartupGate::Blocked + } else { + StartupGate::Unrecognised + }; try_emit_worker_ready( &out_tx, &worker_name, @@ -1244,8 +1273,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &mut init_request_id, init_received_at, &mut startup_readiness, - startup_ready, - startup_blocked, + gate, ) .await; @@ -1808,7 +1836,13 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &post_boot_output, &pty, ); - let startup_blocked = startup_gate_blocked(&pty); + let gate = if startup_ready { + StartupGate::Ready + } else if startup_gate_blocked(&pty) { + StartupGate::Blocked + } else { + StartupGate::Unrecognised + }; try_emit_worker_ready( &out_tx, &worker_name, @@ -1816,8 +1850,7 @@ pub(crate) async fn run_pty_worker(cmd: PtyCommand) -> Result<()> { &mut init_request_id, init_received_at, &mut startup_readiness, - startup_ready, - startup_blocked, + gate, ) .await; @@ -2231,8 +2264,7 @@ mod tests { &mut request_id, Some(started), &mut readiness, - false, // prompt never recognised - false, // and no blocking dialog on screen + StartupGate::Unrecognised, ) .await; @@ -2265,8 +2297,7 @@ mod tests { &mut request_id, Some(started), &mut readiness, - false, // prompt not ready - true, // ...because a known blocking dialog is on screen + StartupGate::Blocked, ) .await; @@ -2294,8 +2325,7 @@ mod tests { &mut request_id, Some(started), &mut readiness, - false, - false, + StartupGate::Unrecognised, ) .await; @@ -2316,8 +2346,7 @@ mod tests { &mut request_id, Some(started), &mut readiness, - true, - false, + StartupGate::Ready, ) .await;