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