play-faults fix: the sidecar link errors carry no text

SidecarLinkError::Spawn and Codec held error text that the fault type now
refuses, leaving two never-read payloads; they are unit variants.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-09 23:40:12 +02:00
commit 189d1d3c2b

View file

@ -192,10 +192,10 @@ pub(super) enum SidecarLinkError {
/// The pipe reached EOF or the child exited. Never a partial output.
Closed,
/// A length prefix over the cap, a truncated body, or an undecodable one.
Codec(String),
Codec,
/// The child could not be started. The entry file existed at open, so this
/// is an environment fault an operator must fix.
Spawn(String),
Spawn,
}
/// The child-process seam, so the protocol driver is testable without Node, a
@ -222,26 +222,21 @@ pub(super) async fn read_frame<R: AsyncRead + Unpin>(
.map_err(|_| SidecarLinkError::Closed)?;
let length = u32::from_be_bytes(prefix) as usize;
if length == 0 || length > MAX_SIDECAR_FRAME_BYTES {
return Err(SidecarLinkError::Codec(format!(
"sidecar frame length {length} is outside 1..={MAX_SIDECAR_FRAME_BYTES}"
)));
return Err(SidecarLinkError::Codec);
}
let mut body = vec![0_u8; length];
reader
.read_exact(&mut body)
.await
.map_err(|error| SidecarLinkError::Codec(error.to_string()))?;
rmp_serde::from_slice(&body).map_err(|error| SidecarLinkError::Codec(error.to_string()))
.map_err(|_| SidecarLinkError::Codec)?;
rmp_serde::from_slice(&body).map_err(|_| SidecarLinkError::Codec)
}
fn encode_frame(frame: &SidecarFrame) -> Result<Vec<u8>, SidecarLinkError> {
let body = rmp_serde::to_vec_named(frame)
.map_err(|error| SidecarLinkError::Codec(error.to_string()))?;
.map_err(|_| SidecarLinkError::Codec)?;
if body.len() > MAX_SIDECAR_FRAME_BYTES {
return Err(SidecarLinkError::Codec(format!(
"sidecar frame of {} bytes exceeds {MAX_SIDECAR_FRAME_BYTES}",
body.len()
)));
return Err(SidecarLinkError::Codec);
}
let mut framed = Vec::with_capacity(body.len() + 4);
framed.extend_from_slice(&(body.len() as u32).to_be_bytes());
@ -279,15 +274,15 @@ impl ChildProcessLink {
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()
.map_err(|error| SidecarLinkError::Spawn(error.to_string()))?;
.map_err(|_| SidecarLinkError::Spawn)?;
let stdin = child
.stdin
.take()
.ok_or_else(|| SidecarLinkError::Spawn("sidecar stdin was not piped".into()))?;
.ok_or(SidecarLinkError::Spawn)?;
let stdout = child
.stdout
.take()
.ok_or_else(|| SidecarLinkError::Spawn("sidecar stdout was not piped".into()))?;
.ok_or(SidecarLinkError::Spawn)?;
Ok(SidecarChild {
child,
stdin,
@ -485,7 +480,7 @@ impl SdkInferencePort {
return Err(link_fault(error));
}
}
Err(error) => {
Err(_) => {
let _ = self.link.restart().await;
return Err(InferenceFault::new("the tool-result owner refused a tool call"));
}
@ -522,8 +517,8 @@ fn link_fault(error: SidecarLinkError) -> InferenceFault {
SidecarLinkError::Closed => {
InferenceFault::new("the SDK sidecar exited during a query")
}
SidecarLinkError::Spawn(_) => InferenceFault::new("the SDK sidecar could not start"),
SidecarLinkError::Codec(_) => {
SidecarLinkError::Spawn => InferenceFault::new("the SDK sidecar could not start"),
SidecarLinkError::Codec => {
InferenceFault::integrity_violation("the SDK sidecar's framing was invalid")
}
}
@ -1707,7 +1702,7 @@ mod tests {
}
/// The SDK port's share of the no-input-text rule: text the sidecar sent,
/// the link's own error text, a request's effort and a request's model
/// a request's effort and a request's model
/// name each arrive as a canary, and none reaches the fault. The fault
/// type carries only fixed text, so this fails if a port ever routes a
/// computed string into it (through `Box::leak`, say).
@ -1745,10 +1740,6 @@ mod tests {
assert_names_no(&fault, CANARY);
}
// The link's own error text.
assert_names_no(&link_fault(SidecarLinkError::Spawn(CANARY.into())), CANARY);
assert_names_no(&link_fault(SidecarLinkError::Codec(CANARY.into())), CANARY);
// An effort the SDK has no counterpart for.
let port = SdkInferencePort::new(ScriptedLink::new(Vec::new()), TEST_RUNTIME);
let mut prepared = prose_prepared(&port, "Say something true.");