feat: add workspace runtime registry source boundary

This commit is contained in:
Keisuke Hirata 2026-06-26 04:31:59 +09:00
parent 40d4138068
commit f6fd7b6323
No known key found for this signature in database
2 changed files with 95 additions and 15 deletions

View File

@ -44,6 +44,69 @@ pub enum DiagnosticSeverity {
Error, Error,
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSourceKind {
/// Compatibility projection over the existing local Pod metadata store.
LocalCompatibility,
/// Reserved boundary for the future in-process worker-runtime Runtime adapter.
EmbeddedWorkerRuntime,
/// Reserved boundary for a future remote Workspace Runtime adapter.
RemoteHttp,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSourceStatus {
Active,
Reserved,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeIdentityAuthority {
/// Public Runtime/Host/Worker ids are registry projections, never raw
/// compatibility-store names, socket addresses, session ids, or paths.
RuntimeRegistryProjection,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeSourceSummary {
pub kind: RuntimeSourceKind,
pub status: RuntimeSourceStatus,
pub identity_authority: RuntimeIdentityAuthority,
pub note: String,
}
impl RuntimeSourceSummary {
pub fn local_compatibility() -> Self {
Self {
kind: RuntimeSourceKind::LocalCompatibility,
status: RuntimeSourceStatus::Active,
identity_authority: RuntimeIdentityAuthority::RuntimeRegistryProjection,
note: "read-only compatibility projection over local Worker metadata; no socket, session, or path authority is exposed".to_string(),
}
}
pub fn embedded_worker_runtime_reserved() -> Self {
Self {
kind: RuntimeSourceKind::EmbeddedWorkerRuntime,
status: RuntimeSourceStatus::Reserved,
identity_authority: RuntimeIdentityAuthority::RuntimeRegistryProjection,
note: "reserved boundary for a future embedded worker-runtime adapter; not connected in this registry foundation".to_string(),
}
}
pub fn remote_http_reserved() -> Self {
Self {
kind: RuntimeSourceKind::RemoteHttp,
status: RuntimeSourceStatus::Reserved,
identity_authority: RuntimeIdentityAuthority::RuntimeRegistryProjection,
note: "reserved boundary for a future remote Runtime adapter; no HTTP client or REST server is implemented here".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeCapabilitySummary { pub struct RuntimeCapabilitySummary {
pub can_list_hosts: bool, pub can_list_hosts: bool,
@ -74,6 +137,7 @@ pub struct RuntimeSummary {
pub label: String, pub label: String,
pub kind: String, pub kind: String,
pub status: String, pub status: String,
pub source: RuntimeSourceSummary,
pub host_ids: Vec<String>, pub host_ids: Vec<String>,
pub capabilities: RuntimeCapabilitySummary, pub capabilities: RuntimeCapabilitySummary,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
@ -317,11 +381,11 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct WorkerRuntimeRegistry { pub struct RuntimeRegistry {
runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>, runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>,
} }
impl WorkerRuntimeRegistry { impl RuntimeRegistry {
pub fn new(runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>) -> Self { pub fn new(runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>) -> Self {
Self { runtimes } Self { runtimes }
} }
@ -582,6 +646,7 @@ impl WorkspaceWorkerRuntime for LocalWorkerRuntime {
label: "Local Worker runtime".to_string(), label: "Local Worker runtime".to_string(),
kind: "local_pod".to_string(), kind: "local_pod".to_string(),
status: "available".to_string(), status: "available".to_string(),
source: RuntimeSourceSummary::local_compatibility(),
host_ids: host_list host_ids: host_list
.items .items
.iter() .iter()
@ -1011,7 +1076,7 @@ mod tests {
fn registry_lists_runtimes_hosts_and_workers() { fn registry_lists_runtimes_hosts_and_workers() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project"))); write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project")));
let registry = WorkerRuntimeRegistry::for_local_pods(LocalWorkerRuntime::new( let registry = RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new(
"local:test", "local:test",
"/workspace/project", "/workspace/project",
Some(temp.path().to_path_buf()), Some(temp.path().to_path_buf()),
@ -1019,6 +1084,14 @@ mod tests {
let runtimes = registry.list_runtimes(10); let runtimes = registry.list_runtimes(10);
assert_eq!(runtimes.items[0].runtime_id, LOCAL_RUNTIME_ID); assert_eq!(runtimes.items[0].runtime_id, LOCAL_RUNTIME_ID);
assert_eq!(
runtimes.items[0].source.kind,
RuntimeSourceKind::LocalCompatibility
);
assert_eq!(
runtimes.items[0].source.identity_authority,
RuntimeIdentityAuthority::RuntimeRegistryProjection
);
assert_eq!(runtimes.items[0].host_ids, vec![host_id()]); assert_eq!(runtimes.items[0].host_ids, vec![host_id()]);
let hosts = registry.list_hosts(10); let hosts = registry.list_hosts(10);
@ -1043,7 +1116,7 @@ mod tests {
fn registry_resolves_backend_validated_host_ids() { fn registry_resolves_backend_validated_host_ids() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project"))); write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project")));
let registry = WorkerRuntimeRegistry::for_local_pods(LocalWorkerRuntime::new( let registry = RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new(
"local:test", "local:test",
"/workspace/project", "/workspace/project",
Some(temp.path().to_path_buf()), Some(temp.path().to_path_buf()),
@ -1133,7 +1206,7 @@ mod tests {
"/workspace/project", "/workspace/project",
Some(temp.path().to_path_buf()), Some(temp.path().to_path_buf()),
); );
let registry = WorkerRuntimeRegistry::for_local_pods(bridge.clone()); let registry = RuntimeRegistry::for_local_pods(bridge.clone());
let listed = registry.list_workers(100); let listed = registry.list_workers(100);
assert_eq!(listed.items.len(), worker_names.len()); assert_eq!(listed.items.len(), worker_names.len());

View File

@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use crate::hosts::{ use crate::hosts::{
DiagnosticSeverity, HostSummary, LocalWorkerRuntime, RuntimeDiagnostic, RuntimeSummary, DiagnosticSeverity, HostSummary, LocalWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry,
WorkerRuntimeRegistry, WorkerSummary, RuntimeSummary, WorkerSummary,
}; };
use crate::identity::WorkspaceIdentity; use crate::identity::WorkspaceIdentity;
use crate::records::{ use crate::records::{
@ -64,7 +64,7 @@ pub struct WorkspaceApi {
config: ServerConfig, config: ServerConfig,
store: Arc<dyn ControlPlaneStore>, store: Arc<dyn ControlPlaneStore>,
records: LocalProjectRecordReader, records: LocalProjectRecordReader,
runtime: Arc<WorkerRuntimeRegistry>, runtime: Arc<RuntimeRegistry>,
} }
impl WorkspaceApi { impl WorkspaceApi {
@ -78,13 +78,11 @@ impl WorkspaceApi {
updated_at: config.workspace_created_at.clone(), updated_at: config.workspace_created_at.clone(),
}) })
.await?; .await?;
let runtime = Arc::new(WorkerRuntimeRegistry::for_local_pods( let runtime = Arc::new(RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new(
LocalWorkerRuntime::new( config.workspace_id.clone(),
config.workspace_id.clone(), config.workspace_root.clone(),
config.workspace_root.clone(), config.local_runtime_data_dir.clone(),
config.local_runtime_data_dir.clone(), )));
),
));
Ok(Self { Ok(Self {
records: LocalProjectRecordReader::new(config.workspace_root.clone()), records: LocalProjectRecordReader::new(config.workspace_root.clone()),
config, config,
@ -746,6 +744,15 @@ mod tests {
let runtimes = get_json(app.clone(), "/api/runtimes").await; let runtimes = get_json(app.clone(), "/api/runtimes").await;
assert_eq!(runtimes["source"], "worker_runtime_registry"); assert_eq!(runtimes["source"], "worker_runtime_registry");
assert_eq!(runtimes["items"][0]["runtime_id"], "local-worker-runtime"); assert_eq!(runtimes["items"][0]["runtime_id"], "local-worker-runtime");
assert_eq!(
runtimes["items"][0]["source"]["kind"],
"local_compatibility"
);
assert_eq!(
runtimes["items"][0]["source"]["identity_authority"],
"runtime_registry_projection"
);
assert!(!runtimes.to_string().contains("/workspace/demo"));
assert_eq!(runtimes["items"][0]["host_ids"][0], host_id); assert_eq!(runtimes["items"][0]["host_ids"][0], host_id);
let workers = get_json(app.clone(), "/api/workers").await; let workers = get_json(app.clone(), "/api/workers").await;