Skip to main content

liminal_server/health/
endpoint.rs

1use std::io::{Read, Write};
2use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
3use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4use std::sync::{Arc, Mutex, PoisonError};
5use std::thread::{self, JoinHandle};
6use std::time::Duration;
7
8use crate::ServerError;
9use crate::server::listener::{loopback_interrupt_target, shed_on_fd_exhaustion};
10
11use super::checks::{SharedReadinessState, health_check, readiness_check};
12use super::reissue::{
13    OperatorCredentialReissueOutcome, OperatorCredentialReissueRefusal,
14    OperatorCredentialReissueRequest, OperatorCredentialReissuer, SharedOperatorCredentialReissue,
15};
16use super::unloadable::{SharedUnloadableConversations, UnloadableConversationRecord};
17
18use super::metrics_route;
19
20const HEALTH_PATH: &str = "/health";
21const READY_PATH: &str = "/ready";
22const METRICS_PATH: &str = "/metrics";
23const UNLOADABLE_PATH: &str = "/unloadable-conversations";
24/// R18 amendment A7 (§0.18): the operator credential re-issue operation.
25const REISSUE_PATH: &str = "/operator/credential-reissue";
26const APPLICATION_JSON: &str = "application/json";
27const READ_BUFFER_BYTES: usize = 2048;
28
29/// Handle for a running health endpoint server.
30///
31/// W4 leg 2 (§4.2): the health accept worker BLOCKS in `accept` (kernel-parked,
32/// zero idle wakes) rather than spinning a non-blocking poll with a backoff
33/// sleep. Shutdown wakes the blocked `accept` with an explicit self-connect
34/// interrupt to `interrupt_target` (the bound address, loopback-normalised),
35/// mirroring the leg-1 listeners. Shutdown also interrupts an in-flight request
36/// read directly via `active_stream`, so a silent client parked in
37/// `handle_connection`'s read cannot defer shutdown by its admitted deadline.
38#[derive(Debug)]
39pub struct HealthServerHandle {
40    local_addr: SocketAddr,
41    /// Loopback-normalised self-connect target used to interrupt the blocking
42    /// `accept` at shutdown.
43    interrupt_target: SocketAddr,
44    shutdown: Arc<AtomicBool>,
45    /// Slot holding a `try_clone` of the request stream the worker is currently
46    /// reading, if any. The worker registers it under the lock (with a shutdown
47    /// recheck) before blocking on the request read and clears it on completion;
48    /// `stop_worker` takes it and `shutdown(Both)`s it to interrupt an in-flight
49    /// blocking read directly (TOLD) rather than waiting out the read's admitted
50    /// deadline. At most one stream exists at a time (the worker is serial), so
51    /// one slot suffices.
52    active_stream: Arc<Mutex<Option<TcpStream>>>,
53    /// The operator read surface for refused conversation loads, shared with
54    /// the worker. The health server binds before any participant handler
55    /// exists, so this starts empty and the participant's record is published
56    /// into it by [`Self::install_unloadable_record`] once built. Pull-only:
57    /// nothing reads it until a request arrives, so it cannot wake the worker.
58    unloadable: SharedUnloadableConversations,
59    /// The operator WRITE surface for A7 credential re-issue, shared with the
60    /// worker. Empty until the participant is built, exactly like `unloadable`
61    /// above and for the same reason. Call-driven only: nothing reads it until
62    /// a request arrives, so it cannot wake the worker.
63    reissue: SharedOperatorCredentialReissue,
64    worker: Option<JoinHandle<Result<(), ServerError>>>,
65    /// Count of `accept` calls issued by the worker (test observability for the
66    /// zero-idle-wakes oracle: on a silent listener this stays at the single
67    /// parked call). The worker always maintains the counter; only the host-side
68    /// handle for reading it is test-scoped.
69    #[cfg(test)]
70    accept_attempts: Arc<AtomicU64>,
71    /// Count of connections shed under fd exhaustion via the reserve descriptor
72    /// (test observability for the shed helper reused from leg 1).
73    #[cfg(test)]
74    shed_count: Arc<AtomicU64>,
75    /// Count of accepted requests the worker has entered (incremented just
76    /// before it blocks reading the request). Test observability so a race can
77    /// deterministically catch the worker mid-request rather than parked in
78    /// `accept`.
79    #[cfg(test)]
80    requests_entered: Arc<AtomicU64>,
81}
82
83impl HealthServerHandle {
84    /// Returns the bound address for the health endpoint server.
85    #[must_use]
86    pub const fn local_addr(&self) -> SocketAddr {
87        self.local_addr
88    }
89
90    /// Publishes a participant's unloadable-conversation record onto
91    /// `GET /unloadable-conversations`.
92    ///
93    /// Server startup binds the health endpoint FIRST — liveness has to be
94    /// answerable while the rest of the server is still being built — so the
95    /// record cannot be a constructor argument. Until this is called the route
96    /// answers `participant_installed: false`, which is a different answer from
97    /// "nothing is refused" and is reported as such.
98    ///
99    /// Installing shares the handler's live record rather than copying it: a
100    /// refusal recorded after this call is reported by the next scrape. No
101    /// thread, timer, or notification is created here.
102    pub fn install_unloadable_record(&self, record: UnloadableConversationRecord) {
103        self.unloadable.install(record);
104    }
105
106    /// Publishes the participant authority onto
107    /// `POST /operator/credential-reissue` (R18 amendment A7, §0.18).
108    ///
109    /// Same timing constraint as [`Self::install_unloadable_record`]: the
110    /// health endpoint binds before the participant exists, so the authority
111    /// arrives here once built. Until it does the route answers 503 and says
112    /// no participant is installed, which is a different answer from an
113    /// unknown identity and is reported as such. No thread, timer, or
114    /// notification is created here.
115    pub fn install_credential_reissuer(&self, reissuer: Arc<dyn OperatorCredentialReissuer>) {
116        self.reissue.install(reissuer);
117    }
118
119    /// Stops the health endpoint server and waits for its worker thread to exit.
120    ///
121    /// # Errors
122    ///
123    /// Returns [`ServerError::HealthEndpoint`] if the worker thread cannot be
124    /// joined cleanly or if the server loop recorded a serving error.
125    pub fn shutdown(mut self) -> Result<(), ServerError> {
126        self.stop_worker()
127    }
128
129    fn stop_worker(&mut self) -> Result<(), ServerError> {
130        self.shutdown.store(true, Ordering::SeqCst);
131        let Some(worker) = self.worker.take() else {
132            return Ok(());
133        };
134        // Interrupt an in-flight request read directly (TOLD): the flag store
135        // above happens-before this lock acquire, so any stream the worker
136        // registered before observing the flag is taken here and shut down,
137        // waking its blocked read at once. `shutdown(Both)` on the clone reaches
138        // the same underlying socket the worker is reading (a dup'd fd). If the
139        // worker is instead parked in `accept`, the slot is empty and the
140        // self-connect below wakes it. The guard is released (statement end)
141        // before the socket call, so no lock is held across `shutdown`.
142        let in_flight = self
143            .active_stream
144            .lock()
145            .unwrap_or_else(PoisonError::into_inner)
146            .take();
147        if let Some(stream) = in_flight {
148            let _ = stream.shutdown(Shutdown::Both);
149        }
150        // Explicit cross-platform interrupt (mirrors the leg-1 listeners): a
151        // single self-connect wakes a blocked `accept`. The worker sees the
152        // shutdown flag it observed under the same ordering and sheds the woken
153        // (spurious) socket rather than serving it — at most one spurious accept
154        // per interrupt. If the worker already exited (a real accept then flag
155        // check), the listener is gone and this connect fails fast; the join then
156        // returns immediately.
157        if let Ok(waker) = TcpStream::connect(self.interrupt_target) {
158            drop(waker);
159        }
160
161        worker.join().map_err(|_| ServerError::HealthEndpoint {
162            message: "health endpoint worker thread terminated unexpectedly".to_owned(),
163        })?
164    }
165
166    /// `accept` calls issued by the worker (test observability, oracle 8).
167    #[cfg(test)]
168    fn accept_attempts(&self) -> u64 {
169        self.accept_attempts.load(Ordering::SeqCst)
170    }
171
172    /// Connections shed under fd exhaustion (test observability).
173    #[cfg(test)]
174    fn shed_count(&self) -> u64 {
175        self.shed_count.load(Ordering::SeqCst)
176    }
177
178    /// Accepted requests the worker has entered (test observability).
179    #[cfg(test)]
180    fn requests_entered(&self) -> u64 {
181        self.requests_entered.load(Ordering::SeqCst)
182    }
183}
184
185impl Drop for HealthServerHandle {
186    fn drop(&mut self) {
187        if let Err(error) = self.stop_worker() {
188            tracing::debug!(%error, "health endpoint shutdown during drop failed");
189        }
190    }
191}
192
193/// Starts the health endpoint HTTP server on a distinct health bind address.
194///
195/// The returned server handle is independent from the main wire protocol
196/// listener. Binding the health endpoint does not mark the main listener ready.
197///
198/// # Errors
199///
200/// Returns [`ServerError::HealthEndpoint`] when the health listener cannot bind
201/// or cannot report its local address. The listener stays BLOCKING (W4 leg 2):
202/// the accept worker kernel-parks in `accept` with zero idle wakes; shutdown
203/// wakes it via the self-connect interrupt.
204pub fn start_health_server(
205    bind_address: SocketAddr,
206    readiness: SharedReadinessState,
207) -> Result<HealthServerHandle, ServerError> {
208    let listener =
209        TcpListener::bind(bind_address).map_err(|error| ServerError::HealthEndpoint {
210            message: format!("failed to bind health endpoint at {bind_address}: {error}"),
211        })?;
212    // The listener stays BLOCKING (W4 leg 2): the accept worker kernel-parks in
213    // `accept` with zero idle wakes; shutdown wakes it via the self-connect
214    // interrupt below.
215    let local_addr = listener
216        .local_addr()
217        .map_err(|error| ServerError::HealthEndpoint {
218            message: format!("failed to inspect health endpoint listener address: {error}"),
219        })?;
220    let interrupt_target = loopback_interrupt_target(local_addr);
221    let shutdown = Arc::new(AtomicBool::new(false));
222    let active_stream = Arc::new(Mutex::new(None));
223    let unloadable = SharedUnloadableConversations::default();
224    let reissue = SharedOperatorCredentialReissue::default();
225    let accept_attempts = Arc::new(AtomicU64::new(0));
226    let shed_count = Arc::new(AtomicU64::new(0));
227    let requests_entered = Arc::new(AtomicU64::new(0));
228    let worker_shutdown = Arc::clone(&shutdown);
229    let worker_stream = Arc::clone(&active_stream);
230    let worker_attempts = Arc::clone(&accept_attempts);
231    let worker_shed = Arc::clone(&shed_count);
232    let worker_entered = Arc::clone(&requests_entered);
233    let served = ServedState {
234        readiness,
235        unloadable: unloadable.clone(),
236        reissue: reissue.clone(),
237    };
238    let worker = thread::spawn(move || {
239        serve(
240            &listener,
241            &served,
242            &worker_shutdown,
243            &worker_stream,
244            &worker_attempts,
245            &worker_shed,
246            &worker_entered,
247        )
248    });
249
250    Ok(HealthServerHandle {
251        local_addr,
252        interrupt_target,
253        shutdown,
254        active_stream,
255        unloadable,
256        reissue,
257        worker: Some(worker),
258        #[cfg(test)]
259        accept_attempts,
260        #[cfg(test)]
261        shed_count,
262        #[cfg(test)]
263        requests_entered,
264    })
265}
266
267/// Everything a request is answered from, carried as one value so the worker's
268/// parameter list does not grow a slot per operator surface.
269#[derive(Debug, Clone)]
270struct ServedState {
271    readiness: SharedReadinessState,
272    unloadable: SharedUnloadableConversations,
273    reissue: SharedOperatorCredentialReissue,
274}
275
276fn serve(
277    listener: &TcpListener,
278    served: &ServedState,
279    shutdown: &AtomicBool,
280    active_stream: &Mutex<Option<TcpStream>>,
281    accept_attempts: &AtomicU64,
282    shed_count: &AtomicU64,
283    requests_entered: &AtomicU64,
284) -> Result<(), ServerError> {
285    // One reserve descriptor held for the shed-with-spare-fd EMFILE policy,
286    // reusing the leg-1 helper.
287    let mut reserve = listener.try_clone().ok();
288    while !shutdown.load(Ordering::SeqCst) {
289        accept_attempts.fetch_add(1, Ordering::SeqCst);
290        match listener.accept() {
291            Ok((stream, ..)) => {
292                // Register the in-flight stream and re-check shutdown atomically
293                // under the slot lock. If shutdown already fired, shed without
294                // serving (no request slips past the broadcast); otherwise the
295                // registered clone lets `stop_worker` interrupt this request's
296                // blocking read directly (TOLD). Pairing the flag load here with
297                // `stop_worker`'s flag-store-then-lock means a registration can
298                // never be missed: either the worker sees the flag and sheds, or
299                // shutdown finds the clone in the slot.
300                let admitted = {
301                    let mut slot = active_stream.lock().unwrap_or_else(PoisonError::into_inner);
302                    if shutdown.load(Ordering::SeqCst) {
303                        false
304                    } else {
305                        *slot = stream.try_clone().ok();
306                        true
307                    }
308                };
309                if !admitted {
310                    drop(stream);
311                    continue;
312                }
313                requests_entered.fetch_add(1, Ordering::SeqCst);
314                // A per-connection error (e.g. a TCP probe that connects but sends no HTTP
315                // data within the read timeout) must NOT terminate the serve loop — otherwise
316                // a single port probe kills the health server for the process lifetime and
317                // subsequent liveness/readiness probes get connection-refused. Only fatal
318                // listener-level accept errors (below) terminate serving.
319                let result = handle_connection(stream, served);
320                // The request is done: clear the slot so a later shutdown finds
321                // nothing to interrupt (and never shuts down an unrelated stream).
322                // A no-op if `stop_worker` already took the clone.
323                *active_stream.lock().unwrap_or_else(PoisonError::into_inner) = None;
324                if let Err(error) = result {
325                    tracing::debug!(%error, "health endpoint connection error");
326                }
327            }
328            Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
329            Err(error) if is_transient_accept_error(&error) => {
330                shed_on_fd_exhaustion(listener, &mut reserve, shed_count, &error);
331            }
332            Err(error) => {
333                return Err(ServerError::HealthEndpoint {
334                    message: format!("health endpoint accept failed: {error}"),
335                });
336            }
337        }
338    }
339
340    Ok(())
341}
342
343/// EMFILE/ENFILE resource exhaustion is transient, exactly as the leg-1 accept
344/// loops treat it (mirrors the sibling WebSocket listener's local predicate).
345fn is_transient_accept_error(error: &std::io::Error) -> bool {
346    matches!(error.raw_os_error(), Some(code) if code == 24 || code == 23)
347}
348
349fn handle_connection(mut stream: TcpStream, served: &ServedState) -> Result<(), ServerError> {
350    stream
351        .set_nonblocking(false)
352        .map_err(|error| ServerError::HealthEndpoint {
353            message: format!("failed to configure health request stream: {error}"),
354        })?;
355    stream
356        .set_read_timeout(Some(Duration::from_secs(2)))
357        .map_err(|error| ServerError::HealthEndpoint {
358            message: format!("failed to set health request read timeout: {error}"),
359        })?;
360
361    let mut buffer = [0_u8; READ_BUFFER_BYTES];
362    let bytes_read = stream
363        .read(&mut buffer)
364        .map_err(|error| ServerError::HealthEndpoint {
365            message: format!("failed to read health request: {error}"),
366        })?;
367
368    if bytes_read == 0 {
369        return Ok(());
370    }
371
372    let response = response_for_request(&buffer[..bytes_read], served)?;
373    stream
374        .write_all(&response)
375        .map_err(|error| ServerError::HealthEndpoint {
376            message: format!("failed to write health response: {error}"),
377        })?;
378    stream.flush().map_err(|error| ServerError::HealthEndpoint {
379        message: format!("failed to flush health response: {error}"),
380    })
381}
382
383fn response_for_request(request: &[u8], served: &ServedState) -> Result<Vec<u8>, ServerError> {
384    let Ok(request) = std::str::from_utf8(request) else {
385        return Ok(empty_response(StatusCode::BadRequest));
386    };
387    let Some((method, path)) = parse_request_line(request) else {
388        return Ok(empty_response(StatusCode::BadRequest));
389    };
390
391    match (method, path) {
392        ("GET", HEALTH_PATH) => json_response(StatusCode::Ok, &health_check()),
393        ("GET", READY_PATH) => {
394            let status = readiness_check(&served.readiness.snapshot());
395            let status_code = if status.ready {
396                StatusCode::Ok
397            } else {
398                StatusCode::ServiceUnavailable
399            };
400            json_response(status_code, &status)
401        }
402        ("GET", METRICS_PATH) => Ok(response(
403            StatusCode::Ok,
404            Some(metrics_route::CONTENT_TYPE),
405            metrics_route::render_body().as_bytes(),
406        )),
407        // The refused-load surface. Reading it is a snapshot of a record
408        // another part of the server already maintains — this route computes
409        // nothing, schedules nothing, and touches no conversation.
410        ("GET", UNLOADABLE_PATH) => json_response(StatusCode::Ok, &served.unloadable.status()),
411        (_, HEALTH_PATH | READY_PATH | METRICS_PATH | UNLOADABLE_PATH) => {
412            Ok(empty_response(StatusCode::MethodNotAllowed))
413        }
414        // R18 amendment A7. Matched on the path with its query string SPLIT
415        // OFF, and only here: the four routes above keep matching the whole
416        // request target exactly as they always have, so a query string on
417        // `/health` is still a 404 and no existing route's answer moves.
418        _ if path.split('?').next() == Some(REISSUE_PATH) => {
419            credential_reissue_response(method, path, served)
420        }
421        _ => Ok(empty_response(StatusCode::NotFound)),
422    }
423}
424
425/// Answers one `OperatorCredentialReissue` call (R18 amendment A7, §0.18).
426///
427/// # Why the inputs ride the query string
428///
429/// The three inputs are fixed by §0.18 (`conversation_id`, `participant_id`,
430/// `expected_current_generation`) and none of them is a secret — the SECRET
431/// travels only in the response. The request shape is a build decision, and a
432/// query string is chosen because this endpoint reads each request with ONE
433/// bounded read and no message framing: a body would be present only when the
434/// client happened to put it in the same segment, so parsing one would make
435/// the route's answer depend on TCP segmentation. Teaching the shared request
436/// reader to frame bodies would change the serving discipline of every route
437/// here, which is not this lane's to do.
438fn credential_reissue_response(
439    method: &str,
440    path: &str,
441    served: &ServedState,
442) -> Result<Vec<u8>, ServerError> {
443    if method != "POST" {
444        return Ok(empty_response(StatusCode::MethodNotAllowed));
445    }
446    let query = path.split_once('?').map_or("", |(_, query)| query);
447    let Some(request) = parse_reissue_query(query) else {
448        return Ok(empty_response(StatusCode::BadRequest));
449    };
450    match served.reissue.reissue(request) {
451        // No participant is configured on this node. Distinct from every
452        // identity answer, and reported as such rather than as a lookup miss.
453        Ok(None) => Ok(empty_response(StatusCode::ServiceUnavailable)),
454        Ok(Some(OperatorCredentialReissueOutcome::Issued(issued))) => {
455            json_response(StatusCode::Ok, &issued)
456        }
457        Ok(Some(OperatorCredentialReissueOutcome::Refused(refusal))) => {
458            json_response(reissue_refusal_status(&refusal), &refusal)
459        }
460        Err(error) => {
461            // The operation could not be DECIDED. The operator is told, and
462            // the text is the refusal's own — never a bare close.
463            tracing::error!(%error, "operator credential re-issue could not be decided");
464            json_response(
465                StatusCode::ServiceUnavailable,
466                &serde_json::json!({ "error": error.message }),
467            )
468        }
469    }
470}
471
472/// The status each typed refusal is served with.
473///
474/// A lookup miss is a 404 and every guard refusal is a 409: the identity
475/// resolved, and the operation was refused by a live fact about it. The
476/// discriminator an operator branches on is the body's `refusal` field, never
477/// the status — the status is the HTTP-shaped summary of it.
478const fn reissue_refusal_status(refusal: &OperatorCredentialReissueRefusal) -> StatusCode {
479    match refusal {
480        OperatorCredentialReissueRefusal::ConversationUnknown { .. }
481        | OperatorCredentialReissueRefusal::ParticipantUnknown { .. } => StatusCode::NotFound,
482        OperatorCredentialReissueRefusal::Retired { .. }
483        | OperatorCredentialReissueRefusal::LiveBinding { .. }
484        | OperatorCredentialReissueRefusal::DetachReplayOpen { .. }
485        | OperatorCredentialReissueRefusal::LiveReceipt { .. }
486        | OperatorCredentialReissueRefusal::GenerationMismatch { .. } => StatusCode::Conflict,
487    }
488}
489
490/// Parses the three §0.18 inputs, or answers `None` for anything else.
491///
492/// Every field is mandatory and every value is a plain decimal `u64`. An
493/// unknown parameter, a repeat, or a missing one is refused rather than
494/// defaulted: an operator who mistypes `expected_current_generation` must not
495/// have a zero silently substituted for it.
496fn parse_reissue_query(query: &str) -> Option<OperatorCredentialReissueRequest> {
497    let mut conversation_id = None;
498    let mut participant_id = None;
499    let mut expected_current_generation = None;
500    for pair in query.split('&') {
501        let (name, value) = pair.split_once('=')?;
502        let value = value.parse::<u64>().ok()?;
503        let slot = match name {
504            "conversation_id" => &mut conversation_id,
505            "participant_id" => &mut participant_id,
506            "expected_current_generation" => &mut expected_current_generation,
507            _ => return None,
508        };
509        if slot.replace(value).is_some() {
510            return None;
511        }
512    }
513    Some(OperatorCredentialReissueRequest {
514        conversation_id: conversation_id?,
515        participant_id: participant_id?,
516        expected_current_generation: expected_current_generation?,
517    })
518}
519
520fn parse_request_line(request: &str) -> Option<(&str, &str)> {
521    let request_line = request.lines().next()?;
522    let mut parts = request_line.split_whitespace();
523    let method = parts.next()?;
524    let path = parts.next()?;
525    parts.next()?;
526
527    Some((method, path))
528}
529
530fn json_response<T>(status: StatusCode, value: &T) -> Result<Vec<u8>, ServerError>
531where
532    T: serde::Serialize,
533{
534    let body = serde_json::to_vec(value).map_err(|error| ServerError::HealthEndpoint {
535        message: format!("failed to serialize health response: {error}"),
536    })?;
537    Ok(response(status, Some(APPLICATION_JSON), &body))
538}
539
540fn empty_response(status: StatusCode) -> Vec<u8> {
541    response(status, None, &[])
542}
543
544fn response(status: StatusCode, content_type: Option<&str>, body: &[u8]) -> Vec<u8> {
545    let mut response = Vec::new();
546    let status_line = format!("HTTP/1.1 {} {}\r\n", status.code(), status.reason());
547    response.extend_from_slice(status_line.as_bytes());
548    response.extend_from_slice(format!("Content-Length: {}\r\n", body.len()).as_bytes());
549    response.extend_from_slice(b"Connection: close\r\n");
550    if let Some(content_type) = content_type {
551        response.extend_from_slice(format!("Content-Type: {content_type}\r\n").as_bytes());
552    }
553    response.extend_from_slice(b"\r\n");
554    response.extend_from_slice(body);
555    response
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq)]
559enum StatusCode {
560    Ok,
561    BadRequest,
562    NotFound,
563    MethodNotAllowed,
564    Conflict,
565    ServiceUnavailable,
566}
567
568impl StatusCode {
569    const fn code(self) -> u16 {
570        match self {
571            Self::Ok => 200,
572            Self::BadRequest => 400,
573            Self::NotFound => 404,
574            Self::MethodNotAllowed => 405,
575            Self::Conflict => 409,
576            Self::ServiceUnavailable => 503,
577        }
578    }
579
580    const fn reason(self) -> &'static str {
581        match self {
582            Self::Ok => "OK",
583            Self::BadRequest => "Bad Request",
584            Self::NotFound => "Not Found",
585            Self::MethodNotAllowed => "Method Not Allowed",
586            Self::Conflict => "Conflict",
587            Self::ServiceUnavailable => "Service Unavailable",
588        }
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use std::io::{Read, Write};
595    use std::net::{SocketAddr, TcpStream};
596    use std::sync::{Arc, Mutex, PoisonError};
597    use std::thread;
598    use std::time::{Duration, Instant};
599
600    use serde_json::Value;
601
602    use super::{
603        OperatorCredentialReissueRefusal, OperatorCredentialReissueRequest,
604        OperatorCredentialReissuer, ServedState, response_for_request, start_health_server,
605    };
606    use crate::health::checks::{
607        ClusterReadiness, ReadinessCondition, ReadinessState, SharedReadinessState,
608    };
609
610    fn loopback_ephemeral() -> Result<SocketAddr, Box<dyn std::error::Error>> {
611        Ok("127.0.0.1:0".parse()?)
612    }
613
614    /// The request-level tests answer from readiness alone; the refused-load
615    /// surface stays uninstalled, which is the state a server is in before its
616    /// participant handler exists.
617    fn served(readiness: SharedReadinessState) -> ServedState {
618        ServedState {
619            readiness,
620            unloadable: crate::health::unloadable::SharedUnloadableConversations::default(),
621            reissue: crate::health::reissue::SharedOperatorCredentialReissue::default(),
622        }
623    }
624
625    fn get(address: SocketAddr, path: &str) -> Result<String, Box<dyn std::error::Error>> {
626        let mut stream = TcpStream::connect(address)?;
627        stream.set_read_timeout(Some(Duration::from_secs(2)))?;
628        let request = format!("GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n");
629        stream.write_all(request.as_bytes())?;
630
631        let mut response = String::new();
632        stream.read_to_string(&mut response)?;
633        Ok(response)
634    }
635
636    fn assert_status(response: &str, status: u16) {
637        let expected = format!("HTTP/1.1 {status} ");
638        assert!(
639            response.starts_with(&expected),
640            "response status did not start with {expected}: {response}"
641        );
642    }
643
644    fn body(response: &str) -> Result<&str, Box<dyn std::error::Error>> {
645        let Some((_headers, body)) = response.split_once("\r\n\r\n") else {
646            return Err("response did not contain a header/body separator".into());
647        };
648        Ok(body)
649    }
650
651    fn json_body(response: &str) -> Result<Value, Box<dyn std::error::Error>> {
652        Ok(serde_json::from_str(body(response)?)?)
653    }
654
655    #[test]
656    fn health_endpoint_returns_json_200_regardless_of_readiness()
657    -> Result<(), Box<dyn std::error::Error>> {
658        let readiness = SharedReadinessState::new(ReadinessState::default());
659        let server = start_health_server(loopback_ephemeral()?, readiness)?;
660
661        let response = get(server.local_addr(), "/health")?;
662        server.shutdown()?;
663
664        assert_status(&response, 200);
665        assert!(response.contains("Content-Type: application/json\r\n"));
666        let body = json_body(&response)?;
667        assert_eq!(body["status"], "healthy");
668
669        Ok(())
670    }
671
672    #[test]
673    fn ready_endpoint_returns_503_before_main_listener_binds()
674    -> Result<(), Box<dyn std::error::Error>> {
675        let readiness = SharedReadinessState::new(ReadinessState::new(
676            true,
677            false,
678            ClusterReadiness::NotConfigured,
679        ));
680        let server = start_health_server(loopback_ephemeral()?, readiness)?;
681
682        let response = get(server.local_addr(), "/ready")?;
683        server.shutdown()?;
684
685        assert_status(&response, 503);
686        assert!(response.contains("Content-Type: application/json\r\n"));
687        let body = json_body(&response)?;
688        assert_eq!(body["ready"], false);
689        assert_eq!(body["unmet_conditions"][0], "listener_bound");
690
691        Ok(())
692    }
693
694    #[test]
695    fn ready_endpoint_returns_200_after_all_startup_gates() -> Result<(), Box<dyn std::error::Error>>
696    {
697        let readiness = SharedReadinessState::new(ReadinessState::ready_without_cluster());
698        let server = start_health_server(loopback_ephemeral()?, readiness)?;
699
700        let response = get(server.local_addr(), "/ready")?;
701        server.shutdown()?;
702
703        assert_status(&response, 200);
704        let body = json_body(&response)?;
705        assert_eq!(body["ready"], true);
706        let Some(unmet_conditions) = body["unmet_conditions"].as_array() else {
707            return Err("unmet_conditions should be an array".into());
708        };
709        assert!(unmet_conditions.is_empty());
710
711        Ok(())
712    }
713
714    #[test]
715    fn ready_endpoint_updates_from_shared_readiness_state() -> Result<(), Box<dyn std::error::Error>>
716    {
717        let readiness = SharedReadinessState::new(ReadinessState::default());
718        let server = start_health_server(loopback_ephemeral()?, readiness.clone())?;
719
720        let response = get(server.local_addr(), "/ready")?;
721        assert_status(&response, 503);
722
723        readiness.set_config_loaded(true);
724        readiness.set_listener_bound(true);
725        let response = get(server.local_addr(), "/ready")?;
726        server.shutdown()?;
727
728        assert_status(&response, 200);
729
730        Ok(())
731    }
732
733    #[test]
734    fn clustered_ready_transitions_503_to_200_when_membership_established()
735    -> Result<(), Box<dyn std::error::Error>> {
736        // A clustered server starts with the cluster gate unmet: config loaded and
737        // listener bound, but membership not yet established (G2). /ready is 503.
738        let readiness = SharedReadinessState::new(ReadinessState::new(
739            true,
740            true,
741            ClusterReadiness::Configured {
742                membership_established: false,
743            },
744        ));
745        let server = start_health_server(loopback_ephemeral()?, readiness.clone())?;
746
747        let response = get(server.local_addr(), "/ready")?;
748        assert_status(&response, 503);
749        let body = json_body(&response)?;
750        assert_eq!(body["ready"], false);
751        assert_eq!(
752            body["unmet_conditions"][0],
753            serde_json::to_value(ReadinessCondition::ClusterMembershipEstablished)?
754        );
755
756        // The cluster start's on_established hook flips exactly this flag; once set,
757        // /ready must transition to 200 with no unmet conditions.
758        readiness.set_cluster_membership_established(true);
759        let response = get(server.local_addr(), "/ready")?;
760        server.shutdown()?;
761
762        assert_status(&response, 200);
763        let body = json_body(&response)?;
764        assert_eq!(body["ready"], true);
765        let Some(unmet_conditions) = body["unmet_conditions"].as_array() else {
766            return Err("unmet_conditions should be an array".into());
767        };
768        assert!(unmet_conditions.is_empty());
769
770        Ok(())
771    }
772
773    #[test]
774    fn cluster_readiness_is_listed_when_configured_but_not_joined()
775    -> Result<(), Box<dyn std::error::Error>> {
776        let readiness = SharedReadinessState::new(ReadinessState::new(
777            true,
778            true,
779            ClusterReadiness::Configured {
780                membership_established: false,
781            },
782        ));
783        let response = response_for_request(b"GET /ready HTTP/1.1\r\n\r\n", &served(readiness))?;
784        let response = String::from_utf8(response)?;
785
786        assert_status(&response, 503);
787        let body = json_body(&response)?;
788        assert_eq!(
789            body["unmet_conditions"][0],
790            serde_json::to_value(ReadinessCondition::ClusterMembershipEstablished)?
791        );
792
793        Ok(())
794    }
795
796    /// THE UNLOADABLE-CONVERSATIONS READ SURFACE, RED UNTIL THE ROUTE EXISTS.
797    ///
798    /// The node already records every conversation it refused to load
799    /// (`server/participant/production/handler.rs`, `record_unloadable`) and
800    /// then offers an operator no way to read it back: the accessor has no
801    /// caller. Containment without a read surface is a node that serves nothing
802    /// on one conversation forever and answers no question about it.
803    ///
804    /// This pin fixes the shape of the answer and nothing else — that the route
805    /// EXISTS, answers JSON 200, and carries the three fields an operator reads:
806    /// how many conversations are refused, which ones, and whether a participant
807    /// record is even attached (so a `count` of zero is never confused with a
808    /// node that has no participant installed at all).
809    ///
810    /// The unknown-path arm in the same test is the discriminator: without it a
811    /// 200 here would also be satisfied by a server that answers 200 to
812    /// everything.
813    #[test]
814    fn unloadable_conversations_route_answers_the_operator_a_json_shape()
815    -> Result<(), Box<dyn std::error::Error>> {
816        let readiness = SharedReadinessState::new(ReadinessState::default());
817        let server = start_health_server(loopback_ephemeral()?, readiness)?;
818
819        let response = get(server.local_addr(), "/unloadable-conversations")?;
820        let unknown = get(server.local_addr(), "/unloadable-conversations-typo")?;
821        server.shutdown()?;
822
823        // The discriminator first: a neighbouring path is still not served, so
824        // the 200 below is this route's own answer.
825        assert_status(&unknown, 404);
826
827        assert_status(&response, 200);
828        assert!(
829            response.contains("Content-Type: application/json\r\n"),
830            "the unloadable-conversations route must answer JSON: {response}"
831        );
832        let body = json_body(&response)?;
833        assert_eq!(
834            body["count"], 0,
835            "a server with no participant record attached refuses nothing: {body}"
836        );
837        assert_eq!(
838            body["participant_installed"], false,
839            "no participant record is attached to this server, and the surface must say so \
840             rather than let a zero count read as a clean node: {body}"
841        );
842        let Some(conversations) = body["conversations"].as_array() else {
843            return Err("conversations should be an array".into());
844        };
845        assert!(
846            conversations.is_empty(),
847            "no conversation was refused: {conversations:?}"
848        );
849
850        Ok(())
851    }
852
853    #[test]
854    fn unsupported_paths_are_not_served() -> Result<(), Box<dyn std::error::Error>> {
855        let readiness = SharedReadinessState::default();
856        let response = response_for_request(b"GET /unknown HTTP/1.1\r\n\r\n", &served(readiness))?;
857        let response = String::from_utf8(response)?;
858
859        assert_status(&response, 404);
860
861        Ok(())
862    }
863
864    #[test]
865    fn unsupported_methods_on_health_paths_are_rejected() -> Result<(), Box<dyn std::error::Error>>
866    {
867        let readiness = SharedReadinessState::default();
868        let response = response_for_request(b"POST /health HTTP/1.1\r\n\r\n", &served(readiness))?;
869        let response = String::from_utf8(response)?;
870
871        assert_status(&response, 405);
872
873        Ok(())
874    }
875
876    // -----------------------------------------------------------------------
877    // R18 amendment A7 (§0.18) — the operator credential re-issue route
878    // -----------------------------------------------------------------------
879
880    /// A reissuer that answers whatever it was built with, and records the
881    /// request it was handed so the route's parsing can be measured rather than
882    /// assumed.
883    #[derive(Debug)]
884    struct RecordingReissuer {
885        outcome: crate::health::reissue::OperatorCredentialReissueOutcome,
886        seen: Mutex<Vec<OperatorCredentialReissueRequest>>,
887    }
888
889    impl OperatorCredentialReissuer for RecordingReissuer {
890        fn reissue(
891            &self,
892            request: OperatorCredentialReissueRequest,
893        ) -> Result<
894            crate::health::reissue::OperatorCredentialReissueOutcome,
895            crate::health::reissue::OperatorCredentialReissueError,
896        > {
897            self.seen
898                .lock()
899                .unwrap_or_else(PoisonError::into_inner)
900                .push(request);
901            Ok(self.outcome.clone())
902        }
903    }
904
905    fn served_with_reissuer(
906        outcome: crate::health::reissue::OperatorCredentialReissueOutcome,
907    ) -> (ServedState, Arc<RecordingReissuer>) {
908        let reissuer = Arc::new(RecordingReissuer {
909            outcome,
910            seen: Mutex::new(Vec::new()),
911        });
912        let state = served(SharedReadinessState::default());
913        state
914            .reissue
915            .install(Arc::clone(&reissuer) as Arc<dyn OperatorCredentialReissuer>);
916        (state, reissuer)
917    }
918
919    const REISSUE_TARGET: &str =
920        "/operator/credential-reissue?conversation_id=7&participant_id=3&\
921         expected_current_generation=14";
922
923    /// A node with no participant configured says so, rather than answering
924    /// like a node whose identity is unknown.
925    #[test]
926    fn the_reissue_route_reports_an_uninstalled_participant()
927    -> Result<(), Box<dyn std::error::Error>> {
928        let request = format!("POST {REISSUE_TARGET} HTTP/1.1\r\n\r\n");
929        let response =
930            response_for_request(request.as_bytes(), &served(SharedReadinessState::default()))?;
931        let response = String::from_utf8(response)?;
932
933        assert_status(&response, 503);
934        Ok(())
935    }
936
937    /// A committed re-issue serves the secret ONCE, and the three §0.18 inputs
938    /// arrive at the authority exactly as the operator wrote them.
939    #[test]
940    fn the_reissue_route_carries_the_three_inputs_and_returns_the_secret_once()
941    -> Result<(), Box<dyn std::error::Error>> {
942        let issued = crate::health::reissue::OperatorCredentialReissued {
943            conversation_id: 7,
944            participant_id: 3,
945            presented_generation: 14,
946            issued_generation: 15,
947            attach_secret: crate::health::reissue::encode_hex(&[0x5A; 32]),
948        };
949        let (state, reissuer) = served_with_reissuer(
950            crate::health::reissue::OperatorCredentialReissueOutcome::Issued(issued),
951        );
952        let request = format!("POST {REISSUE_TARGET} HTTP/1.1\r\n\r\n");
953
954        let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
955
956        assert_status(&response, 200);
957        let body = json_body(&response)?;
958        assert_eq!(body["issued_generation"], 15);
959        assert_eq!(body["presented_generation"], 14);
960        assert_eq!(body["attach_secret"], "5a".repeat(32));
961        let seen = reissuer
962            .seen
963            .lock()
964            .unwrap_or_else(PoisonError::into_inner)
965            .clone();
966        assert_eq!(
967            seen.as_slice(),
968            [OperatorCredentialReissueRequest {
969                conversation_id: 7,
970                participant_id: 3,
971                expected_current_generation: 14,
972            }]
973        );
974        Ok(())
975    }
976
977    /// The NORMATIVE compare-and-set payload survives the route (§0.18 item 4),
978    /// and a guard refusal is a 409 whose discriminator is a field.
979    #[test]
980    fn the_reissue_route_serves_the_normative_generation_pair()
981    -> Result<(), Box<dyn std::error::Error>> {
982        let (state, _) = served_with_reissuer(
983            crate::health::reissue::OperatorCredentialReissueOutcome::Refused(
984                OperatorCredentialReissueRefusal::GenerationMismatch {
985                    conversation_id: 7,
986                    participant_id: 3,
987                    presented_generation: 14,
988                    current_generation: 15,
989                },
990            ),
991        );
992        let request = format!("POST {REISSUE_TARGET} HTTP/1.1\r\n\r\n");
993
994        let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
995
996        assert_status(&response, 409);
997        let body = json_body(&response)?;
998        assert_eq!(body["refusal"], "generation_mismatch");
999        assert_eq!(body["presented_generation"], 14);
1000        assert_eq!(body["current_generation"], 15);
1001        Ok(())
1002    }
1003
1004    /// A pre-guard lookup miss is a 404, and discloses nothing beyond the
1005    /// identifiers the operator presented.
1006    #[test]
1007    fn the_reissue_route_answers_a_lookup_miss_with_not_found()
1008    -> Result<(), Box<dyn std::error::Error>> {
1009        let (state, _) = served_with_reissuer(
1010            crate::health::reissue::OperatorCredentialReissueOutcome::Refused(
1011                OperatorCredentialReissueRefusal::ConversationUnknown { conversation_id: 7 },
1012            ),
1013        );
1014        let request = format!("POST {REISSUE_TARGET} HTTP/1.1\r\n\r\n");
1015
1016        let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
1017
1018        assert_status(&response, 404);
1019        let body = json_body(&response)?;
1020        assert_eq!(body["refusal"], "conversation_unknown");
1021        assert_eq!(body["conversation_id"], 7);
1022        assert!(
1023            body.get("participant_id").is_none(),
1024            "an unknown-conversation refusal must disclose nothing beyond the presented \
1025             conversation id: {body}"
1026        );
1027        Ok(())
1028    }
1029
1030    /// A malformed call is REFUSED, never defaulted. An operator who mistypes a
1031    /// generation must not have a zero silently substituted for it and a
1032    /// credential rotated on the strength of it.
1033    #[test]
1034    fn a_malformed_reissue_call_is_refused_and_never_reaches_the_authority()
1035    -> Result<(), Box<dyn std::error::Error>> {
1036        let (state, reissuer) = served_with_reissuer(
1037            crate::health::reissue::OperatorCredentialReissueOutcome::Refused(
1038                OperatorCredentialReissueRefusal::ConversationUnknown { conversation_id: 7 },
1039            ),
1040        );
1041        let malformed = [
1042            // A missing input.
1043            "/operator/credential-reissue?conversation_id=7&participant_id=3",
1044            // No inputs at all.
1045            "/operator/credential-reissue",
1046            // A non-numeric generation.
1047            "/operator/credential-reissue?conversation_id=7&participant_id=3&\
1048             expected_current_generation=fourteen",
1049            // An unknown parameter riding along.
1050            "/operator/credential-reissue?conversation_id=7&participant_id=3&\
1051             expected_current_generation=14&force=1",
1052            // A repeated parameter, where the last one silently winning would be
1053            // an operator's typo deciding which identity rotates.
1054            "/operator/credential-reissue?conversation_id=7&conversation_id=8&\
1055             participant_id=3&expected_current_generation=14",
1056        ];
1057
1058        for target in malformed {
1059            let request = format!("POST {target} HTTP/1.1\r\n\r\n");
1060            let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
1061            assert_status(&response, 400);
1062        }
1063
1064        assert!(
1065            reissuer
1066                .seen
1067                .lock()
1068                .unwrap_or_else(PoisonError::into_inner)
1069                .is_empty(),
1070            "a malformed call must never reach the serialized participant-state point"
1071        );
1072        Ok(())
1073    }
1074
1075    /// The operation is a POST. A GET of the same target is refused rather than
1076    /// rotating a credential from a link someone clicked.
1077    #[test]
1078    fn the_reissue_route_refuses_every_other_method() -> Result<(), Box<dyn std::error::Error>> {
1079        let (state, reissuer) = served_with_reissuer(
1080            crate::health::reissue::OperatorCredentialReissueOutcome::Refused(
1081                OperatorCredentialReissueRefusal::ConversationUnknown { conversation_id: 7 },
1082            ),
1083        );
1084
1085        for method in ["GET", "PUT", "DELETE"] {
1086            let request = format!("{method} {REISSUE_TARGET} HTTP/1.1\r\n\r\n");
1087            let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
1088            assert_status(&response, 405);
1089        }
1090
1091        assert!(
1092            reissuer
1093                .seen
1094                .lock()
1095                .unwrap_or_else(PoisonError::into_inner)
1096                .is_empty()
1097        );
1098        Ok(())
1099    }
1100
1101    /// ⛔ The query-string split must not have moved any EXISTING route's
1102    /// answer. `GET /health?x=1` was a 404 before A7 and stays one: the four
1103    /// original routes still match the whole request target exactly.
1104    #[test]
1105    fn the_reissue_route_did_not_move_any_existing_routes_answer()
1106    -> Result<(), Box<dyn std::error::Error>> {
1107        let state = served(SharedReadinessState::default());
1108        for target in [
1109            "/health?x=1",
1110            "/ready?x=1",
1111            "/metrics?x=1",
1112            "/unloadable-conversations?x=1",
1113        ] {
1114            let request = format!("GET {target} HTTP/1.1\r\n\r\n");
1115            let response = String::from_utf8(response_for_request(request.as_bytes(), &state)?)?;
1116            assert_status(&response, 404);
1117        }
1118        // POSITIVE CONTROL: the same routes without a query string still serve,
1119        // so the assertions above measure the query handling and not a broken
1120        // request line.
1121        let response =
1122            String::from_utf8(response_for_request(b"GET /health HTTP/1.1\r\n\r\n", &state)?)?;
1123        assert_status(&response, 200);
1124        Ok(())
1125    }
1126
1127    /// Oracle 8 (W4 leg 2) — on a quiet health listener the blocking accept is
1128    /// issued exactly once (the parked call) and never again: zero repeated
1129    /// accepts, zero application wakes after arming, with route behaviour
1130    /// unchanged (a real request is still served afterwards).
1131    #[test]
1132    fn silent_health_listener_has_zero_application_wakes() -> Result<(), Box<dyn std::error::Error>>
1133    {
1134        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1135
1136        let deadline = Instant::now() + Duration::from_secs(2);
1137        while server.accept_attempts() < 1 && Instant::now() < deadline {
1138            thread::sleep(Duration::from_millis(5));
1139        }
1140        let armed = server.accept_attempts();
1141        assert_eq!(
1142            armed, 1,
1143            "the blocking accept is issued exactly once when parked"
1144        );
1145
1146        thread::sleep(Duration::from_millis(200));
1147        assert_eq!(
1148            server.accept_attempts(),
1149            armed,
1150            "a silent health listener must not wake or re-accept"
1151        );
1152        assert_eq!(
1153            server.shed_count(),
1154            0,
1155            "a silent health listener sheds nothing"
1156        );
1157
1158        // Route behaviour unchanged: a real request is still served after silence.
1159        let response = get(server.local_addr(), "/health")?;
1160        assert_status(&response, 200);
1161        let body = json_body(&response)?;
1162        assert_eq!(body["status"], "healthy");
1163
1164        server.shutdown()?;
1165        Ok(())
1166    }
1167
1168    /// Oracle 9 (W4 leg 2) — absence proof over this module's production source:
1169    /// the retired non-blocking flip and its `WouldBlock` + sleep poll must not
1170    /// appear in the health accept path.
1171    #[test]
1172    fn health_accept_source_has_no_wouldblock_sleep_poll() {
1173        const SOURCE: &str = include_str!("endpoint.rs");
1174        let production = SOURCE.split("mod tests").next().unwrap_or(SOURCE);
1175        for forbidden in [
1176            "set_nonblocking(true)",
1177            "ErrorKind::WouldBlock",
1178            "thread::sleep",
1179        ] {
1180            assert!(
1181                !production.contains(forbidden),
1182                "retired health accept-path source `{forbidden}` reappeared"
1183            );
1184        }
1185    }
1186
1187    /// Oracle 10 (W4 leg 2) — shutdown interrupts the blocking accept wait at
1188    /// every race point: before the worker arms, after it parks, concurrent with
1189    /// a pending connection, and after an accept returns. Each shutdown returns
1190    /// promptly (no sleep-poll) and joins cleanly (no worker leak); the released
1191    /// listener refuses further connects (no descriptor leak).
1192    #[test]
1193    fn health_shutdown_interrupts_accept_wait() -> Result<(), Box<dyn std::error::Error>> {
1194        // (a) shutdown immediately after start — possibly before the worker arms.
1195        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1196        let start = Instant::now();
1197        server.shutdown()?;
1198        assert!(
1199            start.elapsed() < Duration::from_secs(2),
1200            "shutdown before arming must interrupt promptly, not sleep-poll"
1201        );
1202
1203        // (b) shutdown after the worker has parked the blocking accept.
1204        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1205        let deadline = Instant::now() + Duration::from_secs(2);
1206        while server.accept_attempts() < 1 && Instant::now() < deadline {
1207            thread::sleep(Duration::from_millis(5));
1208        }
1209        assert_eq!(
1210            server.accept_attempts(),
1211            1,
1212            "the worker parked before shutdown"
1213        );
1214        let parked_addr = server.local_addr();
1215        let start = Instant::now();
1216        server.shutdown()?;
1217        assert!(
1218            start.elapsed() < Duration::from_secs(2),
1219            "shutdown of a parked accept must interrupt promptly"
1220        );
1221        // No descriptor leak: the released listener refuses further connects.
1222        assert!(
1223            TcpStream::connect(parked_addr).is_err(),
1224            "the listener descriptor was released; further connects are refused"
1225        );
1226
1227        // (c) shutdown concurrent with accept readiness: a client is pending.
1228        // Whether the worker is still parked in `accept` or has already accepted
1229        // and is blocked reading the silent client, shutdown interrupts promptly
1230        // (self-connect for the parked case, in-flight stream shutdown for the
1231        // reading case) — deterministically under the 2s read deadline. The bound
1232        // is tightened to 500 ms to reflect the TOLD interrupt: it must not
1233        // approach the read window that the pre-fix bytes deferred to (see
1234        // `shutdown_interrupts_in_flight_silent_request_read` for the dedicated
1235        // mid-read regression).
1236        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1237        let deadline = Instant::now() + Duration::from_secs(2);
1238        while server.accept_attempts() < 1 && Instant::now() < deadline {
1239            thread::sleep(Duration::from_millis(5));
1240        }
1241        let _pending = TcpStream::connect(server.local_addr())?;
1242        let start = Instant::now();
1243        server.shutdown()?;
1244        assert!(
1245            start.elapsed() < Duration::from_millis(500),
1246            "shutdown concurrent with a pending accept must interrupt promptly (TOLD), \
1247             not defer by a request read deadline: elapsed {:?}",
1248            start.elapsed()
1249        );
1250
1251        // (d) shutdown after an accept returns and a request is served.
1252        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1253        let response = get(server.local_addr(), "/health")?;
1254        assert_status(&response, 200);
1255        let start = Instant::now();
1256        server.shutdown()?;
1257        assert!(
1258            start.elapsed() < Duration::from_secs(2),
1259            "shutdown after a served request must interrupt the next parked accept promptly"
1260        );
1261
1262        Ok(())
1263    }
1264
1265    /// Regression for oracle 10 case (c) (W4 leg 2 — tear-seat BOUNCE): an
1266    /// in-flight SILENT request must not defer shutdown by its read window. A
1267    /// client that connects and sends nothing parks the worker inside
1268    /// `handle_connection`'s blocking read (the admitted 2s slow-client
1269    /// deadline). Shutdown must interrupt that read directly (TOLD stream
1270    /// interrupt), NOT wait for the deadline to expire.
1271    ///
1272    /// The promptness bound is inherently a timing assertion: it is set to
1273    /// 500 ms — comfortably under the 2s read deadline (so the pre-fix bytes,
1274    /// where the self-connect interrupt merely queues behind the blocked read,
1275    /// red deterministically) and comfortably over scheduler-wakeup noise even
1276    /// under full-workspace parallel load (so the post-fix stream interrupt
1277    /// greens deterministically). The worker's entry into the read is observed
1278    /// via the `requests_entered` counter, not a sleep, so the race is caught
1279    /// deterministically mid-request.
1280    #[test]
1281    fn shutdown_interrupts_in_flight_silent_request_read() -> Result<(), Box<dyn std::error::Error>>
1282    {
1283        let server = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1284
1285        // Connect but send NOTHING and keep the socket open: the worker accepts
1286        // and blocks in handle_connection's read on the silent stream. The named
1287        // binding keeps the client alive (a bare `_` would drop it and end the
1288        // read early with EOF).
1289        let _silent = TcpStream::connect(server.local_addr())?;
1290
1291        // Observe the worker has entered the in-flight request read (counter, not
1292        // a sleep) so shutdown is fired while it is genuinely blocked on the read.
1293        let deadline = Instant::now() + Duration::from_secs(2);
1294        while server.requests_entered() < 1 && Instant::now() < deadline {
1295            thread::sleep(Duration::from_millis(5));
1296        }
1297        assert_eq!(
1298            server.requests_entered(),
1299            1,
1300            "the worker entered the in-flight silent request read"
1301        );
1302
1303        let start = Instant::now();
1304        server.shutdown()?;
1305        assert!(
1306            start.elapsed() < Duration::from_millis(500),
1307            "shutdown must interrupt an in-flight silent request read promptly (TOLD), \
1308             not defer by the read's admitted 2s deadline: elapsed {:?}",
1309            start.elapsed()
1310        );
1311
1312        Ok(())
1313    }
1314
1315    /// Oracle 11 (W4 leg 2, idle-honesty both-sides) — an unrelated served
1316    /// request grows the BUSY listener's accept-attempt counter while the silent
1317    /// listener's accept-attempt counter stays FLAT during the workload. The
1318    /// growing side proves the fixture cannot pass by hiding the workload (a
1319    /// frozen harness would leave the busy counter flat and fail); the flat side
1320    /// proves genuine silence rather than a global freeze.
1321    #[test]
1322    fn health_idle_grows_unrelated_counters_while_accept_stays_flat()
1323    -> Result<(), Box<dyn std::error::Error>> {
1324        let idle = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1325        let busy = start_health_server(loopback_ephemeral()?, SharedReadinessState::default())?;
1326
1327        let deadline = Instant::now() + Duration::from_secs(2);
1328        while (idle.accept_attempts() < 1 || busy.accept_attempts() < 1)
1329            && Instant::now() < deadline
1330        {
1331            thread::sleep(Duration::from_millis(5));
1332        }
1333        let idle_armed = idle.accept_attempts();
1334        assert_eq!(idle_armed, 1, "the idle listener parks exactly one accept");
1335        let busy_before = busy.accept_attempts();
1336
1337        // Unrelated served workload on the BUSY listener: each served request
1338        // returns the parked accept and re-parks a fresh one, growing its counter.
1339        for _ in 0..5 {
1340            let response = get(busy.local_addr(), "/health")?;
1341            assert_status(&response, 200);
1342        }
1343        let deadline = Instant::now() + Duration::from_secs(2);
1344        while busy.accept_attempts() <= busy_before && Instant::now() < deadline {
1345            thread::sleep(Duration::from_millis(5));
1346        }
1347
1348        assert!(
1349            busy.accept_attempts() > busy_before,
1350            "an unrelated served request grows the busy listener's accept counter"
1351        );
1352        assert_eq!(
1353            idle.accept_attempts(),
1354            idle_armed,
1355            "the silent listener's accept counter stays flat during the workload"
1356        );
1357        assert_eq!(idle.shed_count(), 0, "the silent listener sheds nothing");
1358
1359        idle.shutdown()?;
1360        busy.shutdown()?;
1361        Ok(())
1362    }
1363}