use serde::{Deserialize, Serialize};
use crate::admission_recommendation::{Recommendation, recommend_for_summary};
use crate::admission_rejection::AdmissionRejectionSummary;
use crate::exec_policy::ExecDisposition;
use crate::incident::{ControlState, IncidentReasonCode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionLocation {
Remote,
Local,
NotExecuted,
}
impl ExecutionLocation {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
ExecutionLocation::Remote => "remote",
ExecutionLocation::Local => "local",
ExecutionLocation::NotExecuted => "not_executed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecResponse {
pub local_or_remote: ExecutionLocation,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selected_worker: Option<String>,
pub is_compilation: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub family: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason_code: Option<IncidentReasonCode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub admission: Option<AdmissionRejectionSummary>,
pub disposition: ExecDisposition,
pub proof_mode: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remediation: Vec<Recommendation>,
#[serde(default, skip_serializing_if = "ControlState::is_empty")]
pub control: ControlState,
pub detail: String,
}
impl ExecResponse {
#[must_use]
pub fn invariant_holds(&self) -> bool {
match self.local_or_remote {
ExecutionLocation::Remote => self.selected_worker.is_some(),
ExecutionLocation::Local | ExecutionLocation::NotExecuted => {
self.selected_worker.is_none()
}
}
}
#[must_use]
pub fn remote_success(worker: impl Into<String>, family: impl Into<String>) -> Self {
Self {
local_or_remote: ExecutionLocation::Remote,
selected_worker: Some(worker.into()),
is_compilation: true,
family: Some(family.into()),
reason_code: None,
admission: None,
disposition: ExecDisposition::RunRemote,
proof_mode: false,
remediation: Vec::new(),
control: ControlState::default(),
detail: "ran on remote worker".to_string(),
}
}
#[must_use]
pub fn local_fallback(
is_compilation: bool,
reason_code: IncidentReasonCode,
detail: impl Into<String>,
) -> Self {
Self {
local_or_remote: ExecutionLocation::Local,
selected_worker: None,
is_compilation,
family: None,
reason_code: Some(reason_code),
admission: None,
disposition: ExecDisposition::RunLocalFallback,
proof_mode: false,
remediation: Vec::new(),
control: ControlState::default(),
detail: detail.into(),
}
}
#[must_use]
pub fn proof_refused(admission: AdmissionRejectionSummary) -> Self {
let remediation = recommend_for_summary(&admission, true);
Self {
local_or_remote: ExecutionLocation::NotExecuted,
selected_worker: None,
is_compilation: true,
family: None,
reason_code: Some(IncidentReasonCode::ProofRefusal),
admission: Some(admission),
disposition: ExecDisposition::Reject,
proof_mode: true,
remediation,
control: ControlState::default(),
detail: "proof mode refused before execution".to_string(),
}
}
#[must_use]
pub fn non_compilation_rejected() -> Self {
Self {
local_or_remote: ExecutionLocation::NotExecuted,
selected_worker: None,
is_compilation: false,
family: None,
reason_code: Some(IncidentReasonCode::ProofRefusal),
admission: None,
disposition: ExecDisposition::Reject,
proof_mode: true,
remediation: Vec::new(),
control: ControlState::default(),
detail: "non-compilation command rejected under proof mode".to_string(),
}
}
#[must_use]
pub fn daemon_unavailable() -> Self {
Self {
local_or_remote: ExecutionLocation::Local,
selected_worker: None,
is_compilation: true,
family: None,
reason_code: Some(IncidentReasonCode::DaemonSocketRefused),
admission: None,
disposition: ExecDisposition::RunLocalFallback,
proof_mode: false,
remediation: Vec::new(),
control: ControlState::default(),
detail: "daemon unreachable; ran locally (fail-open)".to_string(),
}
}
#[must_use]
pub fn with_control(mut self, control: ControlState) -> Self {
self.control = control;
self
}
#[must_use]
pub fn effective_worker(&self) -> Option<&str> {
self.selected_worker.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::admission_rejection::{
AdmissionRejectionCategory, CandidateRejection, aggregate_rejections,
};
fn assert_invariant(r: &ExecResponse) {
assert!(
r.invariant_holds(),
"location/worker invariant violated: {r:?}"
);
}
#[test]
fn golden_remote_success() {
let r = ExecResponse::remote_success("css", "cargo_build");
assert_invariant(&r);
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["local_or_remote"], "remote");
assert_eq!(v["selected_worker"], "css");
assert_eq!(v["disposition"], "run_remote");
assert!(v.get("reason_code").is_none());
assert!(v.get("remediation").is_none(), "no remediation on success");
}
#[test]
fn golden_local_fallback() {
let r = ExecResponse::local_fallback(
true,
IncidentReasonCode::LocalFallback,
"no admissible worker; ran locally",
);
assert_invariant(&r);
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["local_or_remote"], "local");
assert!(v.get("selected_worker").is_none());
assert_eq!(v["disposition"], "run_local_fallback");
assert_eq!(v["reason_code"], "RCH-I011");
}
#[test]
fn golden_fail_closed_proof_refusal() {
let admission = aggregate_rejections(
2,
&[
CandidateRejection {
worker_id: "a".into(),
category: AdmissionRejectionCategory::MissingRustTarget,
},
CandidateRejection {
worker_id: "b".into(),
category: AdmissionRejectionCategory::OsArchMismatch,
},
],
);
let r = ExecResponse::proof_refused(admission);
assert_invariant(&r);
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["local_or_remote"], "not_executed");
assert_eq!(v["disposition"], "reject");
assert_eq!(v["proof_mode"], true);
assert_eq!(v["reason_code"], "RCH-I012");
assert_eq!(v["admission"]["rejected"], 2);
assert!(!r.remediation.is_empty());
assert_eq!(v["remediation"][0]["action"], "fix_worker_capability");
}
#[test]
fn golden_non_compilation_rejection() {
let r = ExecResponse::non_compilation_rejected();
assert_invariant(&r);
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["local_or_remote"], "not_executed");
assert_eq!(v["is_compilation"], false);
assert_eq!(v["disposition"], "reject");
}
#[test]
fn golden_daemon_unavailable() {
let r = ExecResponse::daemon_unavailable();
assert_invariant(&r);
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["local_or_remote"], "local");
assert_eq!(v["reason_code"], "RCH-I010");
assert_eq!(v["disposition"], "run_local_fallback");
}
#[test]
fn worker_present_iff_remote() {
assert_invariant(&ExecResponse::remote_success("w", "cargo_build"));
assert_invariant(&ExecResponse::local_fallback(
true,
IncidentReasonCode::LocalFallback,
"x",
));
assert_invariant(&ExecResponse::non_compilation_rejected());
assert_invariant(&ExecResponse::daemon_unavailable());
let bad = ExecResponse {
local_or_remote: ExecutionLocation::Remote,
selected_worker: None,
..ExecResponse::remote_success("w", "cargo_build")
};
assert!(!bad.invariant_holds());
}
#[test]
fn response_round_trips() {
let r = ExecResponse::daemon_unavailable();
let v = serde_json::to_value(&r).unwrap();
let back: ExecResponse = serde_json::from_value(v).unwrap();
assert_eq!(back, r);
}
#[test]
fn empty_control_is_omitted_from_wire() {
let r = ExecResponse::remote_success("css", "cargo_build");
let v = serde_json::to_value(&r).unwrap();
assert!(v.get("control").is_none(), "empty control must be omitted");
}
#[test]
fn control_snapshot_round_trips_and_pairs_requested_with_effective() {
use crate::incident::ControlState;
let r = ExecResponse::remote_success("ovh-a", "cargo_test").with_control(ControlState {
requested_worker: Some("css".to_string()),
strict_remote_policy: true,
queue_policy: Some("no_queue".to_string()),
..ControlState::default()
});
assert_invariant(&r);
assert_eq!(r.effective_worker(), Some("ovh-a"));
let v = serde_json::to_value(&r).unwrap();
assert_eq!(v["selected_worker"], "ovh-a");
assert_eq!(v["control"]["requested_worker"], "css");
assert_eq!(v["control"]["strict_remote_policy"], true);
assert_eq!(v["control"]["queue_policy"], "no_queue");
let back: ExecResponse = serde_json::from_value(v).unwrap();
assert_eq!(back, r);
}
#[test]
fn required_fields_are_always_present() {
for r in [
ExecResponse::remote_success("w", "cargo_build"),
ExecResponse::non_compilation_rejected(),
ExecResponse::daemon_unavailable(),
] {
let v = serde_json::to_value(&r).unwrap();
for key in [
"local_or_remote",
"is_compilation",
"disposition",
"proof_mode",
"detail",
] {
assert!(v.get(key).is_some(), "missing required field {key}");
}
}
}
}