worker: structure runtime worker identities

This commit is contained in:
2026-08-05 21:26:55 +09:00
parent fd391ef705
commit a2781f57e9
9 changed files with 775 additions and 816 deletions
+3 -4
View File
@@ -1,4 +1,4 @@
use crate::identity::{WorkerId, WorkerRef}; use crate::identity::{RuntimeWorkerRef, WorkerId, WorkerRef};
use crate::interaction::WorkerInput; use crate::interaction::WorkerInput;
use crate::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveRef}; use crate::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveRef};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -132,9 +132,8 @@ pub struct WorkingDirectoryCleanupTarget {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkingDirectoryOccupancy { pub struct WorkingDirectoryOccupancy {
pub runtime_id: String, #[serde(flatten)]
pub runtime_worker_id: u64, pub worker: RuntimeWorkerRef,
pub worker_id: String,
pub display_name: String, pub display_name: String,
pub linked_at: String, pub linked_at: String,
} }
+52
View File
@@ -30,6 +30,32 @@ impl fmt::Display for WorkerId {
} }
} }
/// Backend-visible Worker identity, namespaced by the Runtime that owns the Worker record.
///
/// This is intentionally distinct from [`WorkerRef`], which is meaningful only inside one
/// Runtime. Do not flatten this reference into a concatenated string for authority decisions.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct RuntimeWorkerRef {
pub runtime_id: String,
pub worker_id: String,
}
impl RuntimeWorkerRef {
pub fn new(runtime_id: impl Into<String>, worker_id: impl Into<String>) -> Self {
Self {
runtime_id: runtime_id.into(),
worker_id: worker_id.into(),
}
}
pub fn local_worker_ref(&self) -> Result<WorkerRef, std::num::ParseIntError> {
self.worker_id
.parse::<u64>()
.map(WorkerId::new)
.map(WorkerRef::new)
}
}
/// Runtime-local authority reference for Worker operations. /// Runtime-local authority reference for Worker operations.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct WorkerRef { pub struct WorkerRef {
@@ -41,3 +67,29 @@ impl WorkerRef {
Self { worker_id } Self { worker_id }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn runtime_worker_ref_preserves_structured_identity_and_json_fields() {
let worker = RuntimeWorkerRef::new("arcadia", "30");
assert_eq!(worker.runtime_id, "arcadia");
assert_eq!(worker.worker_id, "30");
assert_eq!(
worker.local_worker_ref().unwrap(),
WorkerRef::new(WorkerId::new(30))
);
assert_eq!(
serde_json::to_value(&worker).unwrap(),
serde_json::json!({"runtime_id": "arcadia", "worker_id": "30"})
);
}
#[test]
fn runtime_worker_ref_does_not_treat_composite_text_as_local_worker_id() {
let worker = RuntimeWorkerRef::new("arcadia", "embedded-worker-runtime-5");
assert!(worker.local_worker_ref().is_err());
}
}
+160 -154
View File
@@ -1,5 +1,5 @@
use crate::Error; use crate::Error;
use crate::resource_broker::BackendResourceBroker; use crate::resource_broker::{BackendResourceBroker, BackendResourceTarget};
use chrono::Utc; use chrono::Utc;
use reqwest::blocking::{Client as BlockingHttpClient, RequestBuilder}; use reqwest::blocking::{Client as BlockingHttpClient, RequestBuilder};
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE}; use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
@@ -45,7 +45,9 @@ use worker_runtime::http_server::{
RuntimeHttpWorkerWorkspaceApiRequest, RuntimeHttpWorkersResponse, RuntimeHttpWorkerWorkspaceApiRequest, RuntimeHttpWorkersResponse,
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse, RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
}; };
use worker_runtime::identity::{WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef}; use worker_runtime::identity::{
RuntimeWorkerRef, WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef,
};
use worker_runtime::interaction::{ use worker_runtime::interaction::{
WorkerInput as EmbeddedWorkerInput, WorkerInputKind as EmbeddedWorkerInputKind, WorkerInput as EmbeddedWorkerInput, WorkerInputKind as EmbeddedWorkerInputKind,
}; };
@@ -235,8 +237,8 @@ pub struct WorkerCapabilitySummary {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSummary { pub struct WorkerSummary {
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub host_id: String, pub host_id: String,
/// Human-readable display name. This is not identity and may be duplicated. /// Human-readable display name. This is not identity and may be duplicated.
pub display_name: String, pub display_name: String,
@@ -487,8 +489,8 @@ pub struct WorkerLifecycleRequest {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerLifecycleResult { pub struct WorkerLifecycleResult {
pub state: WorkerOperationState, pub state: WorkerOperationState,
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -505,8 +507,8 @@ pub enum WorkerInputKind {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerDeleteResult { pub struct WorkerDeleteResult {
pub state: WorkerOperationState, pub state: WorkerOperationState,
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub deleted: bool, pub deleted: bool,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -529,8 +531,8 @@ pub struct WorkerCompletionsRequest {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerCompletionsResult { pub struct WorkerCompletionsResult {
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub kind: protocol::CompletionKind, pub kind: protocol::CompletionKind,
pub prefix: String, pub prefix: String,
pub entries: Vec<protocol::CompletionEntry>, pub entries: Vec<protocol::CompletionEntry>,
@@ -540,8 +542,8 @@ pub struct WorkerCompletionsResult {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerInputResult { pub struct WorkerInputResult {
pub state: WorkerOperationState, pub state: WorkerOperationState,
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -561,8 +563,7 @@ pub enum RuntimeRegistryError {
UnknownRuntime(String), UnknownRuntime(String),
UnknownHost(String), UnknownHost(String),
UnknownWorker { UnknownWorker {
runtime_id: String, worker: RuntimeWorkerRef,
worker_id: String,
}, },
RuntimeOperationFailed { RuntimeOperationFailed {
runtime_id: String, runtime_id: String,
@@ -579,10 +580,10 @@ impl RuntimeRegistryError {
} }
Self::UnknownRuntime(runtime_id) => format!("unknown runtime `{runtime_id}`"), Self::UnknownRuntime(runtime_id) => format!("unknown runtime `{runtime_id}`"),
Self::UnknownHost(host_id) => format!("unknown host `{host_id}`"), Self::UnknownHost(host_id) => format!("unknown host `{host_id}`"),
Self::UnknownWorker { Self::UnknownWorker { worker } => format!(
runtime_id, "unknown worker `{}` in runtime `{}`",
worker_id, worker.worker_id, worker.runtime_id
} => format!("unknown worker `{worker_id}` in runtime `{runtime_id}`"), ),
Self::RuntimeOperationFailed { message, .. } => message.clone(), Self::RuntimeOperationFailed { message, .. } => message.clone(),
} }
} }
@@ -595,13 +596,7 @@ impl RuntimeRegistryError {
}, },
Self::UnknownRuntime(runtime_id) => Error::UnknownRuntime(runtime_id), Self::UnknownRuntime(runtime_id) => Error::UnknownRuntime(runtime_id),
Self::UnknownHost(host_id) => Error::UnknownHost(host_id), Self::UnknownHost(host_id) => Error::UnknownHost(host_id),
Self::UnknownWorker { Self::UnknownWorker { worker } => Error::UnknownWorker { worker },
runtime_id,
worker_id,
} => Error::UnknownWorker {
runtime_id,
worker_id,
},
Self::RuntimeOperationFailed { Self::RuntimeOperationFailed {
runtime_id, runtime_id,
code, code,
@@ -798,8 +793,7 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
) -> WorkerLifecycleResult { ) -> WorkerLifecycleResult {
WorkerLifecycleResult { WorkerLifecycleResult {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), worker: RuntimeWorkerRef::new(self.runtime_id().to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_stop_pending", "worker_stop_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -817,8 +811,7 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
) -> WorkerLifecycleResult { ) -> WorkerLifecycleResult {
WorkerLifecycleResult { WorkerLifecycleResult {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), worker: RuntimeWorkerRef::new(self.runtime_id().to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_cancel_pending", "worker_cancel_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -832,8 +825,7 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
fn delete_worker(&self, worker_id: &str) -> WorkerDeleteResult { fn delete_worker(&self, worker_id: &str) -> WorkerDeleteResult {
WorkerDeleteResult { WorkerDeleteResult {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), worker: RuntimeWorkerRef::new(self.runtime_id().to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
deleted: false, deleted: false,
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_delete_unsupported", "worker_delete_unsupported",
@@ -853,8 +845,7 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
fn send_input(&self, worker_id: &str, _request: WorkerInputRequest) -> WorkerInputResult { fn send_input(&self, worker_id: &str, _request: WorkerInputRequest) -> WorkerInputResult {
WorkerInputResult { WorkerInputResult {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), worker: RuntimeWorkerRef::new(self.runtime_id().to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_input_pending", "worker_input_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -871,8 +862,7 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
request: WorkerCompletionsRequest, request: WorkerCompletionsRequest,
) -> WorkerCompletionsResult { ) -> WorkerCompletionsResult {
WorkerCompletionsResult { WorkerCompletionsResult {
runtime_id: self.runtime_id().to_string(), worker: RuntimeWorkerRef::new(self.runtime_id().to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries: Vec::new(), entries: Vec::new(),
@@ -1099,11 +1089,9 @@ impl RuntimeRegistry {
} }
} }
pub fn worker( pub fn worker(&self, worker: &RuntimeWorkerRef) -> Result<WorkerSummary, RuntimeRegistryError> {
&self, let runtime_id = worker.runtime_id.as_str();
runtime_id: &str, let worker_id = worker.worker_id.as_str();
worker_id: &str,
) -> Result<WorkerSummary, RuntimeRegistryError> {
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1116,9 +1104,10 @@ impl RuntimeRegistry {
pub fn restore_worker( pub fn restore_worker(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
) -> Result<WorkerRestoreResult, RuntimeRegistryError> { ) -> Result<WorkerRestoreResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1127,10 +1116,11 @@ impl RuntimeRegistry {
pub fn replace_worker_workspace_api( pub fn replace_worker_workspace_api(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
workspace_api: WorkspaceApiRef, workspace_api: WorkspaceApiRef,
) -> Result<WorkerWorkspaceApiResult, RuntimeRegistryError> { ) -> Result<WorkerWorkspaceApiResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1241,10 +1231,11 @@ impl RuntimeRegistry {
pub fn send_protocol_method( pub fn send_protocol_method(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
method: protocol::Method, method: protocol::Method,
) -> Result<Vec<protocol::Event>, RuntimeRegistryError> { ) -> Result<Vec<protocol::Event>, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1261,10 +1252,11 @@ impl RuntimeRegistry {
pub fn send_input( pub fn send_input(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
request: WorkerInputRequest, request: WorkerInputRequest,
) -> Result<WorkerInputResult, RuntimeRegistryError> { ) -> Result<WorkerInputResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1281,10 +1273,11 @@ impl RuntimeRegistry {
pub fn worker_completions( pub fn worker_completions(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
request: WorkerCompletionsRequest, request: WorkerCompletionsRequest,
) -> Result<WorkerCompletionsResult, RuntimeRegistryError> { ) -> Result<WorkerCompletionsResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1301,10 +1294,11 @@ impl RuntimeRegistry {
pub fn stop_worker( pub fn stop_worker(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
request: WorkerLifecycleRequest, request: WorkerLifecycleRequest,
) -> Result<WorkerLifecycleResult, RuntimeRegistryError> { ) -> Result<WorkerLifecycleResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1321,10 +1315,11 @@ impl RuntimeRegistry {
pub fn cancel_worker( pub fn cancel_worker(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
request: WorkerLifecycleRequest, request: WorkerLifecycleRequest,
) -> Result<WorkerLifecycleResult, RuntimeRegistryError> { ) -> Result<WorkerLifecycleResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1341,9 +1336,10 @@ impl RuntimeRegistry {
pub fn delete_worker( pub fn delete_worker(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
) -> Result<WorkerDeleteResult, RuntimeRegistryError> { ) -> Result<WorkerDeleteResult, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
@@ -1360,17 +1356,17 @@ impl RuntimeRegistry {
pub fn observation_source( pub fn observation_source(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
) -> Result<crate::observation::RuntimeObservationSource, RuntimeRegistryError> { ) -> Result<crate::observation::RuntimeObservationSource, RuntimeRegistryError> {
let runtime_id = worker.runtime_id.as_str();
let worker_id = worker.worker_id.as_str();
validate_backend_identifier("runtime_id", runtime_id)?; validate_backend_identifier("runtime_id", runtime_id)?;
validate_backend_identifier("worker_id", worker_id)?; validate_backend_identifier("worker_id", worker_id)?;
let runtime = self.runtime(runtime_id)?; let runtime = self.runtime(runtime_id)?;
runtime runtime
.observation_source(worker_id) .observation_source(worker_id)
.ok_or_else(|| RuntimeRegistryError::UnknownWorker { .ok_or_else(|| RuntimeRegistryError::UnknownWorker {
runtime_id: runtime_id.to_string(), worker: worker.clone(),
worker_id: worker_id.to_string(),
}) })
} }
@@ -1483,8 +1479,7 @@ impl EmbeddedWorkerRuntime {
true, true,
); );
WorkerSummary { WorkerSummary {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
worker_id,
host_id: self.host_id.clone(), host_id: self.host_id.clone(),
display_name: display.display_name.clone(), display_name: display.display_name.clone(),
label: display.display_name, label: display.display_name,
@@ -1522,8 +1517,7 @@ impl EmbeddedWorkerRuntime {
true, true,
); );
WorkerSummary { WorkerSummary {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
worker_id,
host_id: self.host_id.clone(), host_id: self.host_id.clone(),
display_name: display.display_name.clone(), display_name: display.display_name.clone(),
label: display.display_name, label: display.display_name,
@@ -1974,8 +1968,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
match self.runtime.stop_worker(&worker_ref, request.reason) { match self.runtime.stop_worker(&worker_ref, request.reason) {
Ok(_) => WorkerLifecycleResult { Ok(_) => WorkerLifecycleResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_lifecycle_rejected( Err(error) => embedded_lifecycle_rejected(
@@ -2018,8 +2011,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
match self.runtime.cancel_worker(&worker_ref, request.reason) { match self.runtime.cancel_worker(&worker_ref, request.reason) {
Ok(_) => WorkerLifecycleResult { Ok(_) => WorkerLifecycleResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_lifecycle_rejected( Err(error) => embedded_lifecycle_rejected(
@@ -2034,8 +2026,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
let Some(worker_ref) = self.worker_ref(worker_id) else { let Some(worker_ref) = self.worker_ref(worker_id) else {
return WorkerDeleteResult { return WorkerDeleteResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
deleted: false, deleted: false,
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"embedded_worker_id_invalid", "embedded_worker_id_invalid",
@@ -2047,15 +2038,16 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
match self.runtime.delete_worker(&worker_ref) { match self.runtime.delete_worker(&worker_ref) {
Ok(result) => WorkerDeleteResult { Ok(result) => WorkerDeleteResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(
worker_id: result.worker_id.to_string(), self.runtime_id.clone(),
result.worker_id.to_string(),
),
deleted: result.deleted, deleted: result.deleted,
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => WorkerDeleteResult { Err(error) => WorkerDeleteResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
deleted: false, deleted: false,
diagnostics: vec![embedded_runtime_diagnostic(&error)], diagnostics: vec![embedded_runtime_diagnostic(&error)],
}, },
@@ -2072,8 +2064,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
} }
Some(crate::observation::RuntimeObservationSource::embedded( Some(crate::observation::RuntimeObservationSource::embedded(
crate::observation::EmbeddedRuntimeObservationSource { crate::observation::EmbeddedRuntimeObservationSource {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id),
worker_id: worker_id.to_string(),
runtime: self.runtime.clone(), runtime: self.runtime.clone(),
worker_ref, worker_ref,
}, },
@@ -2096,8 +2087,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
} }
let Some(worker_ref) = self.worker_ref(worker_id) else { let Some(worker_ref) = self.worker_ref(worker_id) else {
return Err(RuntimeRegistryError::UnknownWorker { return Err(RuntimeRegistryError::UnknownWorker {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id),
worker_id: worker_id.to_string(),
}); });
}; };
self.runtime self.runtime
@@ -2148,8 +2138,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
match self.runtime.send_input(&worker_ref, input) { match self.runtime.send_input(&worker_ref, input) {
Ok(_) => WorkerInputResult { Ok(_) => WorkerInputResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_input_rejected( Err(error) => embedded_input_rejected(
@@ -2167,8 +2156,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
) -> WorkerCompletionsResult { ) -> WorkerCompletionsResult {
if !self.execution_enabled { if !self.execution_enabled {
return WorkerCompletionsResult { return WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries: Vec::new(), entries: Vec::new(),
@@ -2183,8 +2171,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
} }
let Some(worker_ref) = self.worker_ref(worker_id) else { let Some(worker_ref) = self.worker_ref(worker_id) else {
return WorkerCompletionsResult { return WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries: Vec::new(), entries: Vec::new(),
@@ -2200,16 +2187,14 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
.worker_completions(&worker_ref, request.kind, &request.prefix) .worker_completions(&worker_ref, request.kind, &request.prefix)
{ {
Ok(entries) => WorkerCompletionsResult { Ok(entries) => WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries, entries,
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => WorkerCompletionsResult { Err(error) => WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries: Vec::new(), entries: Vec::new(),
@@ -2572,8 +2557,7 @@ impl RemoteWorkerRuntime {
false, false,
); );
WorkerSummary { WorkerSummary {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
worker_id,
host_id: self.host_id.clone(), host_id: self.host_id.clone(),
display_name: display.display_name.clone(), display_name: display.display_name.clone(),
label: display.display_name, label: display.display_name,
@@ -2615,8 +2599,7 @@ impl RemoteWorkerRuntime {
false, false,
); );
WorkerSummary { WorkerSummary {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
worker_id,
host_id: self.host_id.clone(), host_id: self.host_id.clone(),
display_name: display.display_name.clone(), display_name: display.display_name.clone(),
label: display.display_name, label: display.display_name,
@@ -2655,8 +2638,7 @@ impl RemoteWorkerRuntime {
) -> WorkerLifecycleResult { ) -> WorkerLifecycleResult {
WorkerLifecycleResult { WorkerLifecycleResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"remote_runtime_lifecycle_accepted", "remote_runtime_lifecycle_accepted",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -3073,15 +3055,16 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
{ {
Ok(response) => WorkerDeleteResult { Ok(response) => WorkerDeleteResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(
worker_id: response.worker.worker_id.to_string(), self.runtime_id.clone(),
response.worker.worker_id.to_string(),
),
deleted: response.worker.deleted, deleted: response.worker.deleted,
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(diagnostic) => WorkerDeleteResult { Err(diagnostic) => WorkerDeleteResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
deleted: false, deleted: false,
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
}, },
@@ -3094,8 +3077,7 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
) -> Option<crate::observation::RuntimeObservationSource> { ) -> Option<crate::observation::RuntimeObservationSource> {
Some(crate::observation::RuntimeObservationSource::remote_ws( Some(crate::observation::RuntimeObservationSource::remote_ws(
crate::observation::RuntimeObservationSourceConfig { crate::observation::RuntimeObservationSourceConfig {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id),
worker_id: worker_id.to_string(),
endpoint: self.ws_endpoint(worker_id), endpoint: self.ws_endpoint(worker_id),
bearer_token: self bearer_token: self
.runtime_capability_token(&format!("/v1/workers/{worker_id}/protocol")) .runtime_capability_token(&format!("/v1/workers/{worker_id}/protocol"))
@@ -3122,8 +3104,7 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
) { ) {
Ok(_) => WorkerInputResult { Ok(_) => WorkerInputResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic), Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic),
@@ -3144,16 +3125,14 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
&request, &request,
) { ) {
Ok(response) => WorkerCompletionsResult { Ok(response) => WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: response.kind, kind: response.kind,
prefix: response.prefix, prefix: response.prefix,
entries: response.entries, entries: response.entries,
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(diagnostic) => WorkerCompletionsResult { Err(diagnostic) => WorkerCompletionsResult {
runtime_id: self.runtime_id.clone(), worker: RuntimeWorkerRef::new(self.runtime_id.clone(), worker_id.to_string()),
worker_id: worker_id.to_string(),
kind: request.kind, kind: request.kind,
prefix: request.prefix, prefix: request.prefix,
entries: Vec::new(), entries: Vec::new(),
@@ -3247,10 +3226,12 @@ fn profile_source_archive_http_source(
backend_base_url: &str, backend_base_url: &str,
) -> Result<ProfileSourceArchiveSource, String> { ) -> Result<ProfileSourceArchiveSource, String> {
let archive = profile_source_archive_for_request(request, profile)?; let archive = profile_source_archive_for_request(request, profile)?;
let target = runtime_id
.map(BackendResourceTarget::Runtime)
.unwrap_or(BackendResourceTarget::Workspace);
let _handle = resource_broker.issue_profile_source_archive_handle( let _handle = resource_broker.issue_profile_source_archive_handle(
workspace_id.to_string(), workspace_id.to_string(),
runtime_id, target,
None,
archive.clone(), archive.clone(),
); );
let etag = format!("\"profile-source:{}\"", archive.reference.digest); let etag = format!("\"profile-source:{}\"", archive.reference.digest);
@@ -3294,10 +3275,12 @@ fn builtin_profile_config_bundle(
let (profile_source_archive, profile_source_archive_handle) = match archive_transport { let (profile_source_archive, profile_source_archive_handle) = match archive_transport {
ProfileSourceArchiveTransport::Inline => (Some(archive), None), ProfileSourceArchiveTransport::Inline => (Some(archive), None),
ProfileSourceArchiveTransport::BackendResourceHandle => { ProfileSourceArchiveTransport::BackendResourceHandle => {
let target = runtime_id
.map(BackendResourceTarget::Runtime)
.unwrap_or(BackendResourceTarget::Workspace);
let handle = resource_broker.issue_profile_source_archive_handle( let handle = resource_broker.issue_profile_source_archive_handle(
workspace_id.to_string(), workspace_id.to_string(),
runtime_id, target,
None,
archive, archive,
); );
(None, Some(handle)) (None, Some(handle))
@@ -3515,8 +3498,7 @@ fn embedded_input_rejected(
) -> WorkerInputResult { ) -> WorkerInputResult {
WorkerInputResult { WorkerInputResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id.to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3528,8 +3510,7 @@ fn remote_input_rejected(
) -> WorkerInputResult { ) -> WorkerInputResult {
WorkerInputResult { WorkerInputResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id.to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3541,8 +3522,7 @@ fn embedded_lifecycle_rejected(
) -> WorkerLifecycleResult { ) -> WorkerLifecycleResult {
WorkerLifecycleResult { WorkerLifecycleResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id.to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3554,8 +3534,7 @@ fn remote_lifecycle_rejected(
) -> WorkerLifecycleResult { ) -> WorkerLifecycleResult {
WorkerLifecycleResult { WorkerLifecycleResult {
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id.to_string(), worker_id.to_string()),
worker_id: worker_id.to_string(),
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3779,8 +3758,7 @@ fn operation_failed_or_unknown_worker(
message: diagnostic.message, message: diagnostic.message,
}) })
.unwrap_or_else(|| RuntimeRegistryError::UnknownWorker { .unwrap_or_else(|| RuntimeRegistryError::UnknownWorker {
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id, worker_id),
worker_id: worker_id.to_string(),
}) })
} }
@@ -3887,8 +3865,7 @@ fn worker_spawn_intent_label(intent: &WorkerSpawnIntent) -> &'static str {
pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary { pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
let host_id = host_id.into(); let host_id = host_id.into();
WorkerSummary { WorkerSummary {
runtime_id: "placeholder".to_string(), worker: RuntimeWorkerRef::new("placeholder", "worker-placeholder"),
worker_id: "worker-placeholder".to_string(),
host_id, host_id,
display_name: "Worker runtime actions are not implemented".to_string(), display_name: "Worker runtime actions are not implemented".to_string(),
label: "Worker runtime actions are not implemented".to_string(), label: "Worker runtime actions are not implemented".to_string(),
@@ -3951,6 +3928,29 @@ mod tests {
} }
} }
#[test]
fn worker_summary_keeps_flat_wire_identity_while_using_structured_internal_identity() {
let summary = placeholder_worker("placeholder");
assert_eq!(
summary.worker,
RuntimeWorkerRef::new("placeholder", "worker-placeholder")
);
let value = serde_json::to_value(summary).unwrap();
assert_eq!(value["runtime_id"], "placeholder");
assert_eq!(value["worker_id"], "worker-placeholder");
assert!(value.get("worker").is_none());
let lifecycle = WorkerLifecycleResult {
state: WorkerOperationState::Accepted,
worker: RuntimeWorkerRef::new("arcadia", "30"),
diagnostics: Vec::new(),
};
let value = serde_json::to_value(lifecycle).unwrap();
assert_eq!(value["runtime_id"], "arcadia");
assert_eq!(value["worker_id"], "30");
assert!(value.get("worker").is_none());
}
#[test] #[test]
fn embedded_orchestrator_profile_enables_workdir_and_worker_authority() { fn embedded_orchestrator_profile_enables_workdir_and_worker_authority() {
let root = tempfile::tempdir().unwrap(); let root = tempfile::tempdir().unwrap();
@@ -4289,8 +4289,7 @@ mod tests {
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
host_id: host_id.to_string(), host_id: host_id.to_string(),
workers: vec![WorkerSummary { workers: vec![WorkerSummary {
runtime_id: runtime_id.to_string(), worker: RuntimeWorkerRef::new(runtime_id, worker_id),
worker_id: worker_id.to_string(),
host_id: host_id.to_string(), host_id: host_id.to_string(),
display_name: label.to_string(), display_name: label.to_string(),
label: label.to_string(), label: label.to_string(),
@@ -4382,7 +4381,7 @@ mod tests {
worker: self worker: self
.workers .workers
.iter() .iter()
.find(|worker| worker.worker_id == worker_id) .find(|worker| worker.worker.worker_id == worker_id)
.cloned(), .cloned(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
} }
@@ -4406,13 +4405,17 @@ mod tests {
)), )),
]); ]);
let from_runtime_b = registry.worker("runtime-b", "shared-worker").unwrap(); let from_runtime_b = registry
assert_eq!(from_runtime_b.runtime_id, "runtime-b"); .worker(&RuntimeWorkerRef::new("runtime-b", "shared-worker"))
.unwrap();
assert_eq!(from_runtime_b.worker.runtime_id, "runtime-b");
assert_eq!(from_runtime_b.host_id, "host-b"); assert_eq!(from_runtime_b.host_id, "host-b");
assert_eq!(from_runtime_b.label, "worker from runtime b"); assert_eq!(from_runtime_b.label, "worker from runtime b");
let from_runtime_a = registry.worker("runtime-a", "shared-worker").unwrap(); let from_runtime_a = registry
assert_eq!(from_runtime_a.runtime_id, "runtime-a"); .worker(&RuntimeWorkerRef::new("runtime-a", "shared-worker"))
.unwrap();
assert_eq!(from_runtime_a.worker.runtime_id, "runtime-a");
assert_eq!(from_runtime_a.host_id, "host-a"); assert_eq!(from_runtime_a.host_id, "host-a");
assert_eq!(from_runtime_a.label, "worker from runtime a"); assert_eq!(from_runtime_a.label, "worker from runtime a");
} }
@@ -4436,7 +4439,7 @@ mod tests {
let listed = registry.list_workers_for_runtime("runtime-b", 10).unwrap(); let listed = registry.list_workers_for_runtime("runtime-b", 10).unwrap();
assert_eq!(listed.items.len(), 1); assert_eq!(listed.items.len(), 1);
assert_eq!(listed.items[0].runtime_id, "runtime-b"); assert_eq!(listed.items[0].worker.runtime_id, "runtime-b");
assert_eq!(listed.items[0].host_id, "host-b"); assert_eq!(listed.items[0].host_id, "host-b");
assert_eq!(listed.items[0].label, "worker from runtime b"); assert_eq!(listed.items[0].label, "worker from runtime b");
} }
@@ -4455,7 +4458,9 @@ mod tests {
Some("builtin:companion") Some("builtin:companion")
); );
let worker = registry.worker("runtime-a", "worker-a").unwrap(); let worker = registry
.worker(&RuntimeWorkerRef::new("runtime-a", "worker-a"))
.unwrap();
assert_eq!(worker.profile.as_deref(), Some("builtin:companion")); assert_eq!(worker.profile.as_deref(), Some("builtin:companion"));
} }
@@ -4468,7 +4473,9 @@ mod tests {
"worker from runtime a", "worker from runtime a",
))]); ))]);
let unknown_runtime = registry.worker("runtime-missing", "worker-a").unwrap_err(); let unknown_runtime = registry
.worker(&RuntimeWorkerRef::new("runtime-missing", "worker-a"))
.unwrap_err();
assert_eq!( assert_eq!(
unknown_runtime, unknown_runtime,
RuntimeRegistryError::UnknownRuntime("runtime-missing".to_string()) RuntimeRegistryError::UnknownRuntime("runtime-missing".to_string())
@@ -4478,18 +4485,19 @@ mod tests {
Error::UnknownRuntime(runtime_id) if runtime_id == "runtime-missing" Error::UnknownRuntime(runtime_id) if runtime_id == "runtime-missing"
)); ));
let unknown_worker = registry.worker("runtime-a", "999").unwrap_err(); let unknown_worker = registry
.worker(&RuntimeWorkerRef::new("runtime-a", "999"))
.unwrap_err();
assert_eq!( assert_eq!(
unknown_worker, unknown_worker,
RuntimeRegistryError::UnknownWorker { RuntimeRegistryError::UnknownWorker {
runtime_id: "runtime-a".to_string(), worker: RuntimeWorkerRef::new("runtime-a", "999"),
worker_id: "999".to_string(),
} }
); );
assert!(matches!( assert!(matches!(
unknown_worker.into_error(), unknown_worker.into_error(),
Error::UnknownWorker { runtime_id, worker_id } Error::UnknownWorker { worker }
if runtime_id == "runtime-a" && worker_id == "999" if worker == RuntimeWorkerRef::new("runtime-a", "999")
)); ));
} }
@@ -4591,7 +4599,7 @@ mod tests {
assert!(worker.capabilities.can_stop); assert!(worker.capabilities.can_stop);
let input = runtime.send_input( let input = runtime.send_input(
&worker.worker_id, &worker.worker.worker_id,
WorkerInputRequest { WorkerInputRequest {
kind: WorkerInputKind::User, kind: WorkerInputKind::User,
content: "hello".to_string(), content: "hello".to_string(),
@@ -4603,7 +4611,7 @@ mod tests {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
loop { loop {
let detail = runtime let detail = runtime
.worker(&worker.worker_id) .worker(&worker.worker.worker_id)
.worker .worker
.expect("worker detail"); .expect("worker detail");
if detail.state == "idle" { if detail.state == "idle" {
@@ -4671,15 +4679,14 @@ mod tests {
.any(|evidence| evidence.kind == "embedded_runtime_backend_internal_projection") .any(|evidence| evidence.kind == "embedded_runtime_backend_internal_projection")
); );
let worker = spawned.worker.expect("created embedded worker"); let worker = spawned.worker.expect("created embedded worker");
assert_eq!(worker.runtime_id, EMBEDDED_RUNTIME_ID); assert_eq!(worker.worker.runtime_id, EMBEDDED_RUNTIME_ID);
assert_eq!(worker.workspace.visibility, "backend_internal"); assert_eq!(worker.workspace.visibility, "backend_internal");
assert_eq!(worker.workspace.identity, "runtime_registry_worker"); assert_eq!(worker.workspace.identity, "runtime_registry_worker");
assert_eq!(worker.implementation.kind, "embedded_worker_runtime"); assert_eq!(worker.implementation.kind, "embedded_worker_runtime");
assert_eq!(worker.profile.as_deref(), Some("builtin:coder")); assert_eq!(worker.profile.as_deref(), Some("builtin:coder"));
let input = registry let input = registry
.send_input( .send_input(
EMBEDDED_RUNTIME_ID, &worker.worker,
&worker.worker_id,
WorkerInputRequest { WorkerInputRequest {
kind: WorkerInputKind::User, kind: WorkerInputKind::User,
content: "hello embedded runtime".to_string(), content: "hello embedded runtime".to_string(),
@@ -4688,12 +4695,10 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(input.state, WorkerOperationState::Accepted); assert_eq!(input.state, WorkerOperationState::Accepted);
assert_eq!(input.runtime_id, EMBEDDED_RUNTIME_ID); assert_eq!(input.worker.runtime_id, EMBEDDED_RUNTIME_ID);
assert_eq!(input.worker_id, worker.worker_id); assert_eq!(input.worker.worker_id, worker.worker.worker_id);
let detail = registry let detail = registry.worker(&worker.worker).unwrap();
.worker(EMBEDDED_RUNTIME_ID, &worker.worker_id)
.unwrap();
let json = serde_json::to_string(&(embedded_summary, worker, input, detail)).unwrap(); let json = serde_json::to_string(&(embedded_summary, worker, input, detail)).unwrap();
for forbidden in [ for forbidden in [
@@ -4871,7 +4876,7 @@ mod tests {
); );
let observation = registry let observation = registry
.observation_source("remote:primary", "1") .observation_source(&RuntimeWorkerRef::new("remote:primary", "1"))
.expect("remote runtime exposes backend-owned WS observation source"); .expect("remote runtime exposes backend-owned WS observation source");
let crate::observation::RuntimeObservationSource::RemoteWs(observation) = observation let crate::observation::RuntimeObservationSource::RemoteWs(observation) = observation
else { else {
@@ -4883,8 +4888,8 @@ mod tests {
let workers = registry.list_workers(10); let workers = registry.list_workers(10);
assert_eq!(workers.items.len(), 1); assert_eq!(workers.items.len(), 1);
assert_eq!(workers.items[0].runtime_id, "remote:primary"); assert_eq!(workers.items[0].worker.runtime_id, "remote:primary");
assert_eq!(workers.items[0].worker_id, "1"); assert_eq!(workers.items[0].worker.worker_id, "1");
assert_eq!( assert_eq!(
workers.items[0].implementation.kind, workers.items[0].implementation.kind,
"remote_worker_runtime" "remote_worker_runtime"
@@ -4897,8 +4902,7 @@ mod tests {
let input = registry let input = registry
.send_input( .send_input(
"remote:primary", &RuntimeWorkerRef::new("remote:primary", "1"),
"1",
WorkerInputRequest { WorkerInputRequest {
kind: WorkerInputKind::User, kind: WorkerInputKind::User,
content: "hello remote".to_string(), content: "hello remote".to_string(),
@@ -4975,7 +4979,9 @@ mod tests {
assert_eq!(workers.items[2].state, "paused"); assert_eq!(workers.items[2].state, "paused");
assert_eq!(workers.items[3].state, "idle"); assert_eq!(workers.items[3].state, "idle");
let stopped_detail = registry.worker("remote:primary", "1").unwrap(); let stopped_detail = registry
.worker(&RuntimeWorkerRef::new("remote:primary", "1"))
.unwrap();
assert!(!stopped_detail.capabilities.can_stop); assert!(!stopped_detail.capabilities.can_stop);
assert_eq!(stopped_detail.state, "stopped"); assert_eq!(stopped_detail.state, "stopped");
@@ -5154,7 +5160,7 @@ mod tests {
); );
let error = registry let error = registry
.worker("remote:primary", "999") .worker(&RuntimeWorkerRef::new("remote:primary", "999"))
.expect_err("auth failure is a backend operation error"); .expect_err("auth failure is a backend operation error");
assert!(matches!( assert!(matches!(
error, error,
+6 -5
View File
@@ -43,6 +43,8 @@ pub use repositories::{
pub use server::{AuthConfig, ServerConfig, WorkspaceApi, build_router, serve}; pub use server::{AuthConfig, ServerConfig, WorkspaceApi, build_router, serve};
pub use store::{ControlPlaneStore, SqliteWorkspaceStore, WorkspaceRecord}; pub use store::{ControlPlaneStore, SqliteWorkspaceStore, WorkspaceRecord};
use worker_runtime::identity::RuntimeWorkerRef;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@@ -65,11 +67,8 @@ pub enum Error {
UnknownHost(String), UnknownHost(String),
#[error("unknown runtime `{0}`")] #[error("unknown runtime `{0}`")]
UnknownRuntime(String), UnknownRuntime(String),
#[error("unknown worker `{worker_id}` in runtime `{runtime_id}`")] #[error("unknown worker `{}` in runtime `{}`", worker.worker_id, worker.runtime_id)]
UnknownWorker { UnknownWorker { worker: RuntimeWorkerRef },
runtime_id: String,
worker_id: String,
},
#[error("invalid runtime {kind} `{value}`")] #[error("invalid runtime {kind} `{value}`")]
InvalidRuntimeIdentifier { kind: String, value: String }, InvalidRuntimeIdentifier { kind: String, value: String },
#[error("worker name is reserved for a dedicated Workspace service: {0}")] #[error("worker name is reserved for a dedicated Workspace service: {0}")]
@@ -93,6 +92,8 @@ pub enum Error {
TicketAssignmentConflict(String), TicketAssignmentConflict(String),
#[error("Workdir attachment conflict: {0}")] #[error("Workdir attachment conflict: {0}")]
WorkdirAttachmentConflict(String), WorkdirAttachmentConflict(String),
#[error("Registry inconsistency: {0}")]
RegistryInconsistency(String),
#[error("Worker source identity is invalid: {0}")] #[error("Worker source identity is invalid: {0}")]
WorkerSourceIdentity(String), WorkerSourceIdentity(String),
#[error("workspace identity error: {0}")] #[error("workspace identity error: {0}")]
+36 -66
View File
@@ -1,7 +1,7 @@
use std::collections::{BTreeMap, VecDeque}; use std::collections::{BTreeMap, VecDeque};
use std::sync::Arc; use std::sync::Arc;
use worker_runtime::identity::WorkerRef; use worker_runtime::identity::{RuntimeWorkerRef, WorkerRef};
use worker_runtime::observation::{WorkerObservationCursor, WorkerObservationEvent}; use worker_runtime::observation::{WorkerObservationCursor, WorkerObservationEvent};
use axum::http::StatusCode; use axum::http::StatusCode;
@@ -15,8 +15,7 @@ use tokio_tungstenite::tungstenite::{Error as TungsteniteError, Message as Tungs
/// Backend-private source for a runtime worker observation stream. /// Backend-private source for a runtime worker observation stream.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct RuntimeObservationSourceConfig { pub struct RuntimeObservationSourceConfig {
pub runtime_id: String, pub worker: RuntimeWorkerRef,
pub worker_id: String,
pub endpoint: String, pub endpoint: String,
pub bearer_token: Option<String>, pub bearer_token: Option<String>,
} }
@@ -24,8 +23,8 @@ pub struct RuntimeObservationSourceConfig {
impl std::fmt::Debug for RuntimeObservationSourceConfig { impl std::fmt::Debug for RuntimeObservationSourceConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RuntimeObservationSourceConfig") f.debug_struct("RuntimeObservationSourceConfig")
.field("runtime_id", &self.runtime_id) .field("runtime_id", &self.worker.runtime_id)
.field("worker_id", &self.worker_id) .field("worker_id", &self.worker.worker_id)
.field("endpoint", &"<backend-private>") .field("endpoint", &"<backend-private>")
.field( .field(
"bearer_token", "bearer_token",
@@ -37,8 +36,7 @@ impl std::fmt::Debug for RuntimeObservationSourceConfig {
#[derive(Clone)] #[derive(Clone)]
pub struct EmbeddedRuntimeObservationSource { pub struct EmbeddedRuntimeObservationSource {
pub runtime_id: String, pub worker: RuntimeWorkerRef,
pub worker_id: String,
pub runtime: worker_runtime::Runtime, pub runtime: worker_runtime::Runtime,
pub worker_ref: WorkerRef, pub worker_ref: WorkerRef,
} }
@@ -60,15 +58,15 @@ impl RuntimeObservationSource {
pub fn runtime_id(&self) -> &str { pub fn runtime_id(&self) -> &str {
match self { match self {
Self::RemoteWs(config) => &config.runtime_id, Self::RemoteWs(config) => &config.worker.runtime_id,
Self::Embedded(source) => &source.runtime_id, Self::Embedded(source) => &source.worker.runtime_id,
} }
} }
pub fn worker_id(&self) -> &str { pub fn worker_id(&self) -> &str {
match self { match self {
Self::RemoteWs(config) => &config.worker_id, Self::RemoteWs(config) => &config.worker.worker_id,
Self::Embedded(source) => &source.worker_id, Self::Embedded(source) => &source.worker.worker_id,
} }
} }
} }
@@ -78,8 +76,8 @@ impl std::fmt::Debug for RuntimeObservationSource {
match self { match self {
Self::RemoteWs(config) => formatter Self::RemoteWs(config) => formatter
.debug_struct("RemoteRuntimeObservationSource") .debug_struct("RemoteRuntimeObservationSource")
.field("runtime_id", &config.runtime_id) .field("runtime_id", &config.worker.runtime_id)
.field("worker_id", &config.worker_id) .field("worker_id", &config.worker.worker_id)
.field("endpoint", &"<backend-private>") .field("endpoint", &"<backend-private>")
.field( .field(
"bearer_token", "bearer_token",
@@ -88,8 +86,8 @@ impl std::fmt::Debug for RuntimeObservationSource {
.finish(), .finish(),
Self::Embedded(source) => formatter Self::Embedded(source) => formatter
.debug_struct("EmbeddedRuntimeObservationSource") .debug_struct("EmbeddedRuntimeObservationSource")
.field("runtime_id", &source.runtime_id) .field("runtime_id", &source.worker.runtime_id)
.field("worker_id", &source.worker_id) .field("worker_id", &source.worker.worker_id)
.finish(), .finish(),
} }
} }
@@ -98,8 +96,8 @@ impl std::fmt::Debug for RuntimeObservationSource {
/// Event consumed from a Runtime-owned worker observation WebSocket. /// Event consumed from a Runtime-owned worker observation WebSocket.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RuntimeObservationUpstreamEvent { pub struct RuntimeObservationUpstreamEvent {
pub runtime_id: String, #[serde(flatten)]
pub worker_id: String, pub worker: RuntimeWorkerRef,
pub runtime_event_id: String, pub runtime_event_id: String,
pub payload: protocol::Event, pub payload: protocol::Event,
} }
@@ -132,11 +130,7 @@ impl ObservationProxyError {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] type ObservationKey = RuntimeWorkerRef;
struct ObservationKey {
runtime_id: String,
worker_id: String,
}
/// Backend-owned in-memory v0 observation proxy state. /// Backend-owned in-memory v0 observation proxy state.
#[derive(Clone)] #[derive(Clone)]
@@ -156,15 +150,7 @@ impl BackendObservationProxy {
pub fn new(sources: Vec<RuntimeObservationSourceConfig>) -> Self { pub fn new(sources: Vec<RuntimeObservationSourceConfig>) -> Self {
let sources = sources let sources = sources
.into_iter() .into_iter()
.map(|source| { .map(|source| (source.worker.clone(), source))
(
ObservationKey {
runtime_id: source.runtime_id.clone(),
worker_id: source.worker_id.clone(),
},
source,
)
})
.collect(); .collect();
Self { Self {
sources: Arc::new(sources), sources: Arc::new(sources),
@@ -173,19 +159,16 @@ impl BackendObservationProxy {
pub fn source( pub fn source(
&self, &self,
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
) -> Result<RuntimeObservationSource, ObservationProxyError> { ) -> Result<RuntimeObservationSource, ObservationProxyError> {
self.sources self.sources
.get(&ObservationKey { .get(worker)
runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(),
})
.cloned() .cloned()
.map(RuntimeObservationSource::remote_ws) .map(RuntimeObservationSource::remote_ws)
.ok_or_else(|| { .ok_or_else(|| {
ObservationProxyError::WorkerNotFound(format!( ObservationProxyError::WorkerNotFound(format!(
"worker {worker_id} is not registered for runtime {runtime_id}" "worker {} is not registered for runtime {}",
worker.worker_id, worker.runtime_id
)) ))
}) })
} }
@@ -209,8 +192,7 @@ fn map_runtime_connect_error(error: TungsteniteError) -> ObservationProxyError {
} }
pub struct RuntimeWsObservationClient { pub struct RuntimeWsObservationClient {
runtime_id: String, worker: RuntimeWorkerRef,
worker_id: String,
stream: tokio_tungstenite::WebSocketStream< stream: tokio_tungstenite::WebSocketStream<
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>, tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
>, >,
@@ -240,8 +222,7 @@ impl RuntimeWsObservationClient {
.await .await
.map_err(map_runtime_connect_error)?; .map_err(map_runtime_connect_error)?;
Ok(Self { Ok(Self {
runtime_id: source.runtime_id.clone(), worker: source.worker.clone(),
worker_id: source.worker_id.clone(),
stream, stream,
}) })
} }
@@ -291,8 +272,7 @@ impl RuntimeWsObservationClient {
)) ))
})?; })?;
return Ok(RuntimeObservationUpstreamEvent { return Ok(RuntimeObservationUpstreamEvent {
runtime_id: self.runtime_id.clone(), worker: self.worker.clone(),
worker_id: self.worker_id.clone(),
runtime_event_id: "protocol".to_string(), runtime_event_id: "protocol".to_string(),
payload, payload,
}); });
@@ -330,8 +310,7 @@ impl RuntimeObservationClient {
} }
pub struct EmbeddedObservationClient { pub struct EmbeddedObservationClient {
runtime_id: String, worker: RuntimeWorkerRef,
worker_id: String,
worker_ref: WorkerRef, worker_ref: WorkerRef,
cursor: WorkerObservationCursor, cursor: WorkerObservationCursor,
receiver: tokio::sync::broadcast::Receiver<WorkerObservationEvent>, receiver: tokio::sync::broadcast::Receiver<WorkerObservationEvent>,
@@ -346,7 +325,7 @@ impl EmbeddedObservationClient {
.map_err(|err| { .map_err(|err| {
ObservationProxyError::WorkerNotFound(format!( ObservationProxyError::WorkerNotFound(format!(
"embedded Worker '{}' is not observable: {err}", "embedded Worker '{}' is not observable: {err}",
source.worker_id source.worker.worker_id
)) ))
})?; })?;
let receiver = source let receiver = source
@@ -355,7 +334,7 @@ impl EmbeddedObservationClient {
.map_err(|err| { .map_err(|err| {
ObservationProxyError::WorkerNotFound(format!( ObservationProxyError::WorkerNotFound(format!(
"embedded Worker '{}' observation subscription is unavailable: {err}", "embedded Worker '{}' observation subscription is unavailable: {err}",
source.worker_id source.worker.worker_id
)) ))
})?; })?;
let mut queued = VecDeque::new(); let mut queued = VecDeque::new();
@@ -365,12 +344,11 @@ impl EmbeddedObservationClient {
.map_err(|err| { .map_err(|err| {
ObservationProxyError::WorkerNotFound(format!( ObservationProxyError::WorkerNotFound(format!(
"embedded Worker '{}' snapshot is unavailable: {err}", "embedded Worker '{}' snapshot is unavailable: {err}",
source.worker_id source.worker.worker_id
)) ))
})?; })?;
queued.push_back(RuntimeObservationUpstreamEvent { queued.push_back(RuntimeObservationUpstreamEvent {
runtime_id: source.runtime_id.clone(), worker: source.worker.clone(),
worker_id: source.worker_id.clone(),
runtime_event_id: "snapshot".to_string(), runtime_event_id: "snapshot".to_string(),
payload: snapshot, payload: snapshot,
}); });
@@ -380,19 +358,14 @@ impl EmbeddedObservationClient {
.map_err(|err| { .map_err(|err| {
ObservationProxyError::RuntimeUnavailable(format!( ObservationProxyError::RuntimeUnavailable(format!(
"embedded Worker '{}' observation cursor is unavailable: {err}", "embedded Worker '{}' observation cursor is unavailable: {err}",
source.worker_id source.worker.worker_id
)) ))
})? })?
{ {
queued.push_back(Self::map_event( queued.push_back(Self::map_event(&source.worker, event));
&source.runtime_id,
&source.worker_id,
event,
));
} }
Ok(Self { Ok(Self {
runtime_id: source.runtime_id.clone(), worker: source.worker.clone(),
worker_id: source.worker_id.clone(),
worker_ref: source.worker_ref.clone(), worker_ref: source.worker_ref.clone(),
cursor, cursor,
receiver, receiver,
@@ -418,7 +391,7 @@ impl EmbeddedObservationClient {
"embedded runtime emitted a malformed cursor".into(), "embedded runtime emitted a malformed cursor".into(),
) )
})?; })?;
return Ok(Self::map_event(&self.runtime_id, &self.worker_id, event)); return Ok(Self::map_event(&self.worker, event));
} }
Ok(_) => continue, Ok(_) => continue,
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
@@ -436,13 +409,11 @@ impl EmbeddedObservationClient {
} }
fn map_event( fn map_event(
runtime_id: &str, worker: &RuntimeWorkerRef,
worker_id: &str,
event: WorkerObservationEvent, event: WorkerObservationEvent,
) -> RuntimeObservationUpstreamEvent { ) -> RuntimeObservationUpstreamEvent {
RuntimeObservationUpstreamEvent { RuntimeObservationUpstreamEvent {
runtime_id: runtime_id.to_string(), worker: worker.clone(),
worker_id: worker_id.to_string(),
runtime_event_id: event.cursor.clone(), runtime_event_id: event.cursor.clone(),
payload: event.payload, payload: event.payload,
} }
@@ -455,8 +426,7 @@ mod tests {
fn sensitive_source() -> RuntimeObservationSourceConfig { fn sensitive_source() -> RuntimeObservationSourceConfig {
RuntimeObservationSourceConfig { RuntimeObservationSourceConfig {
runtime_id: "remote-runtime".to_string(), worker: RuntimeWorkerRef::new("remote-runtime", "worker-1"),
worker_id: "worker-1".to_string(),
endpoint: "wss://remote.example.invalid/private/workers/worker-1/protocol/ws" endpoint: "wss://remote.example.invalid/private/workers/worker-1/protocol/ws"
.to_string(), .to_string(),
bearer_token: Some("top-secret-bearer-token".to_string()), bearer_token: Some("top-secret-bearer-token".to_string()),
+36 -28
View File
@@ -3,7 +3,7 @@ use chrono::{Duration, Utc};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use uuid::Uuid; use uuid::Uuid;
use worker_runtime::identity::WorkerId; use worker_runtime::identity::RuntimeWorkerRef;
use worker_runtime::profile_archive::ProfileSourceArchive; use worker_runtime::profile_archive::ProfileSourceArchive;
use worker_runtime::resource::{ use worker_runtime::resource::{
BackendResourceClient, BackendResourceError, BackendResourceFetchRequest, BackendResourceClient, BackendResourceError, BackendResourceFetchRequest,
@@ -17,10 +17,17 @@ pub struct BackendResourceBroker {
resources: Arc<Mutex<HashMap<String, StoredResource>>>, resources: Arc<Mutex<HashMap<String, StoredResource>>>,
} }
#[derive(Clone, Copy, Debug)]
pub enum BackendResourceTarget<'a> {
Workspace,
Runtime(&'a str),
Worker(&'a RuntimeWorkerRef),
}
#[derive(Clone)] #[derive(Clone)]
struct StoredResource { struct StoredResource {
runtime_id: Option<String>, runtime_id: Option<String>,
worker_id: Option<String>, worker: Option<RuntimeWorkerRef>,
handle: BackendResourceHandle, handle: BackendResourceHandle,
archive: ProfileSourceArchive, archive: ProfileSourceArchive,
} }
@@ -29,11 +36,17 @@ impl BackendResourceBroker {
pub fn issue_profile_source_archive_handle( pub fn issue_profile_source_archive_handle(
&self, &self,
workspace_id: impl Into<String>, workspace_id: impl Into<String>,
runtime_id: Option<&str>, target: BackendResourceTarget<'_>,
worker_id: Option<&WorkerId>,
archive: ProfileSourceArchive, archive: ProfileSourceArchive,
) -> BackendResourceHandle { ) -> BackendResourceHandle {
let workspace_id = workspace_id.into(); let workspace_id = workspace_id.into();
let (runtime_id, worker) = match target {
BackendResourceTarget::Workspace => (None, None),
BackendResourceTarget::Runtime(runtime_id) => (Some(runtime_id.to_string()), None),
BackendResourceTarget::Worker(worker) => {
(Some(worker.runtime_id.clone()), Some(worker.clone()))
}
};
let nonce = Uuid::now_v7().to_string(); let nonce = Uuid::now_v7().to_string();
let audit_correlation_id = format!("resource-fetch-{nonce}"); let audit_correlation_id = format!("resource-fetch-{nonce}");
let expires_at = Utc::now() + Duration::minutes(15); let expires_at = Utc::now() + Duration::minutes(15);
@@ -41,8 +54,8 @@ impl BackendResourceBroker {
kind: BackendResourceKind::ProfileSourceArchive, kind: BackendResourceKind::ProfileSourceArchive,
workspace_id: workspace_id.clone(), workspace_id: workspace_id.clone(),
scope_id: Some("workspace-profile-source".to_string()), scope_id: Some("workspace-profile-source".to_string()),
runtime_id: runtime_id.map(|id| id.to_string()), runtime_id: runtime_id.clone(),
worker_id: worker_id.map(|id| id.to_string()), worker_id: worker.as_ref().map(|worker| worker.worker_id.clone()),
resource_id: archive.reference.id.clone(), resource_id: archive.reference.id.clone(),
digest: archive.reference.digest.clone(), digest: archive.reference.digest.clone(),
operation: BackendResourceOperation::FetchArchive, operation: BackendResourceOperation::FetchArchive,
@@ -57,8 +70,8 @@ impl BackendResourceBroker {
profile_source_graph: Some(archive.reference.source_graph.clone()), profile_source_graph: Some(archive.reference.source_graph.clone()),
}; };
let stored = StoredResource { let stored = StoredResource {
runtime_id: runtime_id.map(|id| id.to_string()), runtime_id,
worker_id: worker_id.map(|id| id.to_string()), worker,
handle: handle.clone(), handle: handle.clone(),
archive, archive,
}; };
@@ -117,8 +130,10 @@ impl BackendResourceBroker {
}); });
} }
} }
if let Some(expected_worker_id) = stored.worker_id.as_deref() { if let Some(expected_worker) = stored.worker.as_ref() {
if Some(expected_worker_id) != request.worker_id.as_deref() { if expected_worker.runtime_id != request.runtime_id
|| Some(expected_worker.worker_id.as_str()) != request.worker_id.as_deref()
{
return Err(BackendResourceError::Unauthorized { return Err(BackendResourceError::Unauthorized {
message: "worker id does not match resource handle".to_string(), message: "worker id does not match resource handle".to_string(),
}); });
@@ -167,7 +182,6 @@ fn verify_handle_shape(handle: &BackendResourceHandle) -> Result<(), BackendReso
mod tests { mod tests {
use super::*; use super::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use worker_runtime::identity::WorkerId;
use worker_runtime::profile_archive::{ use worker_runtime::profile_archive::{
ProfileSourceArchive, ProfileSourceArchiveRef, ProfileSourceGraphSummary, sha256_hex, ProfileSourceArchive, ProfileSourceArchiveRef, ProfileSourceGraphSummary, sha256_hex,
}; };
@@ -215,13 +229,13 @@ mod tests {
fn request( fn request(
handle: BackendResourceHandle, handle: BackendResourceHandle,
runtime_id: &str, runtime_id: &str,
worker_id: Option<&WorkerId>, worker_id: Option<&str>,
) -> BackendResourceFetchRequest { ) -> BackendResourceFetchRequest {
BackendResourceFetchRequest { BackendResourceFetchRequest {
audit_correlation_id: handle.audit_correlation_id.clone(), audit_correlation_id: handle.audit_correlation_id.clone(),
handle, handle,
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
worker_id: worker_id.map(|id| id.to_string()), worker_id: worker_id.map(str::to_string),
} }
} }
@@ -231,8 +245,7 @@ mod tests {
let runtime_id = "runtime-test"; let runtime_id = "runtime-test";
let handle = broker.issue_profile_source_archive_handle( let handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_id), BackendResourceTarget::Runtime(runtime_id),
None,
archive(), archive(),
); );
let response = broker let response = broker
@@ -253,8 +266,7 @@ mod tests {
let runtime_a = "runtime-a"; let runtime_a = "runtime-a";
let handle = broker.issue_profile_source_archive_handle( let handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_a), BackendResourceTarget::Runtime(runtime_a),
None,
archive(), archive(),
); );
let err = broker let err = broker
@@ -267,16 +279,15 @@ mod tests {
fn broker_rejects_worker_mismatch() { fn broker_rejects_worker_mismatch() {
let broker = BackendResourceBroker::default(); let broker = BackendResourceBroker::default();
let runtime_id = "runtime-test"; let runtime_id = "runtime-test";
let worker_a = WorkerId::new(1); let worker_a = RuntimeWorkerRef::new(runtime_id, "1");
let worker_b = WorkerId::new(2); let worker_b = RuntimeWorkerRef::new(runtime_id, "2");
let handle = broker.issue_profile_source_archive_handle( let handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_id), BackendResourceTarget::Worker(&worker_a),
Some(&worker_a),
archive(), archive(),
); );
let err = broker let err = broker
.fetch_profile_source_archive(request(handle, &runtime_id, Some(&worker_b))) .fetch_profile_source_archive(request(handle, runtime_id, Some(&worker_b.worker_id)))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, BackendResourceError::Unauthorized { .. })); assert!(matches!(err, BackendResourceError::Unauthorized { .. }));
} }
@@ -287,8 +298,7 @@ mod tests {
let runtime_id = "runtime-test"; let runtime_id = "runtime-test";
let handle = broker.issue_profile_source_archive_handle( let handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_id), BackendResourceTarget::Runtime(runtime_id),
None,
archive(), archive(),
); );
broker broker
@@ -313,8 +323,7 @@ mod tests {
let runtime_id = "runtime-test"; let runtime_id = "runtime-test";
let mut handle = broker.issue_profile_source_archive_handle( let mut handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_id), BackendResourceTarget::Runtime(runtime_id),
None,
archive(), archive(),
); );
handle.scope_id = Some("tampered-scope".to_string()); handle.scope_id = Some("tampered-scope".to_string());
@@ -331,8 +340,7 @@ mod tests {
let archive = archive_with_len((DEFAULT_PROFILE_SOURCE_ARCHIVE_MAX_BYTES + 1) as usize); let archive = archive_with_len((DEFAULT_PROFILE_SOURCE_ARCHIVE_MAX_BYTES + 1) as usize);
let mut handle = broker.issue_profile_source_archive_handle( let mut handle = broker.issue_profile_source_archive_handle(
"workspace-test", "workspace-test",
Some(runtime_id), BackendResourceTarget::Runtime(runtime_id),
None,
archive, archive,
); );
handle.max_bytes = DEFAULT_PROFILE_SOURCE_ARCHIVE_MAX_BYTES + 1024; handle.max_bytes = DEFAULT_PROFILE_SOURCE_ARCHIVE_MAX_BYTES + 1024;
File diff suppressed because it is too large Load Diff
+83 -98
View File
@@ -6,6 +6,8 @@ use async_trait::async_trait;
use rusqlite::{Connection, OptionalExtension, params}; use rusqlite::{Connection, OptionalExtension, params};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use worker_runtime::identity::RuntimeWorkerRef;
use crate::{Error, Result}; use crate::{Error, Result};
const WORKSPACES_V0_COLUMNS: &[&str] = &[ const WORKSPACES_V0_COLUMNS: &[&str] = &[
@@ -268,8 +270,7 @@ pub struct DeviceLoginFlowRecord {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerRegistryRecord { pub struct WorkerRegistryRecord {
pub workspace_id: String, pub workspace_id: String,
pub runtime_id: String, pub worker: RuntimeWorkerRef,
pub runtime_worker_id: u64,
pub display_name: String, pub display_name: String,
pub profile: Option<String>, pub profile: Option<String>,
/// Retention state is explicit so `pinned` can be represented before prune exists. /// Retention state is explicit so `pinned` can be represented before prune exists.
@@ -287,8 +288,7 @@ pub struct TicketWorkerAssignmentRecord {
pub workspace_id: String, pub workspace_id: String,
pub ticket_id: String, pub ticket_id: String,
pub assignment_id: String, pub assignment_id: String,
pub runtime_id: String, pub worker: RuntimeWorkerRef,
pub worker_id: String,
pub assigned_by: String, pub assigned_by: String,
pub assigned_at: String, pub assigned_at: String,
} }
@@ -330,8 +330,7 @@ pub struct WorkdirRegistryRecord {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerWorkdirLinkRecord { pub struct WorkerWorkdirLinkRecord {
pub workspace_id: String, pub workspace_id: String,
pub runtime_id: String, pub worker: RuntimeWorkerRef,
pub runtime_worker_id: u64,
pub workdir_id: String, pub workdir_id: String,
pub role: String, pub role: String,
pub linked_at: String, pub linked_at: String,
@@ -541,8 +540,7 @@ pub trait ControlPlaneStore: Send + Sync {
fn get_worker_registry( fn get_worker_registry(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<Option<WorkerRegistryRecord>>; ) -> Result<Option<WorkerRegistryRecord>>;
fn list_worker_registry( fn list_worker_registry(
&self, &self,
@@ -552,17 +550,12 @@ pub trait ControlPlaneStore: Send + Sync {
fn update_worker_retention( fn update_worker_retention(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
retention_state: &str, retention_state: &str,
updated_at: &str, updated_at: &str,
) -> Result<bool>; ) -> Result<bool>;
fn delete_worker_registry( fn delete_worker_registry(&self, workspace_id: &str, worker: &RuntimeWorkerRef)
&self, -> Result<bool>;
workspace_id: &str,
runtime_id: &str,
runtime_worker_id: u64,
) -> Result<bool>;
fn get_ticket_assignment_operation( fn get_ticket_assignment_operation(
&self, &self,
@@ -653,22 +646,19 @@ pub trait ControlPlaneStore: Send + Sync {
fn detach_worker_workdir( fn detach_worker_workdir(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
expected_workdir_id: Option<&str>, expected_workdir_id: Option<&str>,
unlinked_at: &str, unlinked_at: &str,
) -> Result<Option<WorkerWorkdirLinkRecord>>; ) -> Result<Option<WorkerWorkdirLinkRecord>>;
fn worker_workdir_link_history_exists( fn worker_workdir_link_history_exists(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<bool>; ) -> Result<bool>;
fn list_worker_workdir_links( fn list_worker_workdir_links(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<Vec<WorkerWorkdirLinkRecord>>; ) -> Result<Vec<WorkerWorkdirLinkRecord>>;
fn list_workdir_worker_links( fn list_workdir_worker_links(
&self, &self,
@@ -1637,8 +1627,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
updated_at = excluded.updated_at"#, updated_at = excluded.updated_at"#,
params![ params![
record.workspace_id, record.workspace_id,
record.runtime_id, record.worker.runtime_id,
record.runtime_worker_id, record.worker.worker_id,
record.display_name, record.display_name,
record.profile, record.profile,
record.retention_state, record.retention_state,
@@ -1657,8 +1647,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn get_worker_registry( fn get_worker_registry(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<Option<WorkerRegistryRecord>> { ) -> Result<Option<WorkerRegistryRecord>> {
self.with_conn(|conn| { self.with_conn(|conn| {
conn.query_row( conn.query_row(
@@ -1666,7 +1655,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
"WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3", "WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3",
) )
.as_str(), .as_str(),
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
read_worker_registry_record, read_worker_registry_record,
) )
.optional() .optional()
@@ -1696,8 +1685,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn update_worker_retention( fn update_worker_retention(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
retention_state: &str, retention_state: &str,
updated_at: &str, updated_at: &str,
) -> Result<bool> { ) -> Result<bool> {
@@ -1708,8 +1696,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3"#, WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3"#,
params![ params![
workspace_id, workspace_id,
runtime_id, worker.runtime_id,
runtime_worker_id, worker.worker_id,
retention_state, retention_state,
updated_at updated_at
], ],
@@ -1721,8 +1709,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn delete_worker_registry( fn delete_worker_registry(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<bool> { ) -> Result<bool> {
self.with_conn(|conn| { self.with_conn(|conn| {
let tx = conn.unchecked_transaction()?; let tx = conn.unchecked_transaction()?;
@@ -1730,11 +1717,11 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
r#"UPDATE worker_workdir_links r#"UPDATE worker_workdir_links
SET unlinked_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') SET unlinked_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#, WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#,
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
)?; )?;
let changed = tx.execute( let changed = tx.execute(
"DELETE FROM worker_registry WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3", "DELETE FROM worker_registry WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3",
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
)?; )?;
tx.commit()?; tx.commit()?;
Ok(changed > 0) Ok(changed > 0)
@@ -1787,7 +1774,12 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
if existing.action == "assign" if existing.action == "assign"
&& existing.ticket_id == ticket_id && existing.ticket_id == ticket_id
&& existing.runtime_id.as_deref() == Some(runtime_id) && existing.runtime_id.as_deref() == Some(runtime_id)
&& (worker_id.is_none() || existing.worker_id.as_deref() == worker_id) && (worker_id.is_none()
|| existing
.worker
.as_ref()
.map(|worker| worker.worker_id.as_str())
== worker_id)
&& existing.expected_assignment_id.is_none() && existing.expected_assignment_id.is_none()
&& existing.request_fingerprint.as_deref() == Some(request_fingerprint) && existing.request_fingerprint.as_deref() == Some(request_fingerprint)
{ {
@@ -1855,8 +1847,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
{ {
if existing.action != if allow_reassign { "reassign" } else { "assign" } if existing.action != if allow_reassign { "reassign" } else { "assign" }
|| existing.ticket_id != record.ticket_id || existing.ticket_id != record.ticket_id
|| existing.runtime_id.as_deref() != Some(record.runtime_id.as_str()) || existing.worker.as_ref() != Some(&record.worker)
|| existing.worker_id.as_deref() != Some(record.worker_id.as_str())
|| existing.expected_assignment_id.as_deref() != expected_assignment_id || existing.expected_assignment_id.as_deref() != expected_assignment_id
{ {
return Err(Error::TicketAssignmentConflict(format!( return Err(Error::TicketAssignmentConflict(format!(
@@ -1937,8 +1928,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
record.workspace_id, record.workspace_id,
record.ticket_id, record.ticket_id,
record.assignment_id, record.assignment_id,
record.runtime_id, record.worker.runtime_id,
record.worker_id, record.worker.worker_id,
record.assigned_by, record.assigned_by,
record.assigned_at, record.assigned_at,
], ],
@@ -1952,8 +1943,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
record.workspace_id, record.workspace_id,
record.ticket_id, record.ticket_id,
record.assignment_id, record.assignment_id,
record.runtime_id, record.worker.runtime_id,
record.worker_id, record.worker.worker_id,
record.assigned_at, record.assigned_at,
], ],
) )
@@ -1966,8 +1957,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
record.workspace_id, record.workspace_id,
record.ticket_id, record.ticket_id,
record.assignment_id, record.assignment_id,
record.runtime_id, record.worker.runtime_id,
record.worker_id, record.worker.worker_id,
record.assigned_at, record.assigned_at,
], ],
) )
@@ -1976,7 +1967,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
return Err(map_assignment_constraint( return Err(map_assignment_constraint(
error, error,
&record.ticket_id, &record.ticket_id,
&record.worker_id, &record.worker.worker_id,
)); ));
} }
tx.execute( tx.execute(
@@ -2024,8 +2015,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
operation_id, operation_id,
if allow_reassign { "reassign" } else { "assign" }, if allow_reassign { "reassign" } else { "assign" },
record.ticket_id, record.ticket_id,
record.runtime_id, record.worker.runtime_id,
record.worker_id, record.worker.worker_id,
record.assignment_id, record.assignment_id,
expected_assignment_id, expected_assignment_id,
record.assigned_at, record.assigned_at,
@@ -2116,8 +2107,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
workspace_id, workspace_id,
operation_id, operation_id,
ticket_id, ticket_id,
previous.runtime_id, previous.worker.runtime_id,
previous.worker_id, previous.worker.worker_id,
previous.assignment_id, previous.assignment_id,
expected_assignment_id, expected_assignment_id,
created_at, created_at,
@@ -2369,8 +2360,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
unlinked_at = NULL"#, unlinked_at = NULL"#,
params![ params![
record.workspace_id, record.workspace_id,
record.runtime_id, record.worker.runtime_id,
record.runtime_worker_id, record.worker.worker_id,
record.workdir_id, record.workdir_id,
record.role, record.role,
record.linked_at, record.linked_at,
@@ -2451,8 +2442,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#, WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#,
params![ params![
record.workspace_id, record.workspace_id,
record.runtime_id, record.worker.runtime_id,
record.runtime_worker_id, record.worker.worker_id,
], ],
read_worker_workdir_link_record, read_worker_workdir_link_record,
) )
@@ -2464,7 +2455,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
} }
return Err(Error::WorkdirAttachmentConflict(format!( return Err(Error::WorkdirAttachmentConflict(format!(
"Worker {}:{} is already attached to Workdir {}", "Worker {}:{} is already attached to Workdir {}",
record.runtime_id, record.runtime_worker_id, active.workdir_id record.worker.runtime_id, record.worker.worker_id, active.workdir_id
))); )));
} }
let active_for_workdir = tx let active_for_workdir = tx
@@ -2479,7 +2470,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
if let Some(active) = active_for_workdir { if let Some(active) = active_for_workdir {
return Err(Error::WorkdirAttachmentConflict(format!( return Err(Error::WorkdirAttachmentConflict(format!(
"Workdir {} is already attached to Worker {}:{}", "Workdir {} is already attached to Worker {}:{}",
record.workdir_id, active.runtime_id, active.runtime_worker_id record.workdir_id, active.worker.runtime_id, active.worker.worker_id
))); )));
} }
let write = tx.execute( let write = tx.execute(
@@ -2491,8 +2482,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
unlinked_at = NULL"#, unlinked_at = NULL"#,
params![ params![
record.workspace_id, record.workspace_id,
record.runtime_id, record.worker.runtime_id,
record.runtime_worker_id, record.worker.worker_id,
record.workdir_id, record.workdir_id,
record.role, record.role,
record.linked_at, record.linked_at,
@@ -2515,8 +2506,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn detach_worker_workdir( fn detach_worker_workdir(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
expected_workdir_id: Option<&str>, expected_workdir_id: Option<&str>,
unlinked_at: &str, unlinked_at: &str,
) -> Result<Option<WorkerWorkdirLinkRecord>> { ) -> Result<Option<WorkerWorkdirLinkRecord>> {
@@ -2530,7 +2520,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
r#"SELECT workspace_id, runtime_id, runtime_worker_id, workdir_id, role, linked_at, unlinked_at r#"SELECT workspace_id, runtime_id, runtime_worker_id, workdir_id, role, linked_at, unlinked_at
FROM worker_workdir_links FROM worker_workdir_links
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#, WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#,
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
read_worker_workdir_link_record, read_worker_workdir_link_record,
) )
.optional()?; .optional()?;
@@ -2541,8 +2531,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
if let Some(expected_workdir_id) = expected_workdir_id { if let Some(expected_workdir_id) = expected_workdir_id {
if active.workdir_id != expected_workdir_id { if active.workdir_id != expected_workdir_id {
return Err(Error::WorkdirAttachmentConflict(format!( return Err(Error::WorkdirAttachmentConflict(format!(
"Worker {runtime_id}:{runtime_worker_id} is attached to Workdir {}, not {expected_workdir_id}", "Worker {}:{} is attached to Workdir {}, not {expected_workdir_id}",
active.workdir_id worker.runtime_id, worker.worker_id, active.workdir_id
))); )));
} }
} }
@@ -2550,11 +2540,12 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
r#"UPDATE worker_workdir_links r#"UPDATE worker_workdir_links
SET unlinked_at = ?4 SET unlinked_at = ?4
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#, WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 AND unlinked_at IS NULL"#,
params![workspace_id, runtime_id, runtime_worker_id, unlinked_at], params![workspace_id, worker.runtime_id, worker.worker_id, unlinked_at],
)?; )?;
if changed != 1 { if changed != 1 {
return Err(Error::WorkdirAttachmentConflict(format!( return Err(Error::WorkdirAttachmentConflict(format!(
"Worker {runtime_id}:{runtime_worker_id} attachment changed during detach" "Worker {}:{} attachment changed during detach",
worker.runtime_id, worker.worker_id
))); )));
} }
tx.commit()?; tx.commit()?;
@@ -2568,8 +2559,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn worker_workdir_link_history_exists( fn worker_workdir_link_history_exists(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<bool> { ) -> Result<bool> {
self.with_conn(|conn| { self.with_conn(|conn| {
let exists = conn.query_row( let exists = conn.query_row(
@@ -2577,7 +2567,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
SELECT 1 FROM worker_workdir_links SELECT 1 FROM worker_workdir_links
WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3 WHERE workspace_id = ?1 AND runtime_id = ?2 AND runtime_worker_id = ?3
)"#, )"#,
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
|row| row.get(0), |row| row.get(0),
)?; )?;
Ok(exists) Ok(exists)
@@ -2587,8 +2577,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
fn list_worker_workdir_links( fn list_worker_workdir_links(
&self, &self,
workspace_id: &str, workspace_id: &str,
runtime_id: &str, worker: &RuntimeWorkerRef,
runtime_worker_id: u64,
) -> Result<Vec<WorkerWorkdirLinkRecord>> { ) -> Result<Vec<WorkerWorkdirLinkRecord>> {
self.with_conn(|conn| { self.with_conn(|conn| {
let mut stmt = conn.prepare( let mut stmt = conn.prepare(
@@ -2598,7 +2587,7 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
ORDER BY linked_at DESC"#, ORDER BY linked_at DESC"#,
)?; )?;
let rows = stmt.query_map( let rows = stmt.query_map(
params![workspace_id, runtime_id, runtime_worker_id], params![workspace_id, worker.runtime_id, worker.worker_id],
read_worker_workdir_link_record, read_worker_workdir_link_record,
)?; )?;
rows.collect::<std::result::Result<Vec<_>, _>>() rows.collect::<std::result::Result<Vec<_>, _>>()
@@ -2807,8 +2796,7 @@ fn read_worker_workdir_link_record(
) -> rusqlite::Result<WorkerWorkdirLinkRecord> { ) -> rusqlite::Result<WorkerWorkdirLinkRecord> {
Ok(WorkerWorkdirLinkRecord { Ok(WorkerWorkdirLinkRecord {
workspace_id: row.get(0)?, workspace_id: row.get(0)?,
runtime_id: row.get(1)?, worker: RuntimeWorkerRef::new(row.get::<_, String>(1)?, row.get::<_, u64>(2)?.to_string()),
runtime_worker_id: row.get(2)?,
workdir_id: row.get(3)?, workdir_id: row.get(3)?,
role: row.get(4)?, role: row.get(4)?,
linked_at: row.get(5)?, linked_at: row.get(5)?,
@@ -2900,8 +2888,7 @@ fn worker_registry_select_sql(where_clause: &str) -> String {
fn read_worker_registry_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<WorkerRegistryRecord> { fn read_worker_registry_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<WorkerRegistryRecord> {
Ok(WorkerRegistryRecord { Ok(WorkerRegistryRecord {
workspace_id: row.get(0)?, workspace_id: row.get(0)?,
runtime_id: row.get(1)?, worker: RuntimeWorkerRef::new(row.get::<_, String>(1)?, row.get::<_, u64>(2)?.to_string()),
runtime_worker_id: row.get(2)?,
display_name: row.get(3)?, display_name: row.get(3)?,
profile: row.get(4)?, profile: row.get(4)?,
retention_state: row.get(5)?, retention_state: row.get(5)?,
@@ -2933,8 +2920,7 @@ fn read_ticket_worker_assignment_record(
workspace_id: row.get(0)?, workspace_id: row.get(0)?,
ticket_id: row.get(1)?, ticket_id: row.get(1)?,
assignment_id: row.get(2)?, assignment_id: row.get(2)?,
runtime_id: row.get(3)?, worker: RuntimeWorkerRef::new(row.get::<_, String>(3)?, row.get::<_, String>(4)?),
worker_id: row.get(4)?,
assigned_by: row.get(5)?, assigned_by: row.get(5)?,
assigned_at: row.get(6)?, assigned_at: row.get(6)?,
}) })
@@ -2960,7 +2946,7 @@ pub struct TicketAssignmentOperationRecord {
pub action: String, pub action: String,
pub ticket_id: String, pub ticket_id: String,
pub runtime_id: Option<String>, pub runtime_id: Option<String>,
pub worker_id: Option<String>, pub worker: Option<RuntimeWorkerRef>,
pub assignment_id: Option<String>, pub assignment_id: Option<String>,
pub expected_assignment_id: Option<String>, pub expected_assignment_id: Option<String>,
pub request_fingerprint: Option<String>, pub request_fingerprint: Option<String>,
@@ -2978,11 +2964,15 @@ fn read_assignment_operation(
WHERE workspace_id = ?1 AND operation_id = ?2"#, WHERE workspace_id = ?1 AND operation_id = ?2"#,
params![workspace_id, operation_id], params![workspace_id, operation_id],
|row| { |row| {
let runtime_id: Option<String> = row.get(2)?;
let worker_id: Option<String> = row.get(3)?;
Ok(TicketAssignmentOperationRecord { Ok(TicketAssignmentOperationRecord {
action: row.get(0)?, action: row.get(0)?,
ticket_id: row.get(1)?, ticket_id: row.get(1)?,
runtime_id: row.get(2)?, runtime_id: runtime_id.clone(),
worker_id: row.get(3)?, worker: runtime_id
.zip(worker_id)
.map(|(runtime_id, worker_id)| RuntimeWorkerRef::new(runtime_id, worker_id)),
assignment_id: row.get(4)?, assignment_id: row.get(4)?,
expected_assignment_id: row.get(5)?, expected_assignment_id: row.get(5)?,
request_fingerprint: row.get(6)?, request_fingerprint: row.get(6)?,
@@ -4204,8 +4194,7 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
workspace_id: "workspace-a".to_string(), workspace_id: "workspace-a".to_string(),
ticket_id: "ticket-1".to_string(), ticket_id: "ticket-1".to_string(),
assignment_id: "assignment-1".to_string(), assignment_id: "assignment-1".to_string(),
runtime_id: "runtime-1".to_string(), worker: RuntimeWorkerRef::new("runtime-1", "worker-1"),
worker_id: "worker-1".to_string(),
assigned_by: "user-1".to_string(), assigned_by: "user-1".to_string(),
assigned_at: "2026-07-31T00:00:01Z".to_string(), assigned_at: "2026-07-31T00:00:01Z".to_string(),
}; };
@@ -4239,7 +4228,7 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
.set_current_ticket_worker_assignment( .set_current_ticket_worker_assignment(
&TicketWorkerAssignmentRecord { &TicketWorkerAssignmentRecord {
assignment_id: "implicit-reassign".to_string(), assignment_id: "implicit-reassign".to_string(),
worker_id: "worker-other".to_string(), worker: RuntimeWorkerRef::new("runtime-1", "worker-other"),
..first.clone() ..first.clone()
}, },
None, None,
@@ -4272,8 +4261,7 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
let second = TicketWorkerAssignmentRecord { let second = TicketWorkerAssignmentRecord {
assignment_id: "assignment-2".to_string(), assignment_id: "assignment-2".to_string(),
runtime_id: "runtime-2".to_string(), worker: RuntimeWorkerRef::new("runtime-2", "worker-2"),
worker_id: "worker-2".to_string(),
assigned_by: "user-2".to_string(), assigned_by: "user-2".to_string(),
assigned_at: "2026-07-31T00:00:02Z".to_string(), assigned_at: "2026-07-31T00:00:02Z".to_string(),
..first.clone() ..first.clone()
@@ -4360,7 +4348,7 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
.get_ticket_assignment_operation("workspace-a", "reserved-operation") .get_ticket_assignment_operation("workspace-a", "reserved-operation")
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(pending.worker_id, None); assert_eq!(pending.worker, None);
assert_eq!( assert_eq!(
pending.request_fingerprint.as_deref(), pending.request_fingerprint.as_deref(),
Some("sha256:reserved") Some("sha256:reserved")
@@ -4376,8 +4364,7 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
workspace_id: "workspace-a".to_string(), workspace_id: "workspace-a".to_string(),
ticket_id: "ticket-3".to_string(), ticket_id: "ticket-3".to_string(),
assignment_id: "assignment-3".to_string(), assignment_id: "assignment-3".to_string(),
runtime_id: "runtime-3".to_string(), worker: RuntimeWorkerRef::new("runtime-3", "worker-3"),
worker_id: "worker-3".to_string(),
assigned_by: "runtime".to_string(), assigned_by: "runtime".to_string(),
assigned_at: "2026-07-31T00:00:06Z".to_string(), assigned_at: "2026-07-31T00:00:06Z".to_string(),
}; };
@@ -4943,8 +4930,7 @@ CREATE TABLE ticket_assignment_operations (
let worker = WorkerRegistryRecord { let worker = WorkerRegistryRecord {
workspace_id: "local-dev".to_string(), workspace_id: "local-dev".to_string(),
runtime_id: "embedded".to_string(), worker: RuntimeWorkerRef::new("embedded", "1"),
runtime_worker_id: 1,
display_name: "Browser 1".to_string(), display_name: "Browser 1".to_string(),
profile: Some("builtin:companion".to_string()), profile: Some("builtin:companion".to_string()),
retention_state: "pinned".to_string(), retention_state: "pinned".to_string(),
@@ -4996,8 +4982,7 @@ CREATE TABLE ticket_assignment_operations (
let link = WorkerWorkdirLinkRecord { let link = WorkerWorkdirLinkRecord {
workspace_id: "local-dev".to_string(), workspace_id: "local-dev".to_string(),
runtime_id: worker.runtime_id.clone(), worker: worker.worker.clone(),
runtime_worker_id: worker.runtime_worker_id,
workdir_id: workdir.workdir_id.clone(), workdir_id: workdir.workdir_id.clone(),
role: "attachment".to_string(), role: "attachment".to_string(),
linked_at: "4".to_string(), linked_at: "4".to_string(),
@@ -5033,7 +5018,7 @@ CREATE TABLE ticket_assignment_operations (
assert_eq!( assert_eq!(
store store
.get_worker_registry("local-dev", "embedded", 1) .get_worker_registry("local-dev", &worker.worker)
.unwrap(), .unwrap(),
Some(expected_worker.clone()) Some(expected_worker.clone())
); );
@@ -5049,7 +5034,7 @@ CREATE TABLE ticket_assignment_operations (
); );
assert_eq!( assert_eq!(
store store
.list_worker_workdir_links("local-dev", "embedded", 1) .list_worker_workdir_links("local-dev", &worker.worker)
.unwrap(), .unwrap(),
vec![link.clone()] vec![link.clone()]
); );
@@ -5065,7 +5050,7 @@ CREATE TABLE ticket_assignment_operations (
)); ));
let second_worker = WorkerRegistryRecord { let second_worker = WorkerRegistryRecord {
runtime_worker_id: 2, worker: RuntimeWorkerRef::new("embedded", "2"),
display_name: "Browser 2".to_string(), display_name: "Browser 2".to_string(),
created_at: "5".to_string(), created_at: "5".to_string(),
updated_at: "5".to_string(), updated_at: "5".to_string(),
@@ -5073,7 +5058,7 @@ CREATE TABLE ticket_assignment_operations (
}; };
store.upsert_worker_registry(&second_worker).unwrap(); store.upsert_worker_registry(&second_worker).unwrap();
let workdir_conflict = WorkerWorkdirLinkRecord { let workdir_conflict = WorkerWorkdirLinkRecord {
runtime_worker_id: second_worker.runtime_worker_id, worker: second_worker.worker.clone(),
linked_at: "5".to_string(), linked_at: "5".to_string(),
..link.clone() ..link.clone()
}; };
@@ -5082,17 +5067,17 @@ CREATE TABLE ticket_assignment_operations (
Err(Error::WorkdirAttachmentConflict(_)) Err(Error::WorkdirAttachmentConflict(_))
)); ));
assert!(matches!( assert!(matches!(
store.detach_worker_workdir("local-dev", "embedded", 1, Some("wrong-workdir"), "6"), store.detach_worker_workdir("local-dev", &worker.worker, Some("wrong-workdir"), "6",),
Err(Error::WorkdirAttachmentConflict(_)) Err(Error::WorkdirAttachmentConflict(_))
)); ));
let detached = store let detached = store
.detach_worker_workdir("local-dev", "embedded", 1, Some(&workdir.workdir_id), "6") .detach_worker_workdir("local-dev", &worker.worker, Some(&workdir.workdir_id), "6")
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(detached.unlinked_at.as_deref(), Some("6")); assert_eq!(detached.unlinked_at.as_deref(), Some("6"));
assert!( assert!(
store store
.worker_workdir_link_history_exists("local-dev", "embedded", 1) .worker_workdir_link_history_exists("local-dev", &worker.worker)
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
@@ -8,6 +8,7 @@ use protocol::subscription::{
SubscriptionResponse, SubscriptionSnapshot, SubscriptionTerminationCode, SubscriptionWorker, SubscriptionResponse, SubscriptionSnapshot, SubscriptionTerminationCode, SubscriptionWorker,
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use worker_runtime::identity::RuntimeWorkerRef;
use crate::runtime_subscription::{BrokerSubscriptionEvent, RuntimeSubscriptionBroker}; use crate::runtime_subscription::{BrokerSubscriptionEvent, RuntimeSubscriptionBroker};
use crate::server::{WorkspaceApi, connect_workspace_worker_protocol}; use crate::server::{WorkspaceApi, connect_workspace_worker_protocol};
@@ -81,13 +82,8 @@ pub(crate) async fn serve_workspace_subscription(api: WorkspaceApi, socket: WebS
worker_id, worker_id,
runtime_id: Some(runtime_id), runtime_id: Some(runtime_id),
} => { } => {
match connect_workspace_worker_protocol( let worker = RuntimeWorkerRef::new(&runtime_id, worker_id.as_str());
&api, match connect_workspace_worker_protocol(&api, &worker).await {
&runtime_id,
worker_id.as_str(),
)
.await
{
Ok(connection) => { Ok(connection) => {
let methods = connection.methods.clone(); let methods = connection.methods.clone();
let task = tokio::spawn(run_worker_protocol( let task = tokio::spawn(run_worker_protocol(
@@ -323,16 +319,15 @@ async fn run_workspace_workers(
} }
} }
let mut revisions = HashMap::<String, u64>::new(); let mut revisions = HashMap::<RuntimeWorkerRef, u64>::new();
let mut initial_workers = workers let mut initial_workers = Vec::new();
.values_mut() for (runtime_id, runtime) in &mut workers {
.flat_map(|runtime| runtime.values_mut()) for worker in runtime.values_mut() {
.map(|worker| { let worker_ref = RuntimeWorkerRef::new(runtime_id, worker.worker_id.as_str());
let key = worker_key(worker.runtime_id.as_deref(), worker.worker_id.as_str()); worker.subject_revision = next_revision(&mut revisions, &worker_ref);
worker.subject_revision = next_revision(&mut revisions, &key); initial_workers.push(worker.clone());
worker.clone() }
}) }
.collect::<Vec<_>>();
sort_workers(&mut initial_workers); sort_workers(&mut initial_workers);
if send_frame( if send_frame(
&outbound, &outbound,
@@ -359,8 +354,8 @@ async fn run_workspace_workers(
BrokerSubscriptionEvent::Snapshot { snapshot, .. } => { BrokerSubscriptionEvent::Snapshot { snapshot, .. } => {
let removed = workers.remove(&runtime_id).unwrap_or_default(); let removed = workers.remove(&runtime_id).unwrap_or_default();
for worker in removed.values() { for worker in removed.values() {
let key = worker_key(Some(&runtime_id), worker.worker_id.as_str()); let worker_ref = RuntimeWorkerRef::new(&runtime_id, worker.worker_id.as_str());
let revision = next_revision(&mut revisions, &key); let revision = next_revision(&mut revisions, &worker_ref);
if send_event( if send_event(
&outbound, &outbound,
&subscription_id, &subscription_id,
@@ -379,8 +374,9 @@ async fn run_workspace_workers(
install_snapshot(&mut workers, &runtime_id, snapshot); install_snapshot(&mut workers, &runtime_id, snapshot);
if let Some(current) = workers.get_mut(&runtime_id) { if let Some(current) = workers.get_mut(&runtime_id) {
for worker in current.values_mut() { for worker in current.values_mut() {
let key = worker_key(Some(&runtime_id), worker.worker_id.as_str()); let worker_ref =
let revision = next_revision(&mut revisions, &key); RuntimeWorkerRef::new(&runtime_id, worker.worker_id.as_str());
let revision = next_revision(&mut revisions, &worker_ref);
worker.subject_revision = revision; worker.subject_revision = revision;
if send_event( if send_event(
&outbound, &outbound,
@@ -401,8 +397,8 @@ async fn run_workspace_workers(
BrokerSubscriptionEvent::Event { payload, .. } => match payload { BrokerSubscriptionEvent::Event { payload, .. } => match payload {
SubscriptionEventPayload::WorkerUpserted { mut worker } => { SubscriptionEventPayload::WorkerUpserted { mut worker } => {
worker.runtime_id = Some(runtime_id.clone()); worker.runtime_id = Some(runtime_id.clone());
let key = worker_key(Some(&runtime_id), worker.worker_id.as_str()); let worker_ref = RuntimeWorkerRef::new(&runtime_id, worker.worker_id.as_str());
let revision = next_revision(&mut revisions, &key); let revision = next_revision(&mut revisions, &worker_ref);
worker.subject_revision = revision; worker.subject_revision = revision;
workers workers
.entry(runtime_id) .entry(runtime_id)
@@ -425,8 +421,8 @@ async fn run_workspace_workers(
.entry(runtime_id.clone()) .entry(runtime_id.clone())
.or_default() .or_default()
.remove(worker_id.as_str()); .remove(worker_id.as_str());
let key = worker_key(Some(&runtime_id), worker_id.as_str()); let worker_ref = RuntimeWorkerRef::new(&runtime_id, worker_id.as_str());
let revision = next_revision(&mut revisions, &key); let revision = next_revision(&mut revisions, &worker_ref);
if send_event( if send_event(
&outbound, &outbound,
&subscription_id, &subscription_id,
@@ -534,14 +530,11 @@ async fn send_frame(
.map_err(|_| ()) .map_err(|_| ())
} }
fn next_revision(revisions: &mut HashMap<String, u64>, key: &str) -> u64 { fn next_revision(revisions: &mut HashMap<RuntimeWorkerRef, u64>, worker: &RuntimeWorkerRef) -> u64 {
let revision = revisions.entry(key.to_string()).or_insert(0); let revision = revisions.entry(worker.clone()).or_insert(0);
*revision = revision.saturating_add(1); *revision = revision.saturating_add(1);
*revision *revision
} }
fn worker_key(runtime_id: Option<&str>, worker_id: &str) -> String {
format!("{}:{worker_id}", runtime_id.unwrap_or_default())
}
fn sort_workers(workers: &mut [SubscriptionWorker]) { fn sort_workers(workers: &mut [SubscriptionWorker]) {
workers.sort_by(|left, right| { workers.sort_by(|left, right| {
left.runtime_id left.runtime_id