use std::collections::{HashMap, HashSet}; use std::path::{Component, Path, PathBuf}; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade}; use axum::extract::{Path as AxumPath, Query, State}; use axum::http::header::{CONTENT_TYPE, ETAG, IF_NONE_MATCH, LOCATION, ORIGIN, SET_COOKIE}; use axum::http::{HeaderMap, StatusCode, Uri}; use axum::response::{IntoResponse, Response}; use axum::routing::{delete, get, post, put}; use axum::{Json, Router}; use chrono::{Duration, SecondsFormat, Utc}; use futures::{SinkExt, StreamExt}; use memory::backend::{ MemoryBackendHttpResponse, MemoryBackendOperation, MemoryConsolidateStagingOperation, MemoryConsolidationOutput, }; use protocol::stream::{decode_method, encode_event}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use ticket::{ SqliteTicketBackend, TicketBackendHttpResponse, TicketBackendOperation, execute_ticket_backend_operation, }; use tokio::net::TcpListener; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; use tokio_tungstenite::tungstenite::client::IntoClientRequest; use url::Url; use uuid::Uuid; use webauthn_rs::prelude::{ CreationChallengeResponse, Passkey, PasskeyAuthentication, PasskeyRegistration, PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse, Webauthn, WebauthnBuilder, }; use worker_runtime::resource::{BackendResourceError, BackendResourceFetchRequest}; use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend}; use crate::auth::{ AuthPublicConfig, AuthenticatedUser, RequestActor, auth_error, is_expired, mint_secret, new_id, new_user_code, normalize_handle, parse_cookie, resolve_request_actor, rfc3339_after, session_set_cookie, token_hash, }; use crate::authority::{ MemoryAuthority, ObjectiveAuthority, SqliteWorkspaceAuthority, TicketAuthority, }; use crate::companion::{ CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse, CompanionStatusResponse, CompanionTranscriptProjection, }; use crate::config::{BackendRuntimesConfigFile, RemoteRuntimeConfigFile, resolve_remote_runtime}; use crate::hosts::{ ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EmbeddedWorkerRuntime, HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry, RuntimeRegistryError, RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerCapabilitySummary, WorkerCompletionsRequest, WorkerCompletionsResult, WorkerImplementationSummary, WorkerInputKind, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerWorkspaceSummary, }; use crate::identity::WorkspaceIdentity; use crate::memory_backend::execute_memory_backend_operation_with_authority; use crate::memory_staging::{ MemoryStagingListResponse, list_memory_staging_from_authority, memory_staging_backlog_from_authority, }; use crate::observation::{ BackendObservationProxy, ObservationProxyError, RuntimeObservationClient, RuntimeObservationSource, RuntimeObservationSourceConfig, }; use crate::profile_settings::{ CreateWorkspaceProfileSourceRequest, DeleteWorkspaceProfileSourceRequest, DeleteWorkspaceProfileTreeFileRequest, ReadWorkspaceProfileTreeFileQuery, UpdateWorkspaceMetadataRequest, UpdateWorkspaceProfileRegistryRequest, UpdateWorkspaceProfileSourceRequest, WriteWorkspaceProfileTreeFileRequest, }; use crate::records::{ObjectiveDetail, ProjectRecordList, TicketDetail, TicketSummary}; use crate::repositories::{ ConfiguredRepository, RepositoryListProjection, RepositoryLogRead, RepositoryLookupError, RepositoryRegistryReader, RepositorySummary, }; use crate::resource_broker::BackendResourceBroker; use crate::skills; use crate::store::{ AccountRecord, ApiTokenRecord, AuthChallengeRecord, BrowserSessionRecord, ControlPlaneStore, DeviceLoginFlowRecord, PasskeyCredentialRecord, RepositoryRecord, UserRecord, WorkdirRegistryRecord, WorkerRegistryRecord, WorkerWorkdirLinkRecord, WorkspaceRecord, }; use crate::{Error, Result}; use worker_runtime::catalog::{ ConfigBundleRef, MaterializerKind, ProfileSelector, RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryClaim, WorkingDirectoryOccupancy, WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectoryStatusKind, WorkingDirectorySummary, }; use worker_runtime::config_bundle::ConfigBundle; use worker_runtime::http_server::{ RuntimeHttpConfigBundleAvailabilityResponse, RuntimeHttpConfigBundlesResponse, RuntimeHttpSummaryResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse, }; use worker_runtime::identity::WorkerId; use worker_runtime::interaction::{ WorkerInput as EmbeddedWorkerInput, WorkerInputKind as EmbeddedWorkerInputKind, }; const EMBEDDED_WORKER_RUNTIME_ID: &str = "embedded-worker-runtime"; fn embedded_runtime_id() -> String { EMBEDDED_WORKER_RUNTIME_ID.to_string() } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum AuthConfig { /// Browser human auth uses Passkey ceremonies and HttpOnly cookie sessions; /// CLI/TUI auth uses API tokens obtained through the device login flow. Passkey { rp_id: String, origin: String, public_base_url: String, cookie_name: String, }, } #[derive(Clone)] pub struct ServerConfig { pub workspace_id: String, pub workspace_display_name: String, pub workspace_created_at: String, pub workspace_root: PathBuf, pub database_path: PathBuf, pub frontend_url: String, pub embedded_runtime_store_root: PathBuf, pub static_assets_dir: Option, pub auth: AuthConfig, pub max_records: usize, pub repositories: Vec, pub runtime_event_sources: Vec, pub remote_runtime_sources: Vec, pub runtime_config_path: Option, pub backend_base_url: Option, } impl ServerConfig { pub fn local_dev(workspace_root: impl Into, identity: WorkspaceIdentity) -> Self { let workspace_root = workspace_root.into(); let workspace_id = identity.workspace_id; let embedded_runtime_store_root = Self::default_embedded_runtime_store_root(&workspace_id); let database_path = Self::default_server_database_path(); Self { workspace_id, workspace_display_name: identity.display_name, workspace_created_at: identity.created_at, workspace_root, database_path, frontend_url: "http://127.0.0.1:5173".to_string(), embedded_runtime_store_root, static_assets_dir: None, auth: AuthConfig::Passkey { rp_id: "localhost".to_string(), origin: "http://localhost:8787".to_string(), public_base_url: "http://localhost:8787".to_string(), cookie_name: "yoi_workspace_session".to_string(), }, max_records: 200, repositories: Vec::new(), runtime_event_sources: Vec::new(), remote_runtime_sources: Vec::new(), runtime_config_path: BackendRuntimesConfigFile::default_path(), backend_base_url: None, } } pub fn server_data_root_for_data_dir(data_dir: impl Into) -> PathBuf { data_dir.into().join("server") } pub fn default_server_data_root() -> PathBuf { match manifest::paths::data_dir() { Some(data_dir) => Self::server_data_root_for_data_dir(data_dir), None => std::env::temp_dir().join("yoi").join("server"), } } pub fn server_database_path_for_data_dir(data_dir: impl Into) -> PathBuf { Self::server_data_root_for_data_dir(data_dir).join("server.db") } pub fn default_server_database_path() -> PathBuf { Self::default_server_data_root().join("server.db") } pub fn workspace_backend_data_root_for_data_dir( data_dir: impl Into, workspace_id: impl AsRef, ) -> PathBuf { Self::server_data_root_for_data_dir(data_dir) .join("workspaces") .join(workspace_id.as_ref()) } pub fn default_workspace_backend_data_root(workspace_id: impl AsRef) -> PathBuf { match manifest::paths::data_dir() { Some(data_dir) => { Self::workspace_backend_data_root_for_data_dir(data_dir, workspace_id.as_ref()) } None => std::env::temp_dir() .join("yoi") .join("server") .join("workspaces") .join(workspace_id.as_ref()), } } pub fn embedded_runtime_store_root_for_data_dir( data_dir: impl Into, workspace_id: impl AsRef, ) -> PathBuf { Self::workspace_backend_data_root_for_data_dir(data_dir, workspace_id) .join("embedded-runtime") } pub fn default_embedded_runtime_store_root(workspace_id: impl AsRef) -> PathBuf { Self::default_workspace_backend_data_root(workspace_id).join("embedded-runtime") } pub fn with_embedded_runtime_store_root(mut self, root: impl Into) -> Self { self.embedded_runtime_store_root = root.into(); self } } #[derive(Clone)] pub struct WorkspaceApi { config: ServerConfig, store: Arc, authority: SqliteWorkspaceAuthority, runtime: Arc, companion: Arc, observation_proxy: BackendObservationProxy, resource_broker: BackendResourceBroker, } impl WorkspaceApi { pub async fn new(config: ServerConfig, store: Arc) -> Result { let resource_broker = BackendResourceBroker::default(); let execution_backend = WorkerRuntimeExecutionBackend::new( ProfileRuntimeWorkerFactory::new(config.workspace_root.clone()) .with_resource_client(Arc::new(resource_broker.clone())), ) .map_err(|err| { crate::Error::Store(format!( "failed to initialize embedded Worker backend: {err}" )) })?; Self::new_with_execution_backend_and_broker( config, store, Arc::new(execution_backend), resource_broker, ) .await } #[cfg(test)] async fn new_with_execution_backend( config: ServerConfig, store: Arc, execution_backend: Arc, ) -> Result { Self::new_with_execution_backend_and_broker( config, store, execution_backend, BackendResourceBroker::default(), ) .await } async fn new_with_execution_backend_and_broker( mut config: ServerConfig, store: Arc, execution_backend: Arc, resource_broker: BackendResourceBroker, ) -> Result { store .upsert_workspace(&WorkspaceRecord { workspace_id: config.workspace_id.clone(), owner_account_id: None, display_name: config.workspace_display_name.clone(), state: "active".to_string(), created_at: config.workspace_created_at.clone(), updated_at: config.workspace_created_at.clone(), }) .await?; import_configured_repositories(store.as_ref(), &config)?; config.repositories = load_configured_repositories_from_store(store.as_ref(), &config)?; let runtime = RuntimeRegistry::for_workspace( EmbeddedWorkerRuntime::new_fs_store_with_execution_backend( config.workspace_id.clone(), config.embedded_runtime_store_root.clone(), execution_backend, ) .map(|runtime| runtime.with_resource_broker(resource_broker.clone())) .map(|runtime| { runtime.with_backend_base_url( config .backend_base_url .clone() .unwrap_or_else(|| "http://127.0.0.1:8787".to_string()), ) }) .map_err(|err| { crate::Error::Store(format!("invalid embedded Worker backend: {err}")) })?, ); for remote_config in config.remote_runtime_sources.iter().cloned() { runtime.register( RemoteWorkerRuntime::new( remote_config, config.workspace_id.clone(), config .backend_base_url .clone() .unwrap_or_else(|| "http://127.0.0.1:8787".to_string()), ) .map(|host| host.with_resource_broker(resource_broker.clone())) .map_err(|err| err.into_error())?, ); } let runtime = Arc::new(runtime); let companion = Arc::new(CompanionConsole::disabled()); let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone()); Ok(Self { authority: SqliteWorkspaceAuthority::new( config.database_path.clone(), config.workspace_id.clone(), )?, config, store, runtime, companion, observation_proxy, resource_broker, }) } pub fn workspace_id(&self) -> &str { self.config.workspace_id.as_str() } fn repository_reader(&self) -> RepositoryRegistryReader { RepositoryRegistryReader::new(self.config.repositories.clone()) } } fn import_configured_repositories( store: &dyn ControlPlaneStore, config: &ServerConfig, ) -> Result<()> { if config.repositories.is_empty() { return Ok(()); } let now = crate::auth::now_rfc3339(); for repository in &config.repositories { store.upsert_repository(&RepositoryRecord { workspace_id: config.workspace_id.clone(), repository_id: repository.id.clone(), name: repository .display_name .clone() .unwrap_or_else(|| repository.id.clone()), kind: repository.provider.clone(), provider: Some(repository.provider.clone()), uri: repository.uri.clone(), default_ref: repository.default_selector.clone(), auth_ref_kind: None, auth_ref_key: None, created_at: now.clone(), updated_at: now.clone(), })?; } Ok(()) } fn load_configured_repositories_from_store( store: &dyn ControlPlaneStore, config: &ServerConfig, ) -> Result> { store .list_repositories(&config.workspace_id)? .into_iter() .map(|record| configured_repository_from_record(&config.workspace_root, record)) .collect() } fn configured_repository_from_record( workspace_root: &Path, record: RepositoryRecord, ) -> Result { let provider = record.provider.unwrap_or_else(|| record.kind.clone()); if record.uri.contains("://") { return Err(Error::Config(format!( "repository `{}` uses a remote URI, but remote repository materialization is not implemented", record.repository_id ))); } let path = resolve_backend_path(workspace_root, Path::new(&record.uri)); Ok(ConfiguredRepository { id: record.repository_id, provider, uri: record.uri, path, display_name: Some(record.name), default_selector: record.default_ref, }) } fn resolve_backend_path(workspace_root: &Path, path: &Path) -> PathBuf { if path.is_absolute() { path.to_path_buf() } else { workspace_root.join(path) } } pub fn build_router(api: WorkspaceApi) -> Router { Router::new() .route("/api/auth/config", get(get_auth_config)) .route("/api/auth/bootstrap-user", post(post_auth_bootstrap_user)) .route( "/api/auth/passkeys/registration/options", post(post_passkey_registration_options), ) .route( "/api/auth/passkeys/registration/complete", post(post_passkey_registration_complete), ) .route( "/api/auth/passkeys/login/options", post(post_passkey_login_options), ) .route( "/api/auth/passkeys/login/complete", post(post_passkey_login_complete), ) .route("/api/auth/logout", post(post_auth_logout)) .route("/api/auth/device-login/start", post(post_device_login_start)) .route("/api/auth/device-login/approve", post(post_device_login_approve)) .route("/api/auth/device-login/poll", post(post_device_login_poll)) .route("/api/auth/whoami", get(get_auth_whoami)) .route("/api/workspace", get(get_workspace)) .route("/api/w/{workspace_id}/workspace", get(scoped_get_workspace)) .route( "/api/w/{workspace_id}/settings/workspace", get(scoped_get_workspace_settings).put(scoped_update_workspace_settings), ) .route( "/api/w/{workspace_id}/settings/profiles", get(scoped_get_profile_settings).post(scoped_create_profile_source), ) .route( "/api/w/{workspace_id}/settings/profiles/registry", put(scoped_update_profile_registry), ) .route( "/api/w/{workspace_id}/settings/profiles/trees/{source_tree_id}", get(scoped_get_profile_source_tree), ) .route( "/api/w/{workspace_id}/settings/profiles/trees/{source_tree_id}/file", get(scoped_get_profile_tree_file) .put(scoped_write_profile_tree_file) .delete(scoped_delete_profile_tree_file), ) .route( "/api/w/{workspace_id}/settings/profiles/{profile_source_id}", get(scoped_get_profile_source) .put(scoped_update_profile_source) .delete(scoped_delete_profile_source), ) .route("/api/tickets", get(list_tickets)) .route("/api/w/{workspace_id}/tickets", get(scoped_list_tickets)) .route( "/api/w/{workspace_id}/tickets/backend", post(scoped_ticket_backend_operation), ) .route( "/api/w/{workspace_id}/memory", get(scoped_get_memory_document), ) .route( "/api/w/{workspace_id}/memory/staging", get(scoped_list_memory_staging), ) .route( "/api/w/{workspace_id}/memory/backend", post(scoped_memory_backend_operation), ) .route( "/api/w/{workspace_id}/memory/consolidation", post(scoped_memory_consolidation), ) .route("/api/tickets/{id}", get(get_ticket)) .route("/api/w/{workspace_id}/skills", get(scoped_list_skills)) .route("/api/w/{workspace_id}/skills/lint", get(scoped_lint_skills)) .route("/api/w/{workspace_id}/skills/{name}", get(scoped_get_skill)) .route( "/api/w/{workspace_id}/skills/{name}/activate", get(scoped_activate_skill), ) .route("/api/w/{workspace_id}/tickets/{id}", get(scoped_get_ticket)) .route("/api/objectives", get(list_objectives)) .route( "/api/w/{workspace_id}/objectives", get(scoped_list_objectives), ) .route("/api/objectives/{id}", get(get_objective)) .route( "/api/w/{workspace_id}/objectives/{id}", get(scoped_get_objective), ) .route("/api/repositories", get(list_repositories)) .route( "/api/w/{workspace_id}/repositories", get(scoped_list_repositories), ) .route("/api/repositories/{repository_id}", get(repository_detail)) .route( "/api/w/{workspace_id}/repositories/{repository_id}", get(scoped_repository_detail), ) .route("/api/repositories/{repository_id}/log", get(repository_log)) .route( "/api/w/{workspace_id}/repositories/{repository_id}/log", get(scoped_repository_log), ) .route( "/api/repositories/{repository_id}/tickets", get(repository_tickets), ) .route( "/api/w/{workspace_id}/repositories/{repository_id}/tickets", get(scoped_repository_tickets), ) .route("/api/hosts", get(list_hosts)) .route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts)) .route( "/api/w/{workspace_id}/profile-source-archives/{digest}", get(scoped_get_profile_source_archive), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", get(scoped_list_runtime_working_directories).post(scoped_create_runtime_working_directory), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", get(scoped_runtime_working_directory_detail).delete(scoped_cleanup_runtime_working_directory), ) .route( "/api/w/{workspace_id}/working-directories", get(scoped_list_working_directories).post(scoped_create_working_directory), ) .route( "/api/w/{workspace_id}/working-directories/{working_directory_id}", get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory), ) .route("/api/runtimes", get(list_runtimes)) .route("/api/w/{workspace_id}/runtimes", get(scoped_list_runtimes)) .route( "/api/workers", get(list_workers).post(create_workspace_worker), ) .route( "/api/w/{workspace_id}/workers", get(scoped_list_workers).post(scoped_create_workspace_worker), ) .route( "/api/workers/launch-options", get(get_worker_launch_options), ) .route( "/api/w/{workspace_id}/workers/launch-options", get(scoped_get_worker_launch_options), ) .route( "/api/settings/runtime-connections", get(get_runtime_connection_settings), ) .route( "/api/w/{workspace_id}/settings/runtime-connections", get(scoped_get_runtime_connection_settings), ) .route( "/api/settings/runtime-connections/remotes", post(add_remote_runtime_connection), ) .route( "/api/w/{workspace_id}/settings/runtime-connections/remotes", post(scoped_add_remote_runtime_connection), ) .route( "/api/settings/runtime-connections/remotes/{runtime_id}", delete(delete_remote_runtime_connection), ) .route( "/api/w/{workspace_id}/settings/runtime-connections/remotes/{runtime_id}", delete(scoped_delete_remote_runtime_connection), ) .route( "/api/settings/runtime-connections/remotes/{runtime_id}/test", post(test_remote_runtime_connection), ) .route( "/api/w/{workspace_id}/settings/runtime-connections/remotes/{runtime_id}/test", post(scoped_test_remote_runtime_connection), ) .route( "/internal/runtime/resources/fetch", post(post_internal_runtime_resource_fetch), ) .route("/api/companion/status", get(get_companion_status)) .route( "/api/w/{workspace_id}/companion/status", get(scoped_get_companion_status), ) .route("/api/companion/transcript", get(get_companion_transcript)) .route( "/api/w/{workspace_id}/companion/transcript", get(scoped_get_companion_transcript), ) .route("/api/companion/messages", post(post_companion_message)) .route( "/api/w/{workspace_id}/companion/messages", post(scoped_post_companion_message), ) .route("/api/companion/cancel", post(post_companion_cancel)) .route( "/api/w/{workspace_id}/companion/cancel", post(scoped_post_companion_cancel), ) .route( "/api/runtimes/{runtime_id}/workers", get(list_runtime_workers).post(create_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers", get(scoped_list_runtime_workers).post(scoped_create_runtime_worker), ) .route( "/api/runtimes/{runtime_id}/config-bundles", post(sync_runtime_config_bundle), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles", post(scoped_sync_runtime_config_bundle), ) .route( "/api/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", get(check_runtime_config_bundle), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability", get(scoped_check_runtime_config_bundle), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}", get(get_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}", get(scoped_get_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/pin", put(scoped_pin_runtime_worker).delete(scoped_unpin_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan", get(scoped_runtime_cleanup_plan), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-executions", post(scoped_execute_runtime_cleanup), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}/input", post(send_runtime_worker_input), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/input", post(scoped_send_runtime_worker_input), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}/completions", post(runtime_worker_completions), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/completions", post(scoped_runtime_worker_completions), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}/stop", post(stop_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/stop", post(scoped_stop_runtime_worker), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}/cancel", post(cancel_runtime_worker), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/cancel", post(scoped_cancel_runtime_worker), ) .route( "/api/runtimes/{runtime_id}/workers/{worker_id}/protocol/ws", get(worker_protocol_ws), ) .route( "/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/protocol/ws", get(scoped_worker_protocol_ws), ) .route("/api/hosts/{host_id}/workers", get(list_host_workers)) .route( "/api/w/{workspace_id}/hosts/{host_id}/workers", get(scoped_list_host_workers), ) .fallback(get(static_or_spa_fallback)) .with_state(api) } pub async fn serve( config: ServerConfig, store: Arc, listener: TcpListener, ) -> Result<()> { let api = WorkspaceApi::new(config, store).await?; axum::serve(listener, build_router(api)).await?; Ok(()) } #[derive(Debug, Serialize, Deserialize)] pub struct WorkspaceResponse { pub workspace_id: String, pub display_name: String, pub record_authority: String, pub schema_version: i64, pub auth: AuthConfig, pub extension_points: ExtensionPoints, } #[derive(Debug, Serialize, Deserialize)] pub struct ExtensionPoints { pub store: String, pub event_stream: ExtensionPointState, pub host_worker_bridge: ExtensionPointState, pub companion_console: ExtensionPointState, } #[derive(Debug, Serialize, Deserialize)] pub struct ExtensionPointState { pub status: String, pub note: String, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct ListResponse { pub workspace_id: String, pub limit: usize, pub items: Vec, pub invalid_records: Vec, pub record_authority: String, } #[derive(Debug, Serialize, Deserialize)] pub struct RuntimeListResponse { pub workspace_id: String, pub limit: usize, pub items: Vec, pub source: String, pub diagnostics: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CleanupTargetKind { WorkerDelete, WorkdirCleanCleanup, WorkdirDirtyDiscard, WorkdirRecordDelete, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CleanupWorkdirFileStatus { Pending, Present, Active, CleanupPending, NotFound, Corrupted, Failed, Unknown, } impl CleanupWorkdirFileStatus { fn from_registry(value: &str) -> Self { match value { "pending" => Self::Pending, "present" => Self::Present, "active" => Self::Active, "cleanup_pending" => Self::CleanupPending, "not_found" => Self::NotFound, "corrupted" => Self::Corrupted, "failed" => Self::Failed, "unknown" => Self::Unknown, _ => Self::Unknown, } } fn from_runtime(value: &WorkingDirectoryStatusKind) -> Self { match value { WorkingDirectoryStatusKind::Active => Self::Active, WorkingDirectoryStatusKind::CleanupPending => Self::CleanupPending, WorkingDirectoryStatusKind::Corrupted => Self::Corrupted, WorkingDirectoryStatusKind::NotFound => Self::NotFound, WorkingDirectoryStatusKind::Unknown => Self::Unknown, } } fn is_record_only(self) -> bool { matches!(self, Self::NotFound) } fn is_corrupted(self) -> bool { matches!(self, Self::Corrupted) } } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CleanupWorkdirCleanliness { Clean, Dirty, Unknown, } impl CleanupWorkdirCleanliness { fn from_registry(value: &str) -> Self { match value { "clean" => Self::Clean, "dirty" => Self::Dirty, "unknown" => Self::Unknown, _ => Self::Unknown, } } fn from_runtime(value: Option<&str>) -> Self { value.map(Self::from_registry).unwrap_or(Self::Unknown) } fn is_clean(self) -> bool { matches!(self, Self::Clean) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CleanupWorkerCandidate { pub target_id: String, pub action: CleanupTargetKind, pub worker_id: String, pub runtime_worker_id: String, pub runtime_id: String, pub reason: String, pub blocking_reason: Option, pub pinned: bool, pub retention_state: String, pub linked_workdir_ids: Vec, pub running_linked: bool, pub estimated_reclaim_bytes: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CleanupWorkdirCandidate { pub target_id: String, pub action: CleanupTargetKind, pub workdir_id: String, pub runtime_id: String, pub repository_id: String, pub reason: String, pub blocking_reason: Option, pub linked_worker_ids: Vec, pub linked_running_worker_ids: Vec, pub running_linked: bool, pub pinned_linked: bool, pub file_status: CleanupWorkdirFileStatus, pub cleanliness: CleanupWorkdirCleanliness, pub estimated_reclaim_bytes: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RuntimeCleanupPlanResponse { pub workspace_id: String, pub runtime_id: String, pub generated_at: String, pub revision: String, pub digest: String, pub workers: Vec, pub workdirs: Vec, pub diagnostics: Vec, } #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExecuteRuntimeCleanupRequest { pub expected_plan_revision: String, pub expected_plan_digest: String, #[serde(default)] pub worker_target_ids: Vec, #[serde(default)] pub workdir_target_ids: Vec, #[serde(default)] pub confirm_dirty_discard_target_ids: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RuntimeCleanupExecutionResult { pub target_id: String, pub action: CleanupTargetKind, pub status: String, pub message: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RuntimeCleanupExecutionResponse { pub workspace_id: String, pub runtime_id: String, pub executed_at: String, pub results: Vec, pub plan_after: RuntimeCleanupPlanResponse, pub diagnostics: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct WorkerRetentionResponse { pub workspace_id: String, pub runtime_id: String, pub worker_id: String, pub pinned: bool, pub retention_state: String, } #[derive(Debug, Serialize, Deserialize)] pub struct RuntimeConnectionSettingsResponse { pub workspace_id: String, pub embedded: RuntimeConnectionSummary, pub remotes: Vec, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct RuntimeConnectionSummary { pub runtime_id: String, pub display_name: String, pub kind: String, pub built_in: bool, pub config_managed: bool, pub active: bool, pub can_spawn_worker: bool, pub restart_required: bool, pub status: String, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct RemoteRuntimeConnectionSummary { #[serde(flatten)] pub summary: RuntimeConnectionSummary, pub endpoint_configured: bool, pub token_ref_configured: bool, } #[derive(Debug, Serialize, Deserialize)] pub struct RuntimeConnectionMutationResponse { pub workspace_id: String, pub restart_required: bool, pub remotes: Vec, pub diagnostics: Vec, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct AddRemoteRuntimeConnectionRequest { pub runtime_id: String, pub display_name: Option, pub endpoint: String, pub token_ref: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct RemoteRuntimeTestResponse { pub workspace_id: String, pub runtime_id: String, pub checked_at: String, pub state: String, pub protocol_version: Option, pub compatibility_basis: String, pub capabilities: Vec, pub health_result: String, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct WorkerLaunchOptionsResponse { pub workspace_id: String, pub runtimes: Vec, pub profiles: Vec, pub repositories: Vec, pub working_directories: Vec, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct WorkerLaunchRuntimeOption { pub runtime_id: String, pub display_name: String, pub built_in: bool, pub can_spawn_worker: bool, pub working_directory_required: bool, pub status: String, pub diagnostics: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WorkerLaunchProfileCandidate { pub id: String, pub label: String, pub description: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WorkingDirectoryRepositoryOption { pub id: String, pub display_name: String, #[serde(skip_serializing_if = "Option::is_none")] pub default_selector: Option, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct BrowserWorkingDirectoryCreateRequest { #[serde(default = "embedded_runtime_id")] pub runtime_id: String, pub repository_id: String, #[serde(default)] pub selector: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct BrowserWorkingDirectoryListResponse { pub workspace_id: String, pub items: Vec, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct BrowserWorkingDirectoryDetailResponse { pub workspace_id: String, pub item: WorkingDirectorySummary, pub diagnostics: Vec, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct BrowserWorkerWorkingDirectorySelection { pub working_directory_id: String, #[serde(default)] pub relative_cwd: Option, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct BrowserCreateWorkerRequest { pub runtime_id: String, pub display_name: String, pub profile: String, pub initial_text: String, #[serde(default)] pub working_directory: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct BrowserCreateWorkerResponse { pub workspace_id: String, pub runtime_id: String, pub worker_id: String, pub console_href: String, pub worker: WorkerSummary, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct RepositoryListResponse { pub workspace_id: String, pub items: Vec, pub source: String, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct RepositoryDetailResponse { pub workspace_id: String, pub item: RepositorySummary, pub source: String, } #[derive(Debug, Serialize, Deserialize)] pub struct RepositoryLogResponse { pub workspace_id: String, pub repository_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub default_selector: Option, pub limit: usize, pub items: Vec, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct RepositoryTicketsResponse { pub workspace_id: String, pub repository_id: String, pub limit: usize, pub columns: Vec, pub invalid_records: Vec, pub record_authority: String, pub source: String, pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct TicketKanbanColumn { pub state: String, pub items: Vec, } #[derive(Debug, Deserialize)] struct LogQuery { limit: Option, } #[derive(Debug, Deserialize)] struct TicketKanbanQuery { limit: Option, } #[derive(Debug, Deserialize)] struct MemoryStagingQuery { limit: Option, } #[derive(Debug, Deserialize)] struct ObjectiveListQuery { limit: Option, } #[derive(Debug, Deserialize)] struct TranscriptQuery { start: Option, limit: Option, } #[derive(Debug, Deserialize)] struct ScopedWorkspacePath { workspace_id: String, } #[derive(Debug, Deserialize)] struct ScopedRecordPath { workspace_id: String, id: String, } #[derive(Debug, Deserialize)] struct ScopedSkillPath { workspace_id: String, name: String, } #[derive(Debug, Deserialize)] struct ScopedRepositoryPath { workspace_id: String, repository_id: String, } #[derive(Debug, Deserialize)] struct ScopedProfileArchivePath { workspace_id: String, digest: String, } #[derive(Debug, Deserialize)] struct ScopedHostPath { workspace_id: String, host_id: String, } #[derive(Debug, Deserialize)] struct ScopedRuntimePath { workspace_id: String, runtime_id: String, } #[derive(Debug, Deserialize)] struct ScopedWorkingDirectoryPath { workspace_id: String, working_directory_id: String, } #[derive(Debug, Deserialize)] struct ScopedRuntimeWorkingDirectoryPath { workspace_id: String, runtime_id: String, working_directory_id: String, } #[derive(Debug, Deserialize)] struct ScopedConfigBundlePath { workspace_id: String, runtime_id: String, bundle_id: String, } #[derive(Debug, Deserialize)] struct ScopedRuntimeWorkerPath { workspace_id: String, runtime_id: String, worker_id: String, } fn validate_workspace_scope(api: &WorkspaceApi, workspace_id: &str) -> ApiResult<()> { if workspace_id == api.workspace_id() { Ok(()) } else { Err(workspace_id_mismatch_error()) } } async fn scoped_get_workspace( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_workspace(State(api)).await } async fn scoped_get_workspace_settings( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(crate::profile_settings::workspace_metadata_settings( &api.config.workspace_root, &api.config.workspace_id, &api.config.workspace_created_at, &api.config.workspace_display_name, ))) } async fn scoped_update_workspace_settings( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let workspace = crate::profile_settings::update_workspace_metadata(&api.config.workspace_root, request)?; Ok(Json( crate::profile_settings::WorkspaceMetadataMutationResponse { workspace, diagnostics: vec![RuntimeDiagnostic { code: "workspace_metadata_updated".to_string(), severity: DiagnosticSeverity::Info, message: "Workspace display metadata was updated.".to_string(), }], }, )) } async fn scoped_get_profile_settings( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(crate::profile_settings::load_profile_settings( &api.config.workspace_id, &api.config.workspace_root, ))) } async fn scoped_create_profile_source( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(crate::profile_settings::create_profile_source( &api.config.workspace_id, &api.config.workspace_root, request, )?)) } async fn scoped_update_profile_registry( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(crate::profile_settings::update_profile_registry( &api.config.workspace_id, &api.config.workspace_root, request, )?)) } async fn scoped_get_profile_source_tree( State(api): State, AxumPath((workspace_id, source_tree_id)): AxumPath<(String, String)>, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::read_profile_source_tree( &workspace_id, &api.config.workspace_root, &source_tree_id, )?)) } async fn scoped_get_profile_tree_file( State(api): State, AxumPath((workspace_id, source_tree_id)): AxumPath<(String, String)>, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::read_profile_tree_file( &workspace_id, &api.config.workspace_root, &source_tree_id, query, )?)) } async fn scoped_write_profile_tree_file( State(api): State, AxumPath((workspace_id, source_tree_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::write_profile_tree_file( &workspace_id, &api.config.workspace_root, &source_tree_id, request, )?)) } async fn scoped_delete_profile_tree_file( State(api): State, AxumPath((workspace_id, source_tree_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::delete_profile_tree_file( &workspace_id, &api.config.workspace_root, &source_tree_id, request, )?)) } async fn scoped_get_profile_source( State(api): State, AxumPath((workspace_id, profile_source_id)): AxumPath<(String, String)>, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::read_profile_source( &api.config.workspace_id, &api.config.workspace_root, &profile_source_id, )?)) } async fn scoped_update_profile_source( State(api): State, AxumPath((workspace_id, profile_source_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::update_profile_source( &api.config.workspace_id, &api.config.workspace_root, &profile_source_id, request, )?)) } async fn scoped_delete_profile_source( State(api): State, AxumPath((workspace_id, profile_source_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &workspace_id)?; Ok(Json(crate::profile_settings::delete_profile_source( &api.config.workspace_id, &api.config.workspace_root, &profile_source_id, request, )?)) } async fn scoped_list_tickets( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_tickets(State(api), Query(query)).await } async fn scoped_get_ticket( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_ticket(State(api), AxumPath(path.id)).await } async fn scoped_ticket_backend_operation( State(api): State, AxumPath(path): AxumPath, Json(operation): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let config = ticket::config::TicketConfig::load_workspace(&api.config.workspace_root) .map_err(|error| Error::Config(format!("load Ticket workspace settings: {error}")))?; let backend = SqliteTicketBackend::new( api.config.database_path.clone(), api.config.workspace_id.clone(), ) .with_record_language(config.ticket_record_language()); let response = match execute_ticket_backend_operation(&backend, operation) { Ok(result) => TicketBackendHttpResponse::Ok { result }, Err(error) => TicketBackendHttpResponse::Error { message: error.to_string(), }, }; Ok(Json(response)) } #[derive(Debug, Clone, Serialize)] struct MemoryDocumentResponse { body_md: String, created_at: String, updated_at: String, bytes: usize, record_source: String, } async fn scoped_get_memory_document( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let document = api.authority.memory_document()?; Ok(Json(MemoryDocumentResponse { bytes: document.body_md.len(), body_md: document.body_md, created_at: document.created_at, updated_at: document.updated_at, record_source: document.record_source, })) } async fn scoped_list_memory_staging( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(list_memory_staging_from_authority( &api.authority, query.limit, )?)) } async fn scoped_memory_backend_operation( State(api): State, AxumPath(path): AxumPath, Json(operation): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let response = match execute_memory_backend_operation_with_authority(&api.authority, operation) { Ok(result) => MemoryBackendHttpResponse::Ok { result }, Err(error) => MemoryBackendHttpResponse::Error { message: sanitize_backend_error(&error.to_string()), }, }; Ok(Json(response)) } const MEMORY_CONSOLIDATION_PROFILE: &str = "memory-consolidation"; const MEMORY_CONSOLIDATION_WORKER_SCAN_LIMIT: usize = 100; async fn scoped_memory_consolidation( State(api): State, AxumPath(path): AxumPath, Json(operation): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(start_memory_staging_consolidation(api, operation)?)) } fn start_memory_staging_consolidation( api: WorkspaceApi, operation: MemoryConsolidateStagingOperation, ) -> ApiResult { let backlog = memory_staging_backlog_from_authority(&api.authority)?; let candidate_count = backlog.candidate_count; let total_bytes = backlog.total_bytes; if candidate_count == 0 { return Ok(MemoryConsolidationOutput { status: "skipped_empty".to_string(), summary: "No Memory staging candidates are pending.".to_string(), candidate_count, total_bytes, }); } let reached_files = operation .threshold_files .is_some_and(|threshold| candidate_count >= threshold); let reached_bytes = operation .threshold_bytes .is_some_and(|threshold| total_bytes >= threshold); if !operation.force && !reached_files && !reached_bytes { return Ok(MemoryConsolidationOutput { status: "skipped_below_threshold".to_string(), summary: format!( "Memory staging backlog has {candidate_count} candidate(s), {total_bytes} byte(s), below configured threshold." ), candidate_count, total_bytes, }); } let runtime_id = select_memory_consolidation_runtime(&api)?; let input_content = memory_consolidation_input_content(candidate_count, total_bytes); if let Some(output) = try_reuse_memory_consolidation_worker( &api, &runtime_id, &input_content, candidate_count, total_bytes, )? { return Ok(output); } let profile_selector = ProfileSelector::Builtin(MEMORY_CONSOLIDATION_PROFILE.to_string()); let resolved_config_bundle = crate::profile_settings::build_workspace_profile_config_bundle( &api.config.workspace_root, &api.config.workspace_id, &api.config.workspace_created_at, MEMORY_CONSOLIDATION_PROFILE, )?; let input = EmbeddedWorkerInput { kind: EmbeddedWorkerInputKind::User, content: input_content, segments: None, }; let result = api .runtime .spawn_worker( &runtime_id, WorkerSpawnRequest { requested_worker_name: Some(MEMORY_CONSOLIDATION_PROFILE.to_string()), intent: WorkerSpawnIntent::WorkspaceOrchestrator, acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments: 1, }, profile: Some(profile_selector), initial_input: Some(input), working_directory_request: None, resolved_working_directory_request: None, resolved_working_directory: None, resolved_config_bundle, }, ) .map_err(|err| err.into_error())?; if result.state != WorkerOperationState::Accepted { return Ok(MemoryConsolidationOutput { status: "skipped_spawn_rejected".to_string(), summary: "Runtime rejected Memory consolidater spawn.".to_string(), candidate_count, total_bytes, }); } let Some(worker) = result.worker else { return Ok(MemoryConsolidationOutput { status: "skipped_spawn_missing_worker".to_string(), summary: "Runtime accepted Memory consolidater spawn without returning a Worker summary." .to_string(), candidate_count, total_bytes, }); }; Ok(MemoryConsolidationOutput { status: "started".to_string(), summary: format!( "Started Memory consolidater '{}' for {candidate_count} staging candidate(s).", worker.worker_id ), candidate_count, total_bytes, }) } fn try_reuse_memory_consolidation_worker( api: &WorkspaceApi, runtime_id: &str, input_content: &str, candidate_count: usize, total_bytes: u64, ) -> ApiResult> { let workers = api .runtime .list_workers_for_runtime(runtime_id, MEMORY_CONSOLIDATION_WORKER_SCAN_LIMIT) .map_err(|err| err.into_error())?; let mut consolidaters = workers .items .into_iter() .filter(is_memory_consolidation_worker) .collect::>(); if consolidaters.is_empty() { return Ok(None); } consolidaters.sort_by(|a, b| a.worker_id.cmp(&b.worker_id)); if let Some(worker) = consolidaters.iter().find(|worker| worker.state != "idle") { return Ok(Some(MemoryConsolidationOutput { status: "skipped_existing_not_idle".to_string(), summary: format!( "Existing Memory consolidater '{}' is '{}', not confirmed idle.", worker.worker_id, worker.state ), candidate_count, total_bytes, })); } let worker = consolidaters .first() .expect("non-empty consolidater list") .clone(); let input = api .runtime .send_input( &worker.runtime_id, &worker.worker_id, WorkerInputRequest { kind: WorkerInputKind::User, content: input_content.to_string(), segments: None, }, ) .map_err(|err| err.into_error())?; if input.state != WorkerOperationState::Accepted { return Ok(Some(MemoryConsolidationOutput { status: "skipped_existing_input_rejected".to_string(), summary: format!( "Existing idle Memory consolidater '{}' rejected the new consolidation input.", worker.worker_id ), candidate_count, total_bytes, })); } Ok(Some(MemoryConsolidationOutput { status: "reused".to_string(), summary: format!( "Reused Memory consolidater '{}' for {candidate_count} staging candidate(s).", worker.worker_id ), candidate_count, total_bytes, })) } fn is_memory_consolidation_worker(worker: &WorkerSummary) -> bool { [worker.profile.as_deref(), worker.role.as_deref()] .into_iter() .flatten() .any(|value| value == MEMORY_CONSOLIDATION_PROFILE) } fn memory_consolidation_input_content(candidate_count: usize, total_bytes: u64) -> String { format!( "Process pending Memory staging candidates through MemoryStagingList, MemoryStagingRead, Memory tools, and MemoryStagingClose. Current backlog: {candidate_count} candidate(s), {total_bytes} byte(s)." ) } fn select_memory_consolidation_runtime(api: &WorkspaceApi) -> ApiResult { let runtimes = api.runtime.list_runtimes(100); if let Some(runtime) = runtimes .items .iter() .find(|runtime| { runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID && runtime.capabilities.can_spawn_worker }) .or_else(|| { runtimes .items .iter() .find(|runtime| runtime.capabilities.can_spawn_worker) }) { return Ok(runtime.runtime_id.clone()); } Err(Error::RuntimeOperationFailed { runtime_id: "memory-consolidation".to_string(), code: "memory_consolidation_runtime_unavailable".to_string(), message: "No runtime capable of spawning the Memory consolidater is available".to_string(), } .into()) } async fn scoped_list_skills( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(skills::catalog(&api.config.workspace_root))) } async fn scoped_lint_skills( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Ok(Json(skills::lint(&api.config.workspace_root))) } async fn scoped_get_skill( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; skills::detail(&api.config.workspace_root, &path.name) .map(Json) .map_err(skill_api_error) } async fn scoped_activate_skill( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; skills::activation(&api.config.workspace_root, &path.name) .map(Json) .map_err(skill_api_error) } fn skill_api_error(error: skills::SkillError) -> ApiError { match error { skills::SkillError::NotFound(name) => ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: "workspace".to_string(), code: "skill_not_found".to_string(), message: format!("unknown Skill `{name}`"), }, vec![RuntimeDiagnostic { code: "skill_not_found".to_string(), severity: DiagnosticSeverity::Error, message: format!("unknown Skill `{name}`"), }], ), skills::SkillError::Io(error) => ApiError::from(Error::Io(error)), } } async fn scoped_list_objectives( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_objectives(State(api), Query(query)).await } async fn scoped_get_objective( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_objective(State(api), AxumPath(path.id)).await } async fn scoped_list_repositories( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; list_repositories(State(api)).await } async fn scoped_repository_detail( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; repository_detail(State(api), AxumPath(path.repository_id)).await } async fn scoped_repository_log( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; repository_log(State(api), AxumPath(path.repository_id), Query(query)).await } async fn scoped_repository_tickets( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; repository_tickets(State(api), AxumPath(path.repository_id), Query(query)).await } async fn scoped_list_hosts( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_hosts(State(api)).await } async fn scoped_get_profile_source_archive( State(api): State, AxumPath(path): AxumPath, headers: HeaderMap, ) -> ApiResult<(StatusCode, HeaderMap, Vec)> { validate_workspace_scope(&api, &path.workspace_id)?; let archive = api .resource_broker .profile_source_archive(&path.digest) .ok_or_else(|| Error::Store("profile source archive not found".to_string()))?; let etag = format!("\"profile-source:{}\"", archive.reference.digest); if headers .get(IF_NONE_MATCH) .and_then(|value| value.to_str().ok()) .is_some_and(|value| value.split(',').any(|candidate| candidate.trim() == etag)) { let mut response_headers = HeaderMap::new(); response_headers.insert(ETAG, etag.parse().unwrap()); return Ok((StatusCode::NOT_MODIFIED, response_headers, Vec::new())); } let mut response_headers = HeaderMap::new(); response_headers.insert(ETAG, etag.parse().unwrap()); response_headers.insert(CONTENT_TYPE, "application/x-tar".parse().unwrap()); Ok((StatusCode::OK, response_headers, archive.content)) } async fn scoped_list_runtimes( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_runtimes(State(api)).await } async fn scoped_list_workers( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_workers(State(api)).await } async fn scoped_create_workspace_worker( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; create_workspace_worker(State(api), Json(request)).await } async fn scoped_get_worker_launch_options( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_worker_launch_options(State(api)).await } async fn scoped_list_runtime_working_directories( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let (items, diagnostics) = runtime_working_directory_summaries(&api, &path.runtime_id)?; Ok(Json(BrowserWorkingDirectoryListResponse { workspace_id: api.config.workspace_id.clone(), items, diagnostics, })) } async fn scoped_create_runtime_working_directory( State(api): State, AxumPath(path): AxumPath, Json(mut request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; request.runtime_id = path.runtime_id; create_working_directory_for_runtime(api, request) } async fn scoped_runtime_working_directory_detail( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; working_directory_detail_for_runtime(api, &path.runtime_id, &path.working_directory_id) } async fn scoped_cleanup_runtime_working_directory( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; cleanup_working_directory_for_runtime(api, &path.runtime_id, &path.working_directory_id) } async fn scoped_list_working_directories( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let items = working_directory_summaries(&api)?; Ok(Json(BrowserWorkingDirectoryListResponse { workspace_id: api.config.workspace_id.clone(), items, diagnostics: Vec::new(), })) } async fn scoped_create_working_directory( State(api): State, AxumPath(path): AxumPath, Json(_request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: "workspace-backend".to_string(), code: "backend_workdir_create_unsupported".to_string(), message: "Working directory creation must be scoped to a concrete Runtime".to_string(), }, vec![RuntimeDiagnostic { code: "backend_workdir_create_unsupported".to_string(), severity: DiagnosticSeverity::Error, message: "Use runtime-scoped Worker creation or a runtime-scoped working-directory API; the backend does not own workdir lifecycle.".to_string(), }], )) } async fn scoped_working_directory_detail( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; working_directory_detail_for_runtime( api, EMBEDDED_WORKER_RUNTIME_ID, &path.working_directory_id, ) } async fn scoped_cleanup_working_directory( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; cleanup_working_directory_for_runtime( api, EMBEDDED_WORKER_RUNTIME_ID, &path.working_directory_id, ) } fn create_working_directory_for_runtime( api: WorkspaceApi, request: BrowserWorkingDirectoryCreateRequest, ) -> ApiResult> { let runtime_id = request.runtime_id.clone(); let mut working_directory_request = working_directory_request_for_browser(&api, request)?; let workdir_id = next_backend_workdir_id(&working_directory_request.repository.id); working_directory_request.backend_workdir_id = Some(workdir_id.clone()); let pending = WorkdirRegistryRecord { workspace_id: api.config.workspace_id.clone(), workdir_id: workdir_id.clone(), runtime_id: runtime_id.clone(), repository_id: working_directory_request.repository.id.clone(), selector: working_directory_request .repository .selector .as_ref() .map(|selector| selector.as_ref().to_string()), resolved_commit: None, materialization_status: "pending".to_string(), cleanliness: "unknown".to_string(), created_at: now_registry_timestamp(), updated_at: now_registry_timestamp(), }; api.store.upsert_workdir_registry(&pending)?; let result = api .runtime .create_working_directory(&runtime_id, working_directory_request) .map_err(|err| err.into_error())?; let Some(working_directory) = result.working_directory else { let mut failed = pending; failed.materialization_status = "failed".to_string(); failed.updated_at = now_registry_timestamp(); api.store.upsert_workdir_registry(&failed)?; return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id, code: "workspace_working_directory_create_failed".to_string(), message: "Runtime did not create working directory".to_string(), }, result.diagnostics, )); }; let record = workdir_record_from_summary(&api, &runtime_id, &working_directory.summary); api.store.upsert_workdir_registry(&record)?; let mut summary = working_directory.summary; apply_workdir_occupancy_projection(&api, &mut summary)?; Ok(Json(BrowserWorkingDirectoryDetailResponse { workspace_id: api.config.workspace_id.clone(), item: summary, diagnostics: result.diagnostics, })) } fn working_directory_detail_for_runtime( api: WorkspaceApi, runtime_id: &str, working_directory_id: &str, ) -> ApiResult> { let result = api .runtime .working_directory(runtime_id, working_directory_id) .map_err(|err| err.into_error())?; if let Some(working_directory) = result.working_directory { let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary); api.store.upsert_workdir_registry(&record)?; let mut summary = working_directory.summary; apply_workdir_occupancy_projection(&api, &mut summary)?; return Ok(Json(BrowserWorkingDirectoryDetailResponse { workspace_id: api.config.workspace_id.clone(), item: summary, diagnostics: result.diagnostics, })); } if let Some(record) = api .store .get_workdir_registry(&api.config.workspace_id, working_directory_id)? { return Ok(Json(BrowserWorkingDirectoryDetailResponse { workspace_id: api.config.workspace_id.clone(), item: projected_workdir_summary_from_record(&api, &record)?, diagnostics: result.diagnostics, })); } Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_working_directory_lookup_failed".to_string(), message: "Runtime did not return working directory".to_string(), }, result.diagnostics, )) } fn cleanup_working_directory_for_runtime( api: WorkspaceApi, runtime_id: &str, working_directory_id: &str, ) -> ApiResult> { if let Some(candidate) = build_runtime_cleanup_plan(&api, runtime_id)? .workdirs .into_iter() .find(|candidate| candidate.workdir_id == working_directory_id) { if let Some(reason) = candidate.blocking_reason { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_workdir_blocked", &reason, )); } if candidate.action == CleanupTargetKind::WorkdirDirtyDiscard { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_dirty_confirmation_required", "dirty Workdir discard requires the cleanup execution API with explicit confirmation", )); } } let result = api .runtime .cleanup_working_directory(runtime_id, working_directory_id) .map_err(|err| err.into_error())?; let Some(working_directory) = result.working_directory else { return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_working_directory_cleanup_failed".to_string(), message: "Runtime did not cleanup working directory".to_string(), }, result.diagnostics, )); }; let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary); api.store.upsert_workdir_registry(&record)?; let mut summary = working_directory.summary; apply_workdir_occupancy_projection(&api, &mut summary)?; Ok(Json(BrowserWorkingDirectoryDetailResponse { workspace_id: api.config.workspace_id.clone(), item: summary, diagnostics: result.diagnostics, })) } async fn set_worker_retention( api: WorkspaceApi, runtime_id: String, runtime_worker_id: String, pinned: bool, ) -> ApiResult> { let runtime_worker_registry_id = parse_runtime_worker_id_for_registry(&runtime_worker_id)?; if api .store .get_worker_registry( &api.config.workspace_id, runtime_id.as_str(), runtime_worker_registry_id, )? .is_none() { if let Ok(worker) = api .runtime .worker(runtime_id.as_str(), runtime_worker_id.as_str()) { let _ = sync_worker_observation(&api, &worker); } } let retention_state = if pinned { "pinned" } else { "normal" }; let changed = api.store.update_worker_retention( &api.config.workspace_id, runtime_id.as_str(), runtime_worker_registry_id, retention_state, now_registry_timestamp().as_str(), )?; if !changed { return Err(cleanup_api_error( runtime_id.as_str(), "workspace_worker_retention_unknown_worker", "Worker is not known to the Backend registry", )); } Ok(Json(WorkerRetentionResponse { workspace_id: api.config.workspace_id, runtime_id, worker_id: runtime_worker_id, pinned, retention_state: retention_state.to_string(), })) } fn build_runtime_cleanup_plan( api: &WorkspaceApi, runtime_id: &str, ) -> ApiResult { let workers = workers_response(api.clone())?; let live_running_worker_ids: HashSet<(String, u64)> = workers .items .iter() .filter(|worker| worker.state == "running") .filter_map(|worker| { parse_runtime_worker_id_for_registry(worker.worker_id.as_str()) .ok() .map(|worker_id| (worker.runtime_id.clone(), worker_id)) }) .collect(); let (workdir_summaries, mut diagnostics) = match runtime_working_directory_summaries(api, runtime_id) { Ok(result) => result, Err(error) => { let mut diagnostics = error.diagnostics; if diagnostics.is_empty() { diagnostics.push(RuntimeDiagnostic { code: "workspace_cleanup_runtime_observation_unavailable".to_string(), severity: DiagnosticSeverity::Warning, message: sanitize_backend_error(&error.error.to_string()), }); } (Vec::new(), diagnostics) } }; let workdir_records = api .store .list_workdir_registry(&api.config.workspace_id, 500)?; let worker_records = api .store .list_worker_registry(&api.config.workspace_id, 500)?; let worker_by_id: HashMap<_, _> = worker_records .iter() .map(|record| { ( (record.runtime_id.clone(), record.runtime_worker_id.clone()), record.clone(), ) }) .collect(); let observed_workdirs: HashMap<_, _> = workdir_summaries .into_iter() .map(|summary| (summary.working_directory_id.clone(), summary)) .collect(); let mut worker_candidates = Vec::new(); for record in worker_records .iter() .filter(|record| record.runtime_id == runtime_id) { let links = api.store.list_worker_workdir_links( &api.config.workspace_id, record.runtime_id.as_str(), record.runtime_worker_id, )?; let is_running = live_running_worker_ids .contains(&(record.runtime_id.clone(), record.runtime_worker_id.clone())); let pinned = record.retention_state == "pinned"; let blocking_reason = if pinned { Some("worker is pinned".to_string()) } else if is_running { Some("worker is running".to_string()) } else { None }; worker_candidates.push(CleanupWorkerCandidate { target_id: format!( "worker:{}:{}", encode_path_segment(record.runtime_id.as_str()), encode_path_segment(&record.runtime_worker_id.to_string()) ), action: CleanupTargetKind::WorkerDelete, worker_id: record.runtime_worker_id.to_string(), runtime_worker_id: record.runtime_worker_id.to_string(), runtime_id: record.runtime_id.clone(), reason: if blocking_reason.is_some() { "Worker cannot be deleted until blocking conditions are cleared".to_string() } else { "Stopped or missing Worker can be manually deleted".to_string() }, blocking_reason, pinned, retention_state: record.retention_state.clone(), linked_workdir_ids: links.iter().map(|link| link.workdir_id.clone()).collect(), running_linked: is_running, estimated_reclaim_bytes: None, }); } let mut workdir_candidates = Vec::new(); for record in workdir_records .iter() .filter(|record| record.runtime_id == runtime_id) { let links = api .store .list_workdir_worker_links(&api.config.workspace_id, record.workdir_id.as_str())?; let linked_workers = links .iter() .filter_map(|link| { worker_by_id.get(&(link.runtime_id.clone(), link.runtime_worker_id.clone())) }) .collect::>(); let linked_worker_ids = links .iter() .map(|link| link.runtime_worker_id.to_string()) .collect::>(); let linked_running_worker_ids = linked_workers .iter() .filter(|worker| { live_running_worker_ids .contains(&(worker.runtime_id.clone(), worker.runtime_worker_id.clone())) }) .map(|worker| worker.runtime_worker_id.to_string()) .collect::>(); let pinned_linked = linked_workers .iter() .any(|worker| worker.retention_state == "pinned"); let running_linked = !linked_running_worker_ids.is_empty(); let observed_status = observed_workdirs .get(record.workdir_id.as_str()) .map(|summary| CleanupWorkdirFileStatus::from_runtime(&summary.status)); let file_status = observed_status.unwrap_or_else(|| { CleanupWorkdirFileStatus::from_registry(&record.materialization_status) }); let cleanliness = observed_workdirs .get(record.workdir_id.as_str()) .map(|summary| CleanupWorkdirCleanliness::from_runtime(summary.cleanliness.as_deref())) .unwrap_or_else(|| CleanupWorkdirCleanliness::from_registry(&record.cleanliness)); let action = if file_status.is_record_only() { CleanupTargetKind::WorkdirRecordDelete } else if file_status.is_corrupted() || cleanliness.is_clean() { CleanupTargetKind::WorkdirCleanCleanup } else { CleanupTargetKind::WorkdirDirtyDiscard }; let blocking_reason = if running_linked { Some("workdir is linked to a running Worker".to_string()) } else if pinned_linked { Some("workdir is linked to a pinned Worker/history".to_string()) } else { None }; workdir_candidates.push(CleanupWorkdirCandidate { target_id: format!("workdir:{}", record.workdir_id), action, workdir_id: record.workdir_id.clone(), runtime_id: record.runtime_id.clone(), repository_id: record.repository_id.clone(), reason: if blocking_reason.is_some() { "Workdir cleanup is blocked until linked Worker state is safe".to_string() } else if file_status.is_record_only() { "Not-found Workdir record can be deleted from the Backend registry".to_string() } else if file_status.is_corrupted() { "Corrupted Workdir can be deleted from Runtime storage and Backend registry" .to_string() } else if matches!(cleanliness, CleanupWorkdirCleanliness::Dirty) { "Dirty Workdir requires explicit discard confirmation before cleanup".to_string() } else if matches!(cleanliness, CleanupWorkdirCleanliness::Unknown) { "Workdir clean state is unknown; explicit discard confirmation is required" .to_string() } else { "Clean Workdir can be manually cleaned up".to_string() }, blocking_reason, linked_worker_ids, linked_running_worker_ids, running_linked, pinned_linked, file_status, cleanliness, estimated_reclaim_bytes: None, }); } worker_candidates.sort_by(|left, right| left.target_id.cmp(&right.target_id)); workdir_candidates.sort_by(|left, right| left.target_id.cmp(&right.target_id)); diagnostics.truncate(16); let generated_at = now_registry_timestamp(); let digest = cleanup_plan_digest(&worker_candidates, &workdir_candidates)?; Ok(RuntimeCleanupPlanResponse { workspace_id: api.config.workspace_id.clone(), runtime_id: runtime_id.to_string(), generated_at, revision: digest.clone(), digest, workers: worker_candidates, workdirs: workdir_candidates, diagnostics, }) } fn cleanup_plan_digest( workers: &[CleanupWorkerCandidate], workdirs: &[CleanupWorkdirCandidate], ) -> ApiResult { let bytes = serde_json::to_vec(&(workers, workdirs)).map_err(|error| { cleanup_api_error( "backend", "workspace_cleanup_plan_digest_failed", &format!("failed to serialize cleanup plan: {error}"), ) })?; let mut hasher = Sha256::new(); hasher.update(bytes); let bytes = hasher.finalize(); let digest = bytes .iter() .map(|byte| format!("{byte:02x}")) .collect::(); Ok(format!("sha256:{digest}")) } fn execute_runtime_cleanup( api: &WorkspaceApi, runtime_id: &str, request: ExecuteRuntimeCleanupRequest, ) -> ApiResult { let plan = build_runtime_cleanup_plan(api, runtime_id)?; if request.expected_plan_revision != plan.revision || request.expected_plan_digest != plan.digest { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_plan_stale", "cleanup plan revision/digest is stale; refresh the preview before executing", )); } let worker_targets: HashSet<_> = request.worker_target_ids.iter().cloned().collect(); let workdir_targets: HashSet<_> = request.workdir_target_ids.iter().cloned().collect(); let dirty_confirmations: HashSet<_> = request .confirm_dirty_discard_target_ids .iter() .cloned() .collect(); let mut results = Vec::new(); for candidate in plan .workers .iter() .filter(|candidate| worker_targets.contains(candidate.target_id.as_str())) { if let Some(reason) = &candidate.blocking_reason { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_worker_blocked", reason, )); } if candidate.pinned { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_worker_pinned", "pinned Worker/history cannot be deleted", )); } let runtime_worker_id = parse_runtime_worker_id_for_registry(&candidate.runtime_worker_id)?; cleanup_runtime_worker_for_execution(api, runtime_id, candidate)?; api.store.delete_worker_registry( &api.config.workspace_id, candidate.runtime_id.as_str(), runtime_worker_id, )?; results.push(RuntimeCleanupExecutionResult { target_id: candidate.target_id.clone(), action: candidate.action.clone(), status: "deleted".to_string(), message: "Worker deleted from Runtime and Backend registry".to_string(), }); } for candidate in plan .workdirs .iter() .filter(|candidate| workdir_targets.contains(candidate.target_id.as_str())) { if let Some(reason) = &candidate.blocking_reason { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_workdir_blocked", reason, )); } match candidate.action { CleanupTargetKind::WorkdirDirtyDiscard => { if !dirty_confirmations.contains(candidate.target_id.as_str()) { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_dirty_confirmation_required", "dirty Workdir discard requires explicit confirmation", )); } cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?; let deleted = api.store.delete_workdir_registry( &api.config.workspace_id, candidate.workdir_id.as_str(), )?; if !deleted { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_workdir_registry_not_found", "Backend Workdir registry row was not found after Runtime cleanup", )); } results.push(RuntimeCleanupExecutionResult { target_id: candidate.target_id.clone(), action: candidate.action.clone(), status: "deleted".to_string(), message: "Dirty/unknown Workdir was deleted from Runtime storage and Backend registry after explicit confirmation" .to_string(), }); } CleanupTargetKind::WorkdirCleanCleanup => { cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?; let deleted = api.store.delete_workdir_registry( &api.config.workspace_id, candidate.workdir_id.as_str(), )?; if !deleted { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_workdir_registry_not_found", "Backend Workdir registry row was not found after Runtime cleanup", )); } results.push(RuntimeCleanupExecutionResult { target_id: candidate.target_id.clone(), action: candidate.action.clone(), status: "deleted".to_string(), message: "Workdir deleted from Runtime storage and Backend registry" .to_string(), }); } CleanupTargetKind::WorkdirRecordDelete => { let deleted = api.store.delete_workdir_registry( &api.config.workspace_id, candidate.workdir_id.as_str(), )?; if !deleted { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_workdir_registry_not_found", "Backend Workdir registry row was not found", )); } results.push(RuntimeCleanupExecutionResult { target_id: candidate.target_id.clone(), action: candidate.action.clone(), status: "deleted".to_string(), message: "Not-found Workdir registry row deleted".to_string(), }); } CleanupTargetKind::WorkerDelete => { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_invalid_target_kind", "worker delete action cannot be executed as a Workdir target", )); } } } let requested_target_count = worker_targets.len() + workdir_targets.len(); if requested_target_count > 0 && results.len() != requested_target_count { return Err(cleanup_api_error( runtime_id, "workspace_cleanup_target_not_executed", "one or more selected cleanup targets were not present in the current cleanup plan", )); } let plan_after = build_runtime_cleanup_plan(api, runtime_id)?; let executed_at = now_registry_timestamp(); Ok(RuntimeCleanupExecutionResponse { workspace_id: api.config.workspace_id.clone(), runtime_id: runtime_id.to_string(), executed_at, results, diagnostics: plan_after.diagnostics.clone(), plan_after, }) } fn cleanup_runtime_worker_for_execution( api: &WorkspaceApi, runtime_id: &str, candidate: &CleanupWorkerCandidate, ) -> ApiResult<()> { match api.runtime.stop_worker( runtime_id, candidate.runtime_worker_id.as_str(), WorkerLifecycleRequest { reason: Some("cleanup worker before deletion".to_string()), }, ) { Ok(result) if result.state == WorkerOperationState::Accepted => {} Ok(result) => { return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_cleanup_worker_runtime_stop_rejected".to_string(), message: "Runtime did not stop selected Worker before deletion".to_string(), }, result.diagnostics, )); } Err(RuntimeRegistryError::UnknownWorker { .. }) => return Ok(()), Err(error) => return Err(error.into_error().into()), } match api .runtime .delete_worker(runtime_id, candidate.runtime_worker_id.as_str()) { Ok(result) if result.deleted && result.state == WorkerOperationState::Accepted => Ok(()), Ok(result) => Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_cleanup_worker_runtime_delete_rejected".to_string(), message: "Runtime did not delete selected Worker after stopping it".to_string(), }, result.diagnostics, )), Err(RuntimeRegistryError::UnknownWorker { .. }) => Ok(()), Err(error) => Err(error.into_error().into()), } } fn cleanup_runtime_workdir_for_execution( api: &WorkspaceApi, runtime_id: &str, candidate: &CleanupWorkdirCandidate, ) -> ApiResult<()> { let result = api .runtime .cleanup_working_directory(runtime_id, candidate.workdir_id.as_str()) .map_err(|err| err.into_error())?; if result.working_directory.is_none() { return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_cleanup_workdir_runtime_failed".to_string(), message: "Runtime did not cleanup selected Workdir".to_string(), }, result.diagnostics, )); }; Ok(()) } fn cleanup_api_error(runtime_id: &str, code: &str, message: &str) -> ApiError { Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: code.to_string(), message: message.to_string(), } .into() } async fn scoped_get_runtime_connection_settings( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_runtime_connection_settings(State(api)).await } async fn scoped_add_remote_runtime_connection( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; add_remote_runtime_connection(State(api), Json(request)).await } async fn scoped_delete_remote_runtime_connection( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; delete_remote_runtime_connection(State(api), AxumPath(path.runtime_id)).await } async fn scoped_test_remote_runtime_connection( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; test_remote_runtime_connection(State(api), AxumPath(path.runtime_id)).await } async fn scoped_get_companion_status( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_companion_status(State(api)).await } async fn scoped_get_companion_transcript( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_companion_transcript(State(api), Query(query)).await } async fn scoped_post_companion_message( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; post_companion_message(State(api), Json(request)).await } async fn scoped_post_companion_cancel( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; post_companion_cancel(State(api), Json(request)).await } async fn scoped_list_runtime_workers( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_runtime_workers(State(api), AxumPath(path.runtime_id)).await } async fn scoped_create_runtime_worker( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; create_runtime_worker(State(api), AxumPath(path.runtime_id), Json(request)).await } async fn scoped_sync_runtime_config_bundle( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; sync_runtime_config_bundle(State(api), AxumPath(path.runtime_id), Json(request)).await } async fn scoped_check_runtime_config_bundle( State(api): State, AxumPath(path): AxumPath, Query(query): Query, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; check_runtime_config_bundle( State(api), AxumPath((path.runtime_id, path.bundle_id)), Query(query), ) .await } async fn scoped_get_runtime_worker( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; get_runtime_worker(State(api), AxumPath((path.runtime_id, path.worker_id))).await } async fn scoped_pin_runtime_worker( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; set_worker_retention(api, path.runtime_id, path.worker_id, true).await } async fn scoped_unpin_runtime_worker( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; set_worker_retention(api, path.runtime_id, path.worker_id, false).await } async fn scoped_runtime_cleanup_plan( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let plan = build_runtime_cleanup_plan(&api, path.runtime_id.as_str())?; Ok(Json(plan)) } async fn scoped_execute_runtime_cleanup( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; let response = execute_runtime_cleanup(&api, path.runtime_id.as_str(), request)?; Ok(Json(response)) } async fn scoped_send_runtime_worker_input( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; send_runtime_worker_input( State(api), AxumPath((path.runtime_id, path.worker_id)), Json(request), ) .await } async fn scoped_runtime_worker_completions( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; runtime_worker_completions( State(api), AxumPath((path.runtime_id, path.worker_id)), Json(request), ) .await } async fn scoped_stop_runtime_worker( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; stop_runtime_worker( State(api), AxumPath((path.runtime_id, path.worker_id)), Json(request), ) .await } async fn scoped_cancel_runtime_worker( State(api): State, AxumPath(path): AxumPath, Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; cancel_runtime_worker( State(api), AxumPath((path.runtime_id, path.worker_id)), Json(request), ) .await } async fn scoped_worker_protocol_ws( ws: WebSocketUpgrade, State(api): State, AxumPath(path): AxumPath, ) -> Response { if let Err(err) = validate_workspace_scope(&api, &path.workspace_id) { return err.into_response(); } worker_protocol_ws(State(api), AxumPath((path.runtime_id, path.worker_id)), ws) .await .into_response() } async fn scoped_list_host_workers( State(api): State, AxumPath(path): AxumPath, ) -> ApiResult>> { validate_workspace_scope(&api, &path.workspace_id)?; list_host_workers(State(api), AxumPath(path.host_id)).await } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct AuthBootstrapUserRequest { handle: String, #[serde(default)] display_name: Option, } #[derive(Debug, Serialize, Deserialize)] struct AuthUserResponse { user: AuthenticatedUser, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct PasskeyRegistrationOptionsRequest { handle: String, #[serde(default)] display_name: Option, #[serde(default)] browser_origin: Option, } #[derive(Debug, Serialize, Deserialize)] struct PasskeyRegistrationOptionsResponse { challenge_id: String, public_key: CreationChallengeResponse, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct PasskeyRegistrationCompleteRequest { challenge_id: String, credential: RegisterPublicKeyCredential, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct PasskeyLoginOptionsRequest { #[serde(default)] handle: Option, #[serde(default)] browser_origin: Option, } #[derive(Debug, Serialize, Deserialize)] struct PasskeyLoginOptionsResponse { challenge_id: String, public_key: RequestChallengeResponse, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct PasskeyLoginCompleteRequest { challenge_id: String, credential: PublicKeyCredential, } #[derive(Debug, Serialize, Deserialize)] struct PasskeyLoginCompleteResponse { user: AuthenticatedUser, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct DeviceLoginStartRequest { #[serde(default)] client_name: Option, } #[derive(Debug, Serialize, Deserialize)] struct DeviceLoginStartResponse { device_code: String, user_code: String, verification_uri: String, verification_uri_complete: String, expires_in: u64, interval: u64, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct DeviceLoginApproveRequest { user_code: String, } #[derive(Debug, Serialize, Deserialize)] struct DeviceLoginApproveResponse { status: String, user: AuthenticatedUser, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct DeviceLoginPollRequest { device_code: String, } #[derive(Debug, Serialize, Deserialize)] struct DeviceLoginPollResponse { status: String, #[serde(skip_serializing_if = "Option::is_none")] access_token: Option, #[serde(skip_serializing_if = "Option::is_none")] token_type: Option, } #[derive(Debug, Serialize, Deserialize)] struct WhoamiResponse { actor: Option, } #[derive(Debug, Serialize, Deserialize)] struct LogoutResponse { status: String, } async fn get_auth_config(State(api): State) -> ApiResult> { Ok(Json(auth_public_config(&api.config))) } async fn post_auth_bootstrap_user( State(api): State, Json(request): Json, ) -> ApiResult> { let user = ensure_user_account(&api, &request.handle, request.display_name.as_deref())?; Ok(Json(AuthUserResponse { user })) } async fn post_passkey_registration_options( State(api): State, headers: HeaderMap, Json(request): Json, ) -> ApiResult> { let user = ensure_user_account(&api, &request.handle, request.display_name.as_deref())?; let header_origin = request_origin(&headers); let requested_origin = request .browser_origin .as_deref() .or(header_origin.as_deref()); let auth = auth_config_for_origin(&api.config, requested_origin)?; let webauthn = webauthn_for_auth(&auth)?; let exclude_credentials = passkeys_for_user(&api, &user.user_id)? .into_iter() .map(|passkey| passkey.cred_id().clone()) .collect(); let user_unique_id = Uuid::now_v7(); let (public_key, state) = webauthn .start_passkey_registration( user_unique_id, &user.handle, &user.display_name, Some(exclude_credentials), ) .map_err(|error| auth_error("webauthn_registration_options_failed", &error.to_string()))?; let challenge_id = new_id("webauthn-registration"); api.store.put_auth_challenge(&AuthChallengeRecord { challenge_id: challenge_id.clone(), ceremony: "passkey_registration".to_string(), challenge: challenge_id.clone(), user_id: Some(user.user_id), rp_id: auth.rp_id, origin: auth.origin, state_json: Some( serde_json::to_string(&state).map_err(|error| { auth_error("webauthn_state_serialize_failed", &error.to_string()) })?, ), expires_at: rfc3339_after(Duration::minutes(5)), created_at: crate::auth::now_rfc3339(), consumed_at: None, })?; Ok(Json(PasskeyRegistrationOptionsResponse { challenge_id, public_key, })) } async fn post_passkey_registration_complete( State(api): State, Json(request): Json, ) -> ApiResult { let challenge = api .store .consume_auth_challenge_by_id( &request.challenge_id, "passkey_registration", &crate::auth::now_rfc3339(), )? .ok_or_else(|| { auth_error( "invalid_passkey_challenge", "passkey registration challenge is invalid or already consumed", ) })?; if is_expired(&challenge.expires_at) { return Err(auth_error( "expired_passkey_challenge", "passkey registration challenge expired", ) .into()); } let user_id = challenge.user_id.clone().ok_or_else(|| { auth_error( "invalid_passkey_challenge", "passkey registration challenge is not bound to a user", ) })?; let user = api.store.get_user(&user_id)?.ok_or_else(|| { auth_error( "unknown_auth_user", "passkey registration user does not exist", ) })?; let state_json = challenge.state_json.clone().ok_or_else(|| { auth_error( "missing_webauthn_state", "passkey registration state was not persisted", ) })?; let state: PasskeyRegistration = serde_json::from_str(&state_json) .map_err(|error| auth_error("webauthn_state_deserialize_failed", &error.to_string()))?; let webauthn = webauthn_for_challenge(&api.config, &challenge)?; let passkey = webauthn .finish_passkey_registration(&request.credential, &state) .map_err(|error| { auth_error( "webauthn_registration_verification_failed", &error.to_string(), ) })?; let credential_id = passkey_credential_id(&passkey)?; api.store .upsert_passkey_credential(&PasskeyCredentialRecord { credential_id, user_id: user.user_id.clone(), public_key_cose: serde_json::to_string(&passkey).map_err(|error| { auth_error("webauthn_passkey_serialize_failed", &error.to_string()) })?, transports_json: None, sign_count: 0, created_at: crate::auth::now_rfc3339(), last_used_at: None, })?; issue_browser_session_response(&api, user) } async fn post_passkey_login_options( State(api): State, headers: HeaderMap, Json(request): Json, ) -> ApiResult> { let user = match request.handle.as_deref() { Some(handle) => api.store.get_user_by_handle(&normalize_handle(handle)?)?, None => api.store.any_user()?, } .ok_or_else(|| auth_error("unknown_auth_user", "no matching user account exists"))?; let passkeys = passkeys_for_user(&api, &user.user_id)?; if passkeys.is_empty() { return Err(auth_error( "passkey_not_registered", "user has no registered passkey credentials", ) .into()); } let header_origin = request_origin(&headers); let requested_origin = request .browser_origin .as_deref() .or(header_origin.as_deref()); let auth = auth_config_for_origin(&api.config, requested_origin)?; let webauthn = webauthn_for_auth(&auth)?; let (public_key, state) = webauthn .start_passkey_authentication(&passkeys) .map_err(|error| auth_error("webauthn_login_options_failed", &error.to_string()))?; let challenge_id = new_id("webauthn-login"); api.store.put_auth_challenge(&AuthChallengeRecord { challenge_id: challenge_id.clone(), ceremony: "passkey_login".to_string(), challenge: challenge_id.clone(), user_id: Some(user.user_id), rp_id: auth.rp_id, origin: auth.origin, state_json: Some( serde_json::to_string(&state).map_err(|error| { auth_error("webauthn_state_serialize_failed", &error.to_string()) })?, ), expires_at: rfc3339_after(Duration::minutes(5)), created_at: crate::auth::now_rfc3339(), consumed_at: None, })?; Ok(Json(PasskeyLoginOptionsResponse { challenge_id, public_key, })) } async fn post_passkey_login_complete( State(api): State, Json(request): Json, ) -> ApiResult { let challenge = api .store .consume_auth_challenge_by_id( &request.challenge_id, "passkey_login", &crate::auth::now_rfc3339(), )? .ok_or_else(|| { auth_error( "invalid_passkey_challenge", "passkey login challenge is invalid or already consumed", ) })?; if is_expired(&challenge.expires_at) { return Err(auth_error( "expired_passkey_challenge", "passkey login challenge expired", ) .into()); } let user_id = challenge.user_id.clone().ok_or_else(|| { auth_error( "invalid_passkey_challenge", "passkey login challenge is not bound to a user", ) })?; let user = api .store .get_user(&user_id)? .ok_or_else(|| auth_error("unknown_auth_user", "passkey user does not exist"))?; let state_json = challenge.state_json.clone().ok_or_else(|| { auth_error( "missing_webauthn_state", "passkey login state was not persisted", ) })?; let state: PasskeyAuthentication = serde_json::from_str(&state_json) .map_err(|error| auth_error("webauthn_state_deserialize_failed", &error.to_string()))?; let webauthn = webauthn_for_challenge(&api.config, &challenge)?; let auth_result = webauthn .finish_passkey_authentication(&request.credential, &state) .map_err(|error| auth_error("webauthn_login_verification_failed", &error.to_string()))?; let credential_id = serde_json::to_value(auth_result.cred_id()) .ok() .and_then(|value| value.as_str().map(ToOwned::to_owned)) .ok_or_else(|| { auth_error( "invalid_webauthn_credential_id", "verified credential id was not serializable", ) })?; let stored = api .store .get_passkey_credential(&credential_id)? .ok_or_else(|| { auth_error( "unknown_passkey_credential", "verified passkey credential is not registered", ) })?; if stored.user_id != user.user_id { return Err(auth_error( "passkey_user_mismatch", "verified passkey credential does not belong to the challenged user", ) .into()); } api.store .upsert_passkey_credential(&PasskeyCredentialRecord { credential_id, user_id: user.user_id.clone(), public_key_cose: stored.public_key_cose, transports_json: stored.transports_json, sign_count: u64::from(auth_result.counter()), created_at: stored.created_at, last_used_at: Some(crate::auth::now_rfc3339()), })?; issue_browser_session_response(&api, user) } fn issue_browser_session_response(api: &WorkspaceApi, user: UserRecord) -> ApiResult { let session_token = mint_secret("yoi_sess"); api.store.create_browser_session(&BrowserSessionRecord { session_id: new_id("session"), token_hash: token_hash(&session_token), user_id: user.user_id.clone(), created_at: crate::auth::now_rfc3339(), expires_at: rfc3339_after(Duration::days(14)), revoked_at: None, })?; let cookie_name = auth_public_config(&api.config).cookie_name; let mut headers = HeaderMap::new(); headers.insert( SET_COOKIE, session_set_cookie(&cookie_name, &session_token, 14 * 24 * 60 * 60) .parse() .map_err(|error| { auth_error( "invalid_session_cookie", &format!("failed to build session cookie: {error}"), ) })?, ); Ok(( headers, Json(AuthUserResponse { user: user_response(user), }), ) .into_response()) } async fn post_device_login_start( State(api): State, Json(request): Json, ) -> ApiResult> { let auth = auth_public_config(&api.config); let device_code = mint_secret("yoi_device"); let user_code = new_user_code(); let verification_uri = format!( "{}/login/device", auth.public_base_url.trim_end_matches('/') ); let verification_uri_complete = format!("{verification_uri}?user_code={user_code}"); api.store.create_device_login_flow(&DeviceLoginFlowRecord { device_code: device_code.clone(), user_code: user_code.clone(), verification_uri: verification_uri.clone(), client_name: request.client_name, user_id: None, api_token_id: None, issued_access_token: None, created_at: crate::auth::now_rfc3339(), expires_at: rfc3339_after(Duration::minutes(10)), approved_at: None, consumed_at: None, })?; Ok(Json(DeviceLoginStartResponse { device_code, user_code, verification_uri, verification_uri_complete, expires_in: 600, interval: 5, })) } async fn post_device_login_approve( State(api): State, headers: HeaderMap, Json(request): Json, ) -> ApiResult> { let actor = require_actor(&api, &headers).await?; let flow = api .store .get_device_login_flow_by_user_code(&request.user_code.trim().to_ascii_uppercase())? .ok_or_else(|| { auth_error( "unknown_device_login_code", "device login code does not exist", ) })?; if is_expired(&flow.expires_at) { return Err(auth_error("expired_device_login", "device login code expired").into()); } if flow.approved_at.is_some() || flow.consumed_at.is_some() { return Err(auth_error( "device_login_already_used", "device login code is already used", ) .into()); } let access_token = mint_secret("yoi_api"); let token_id = new_id("api-token"); api.store.create_api_token(&ApiTokenRecord { token_id: token_id.clone(), token_hash: token_hash(&access_token), user_id: actor.user_id.clone(), label: flow .client_name .clone() .unwrap_or_else(|| "yoi device login".to_string()), created_at: crate::auth::now_rfc3339(), expires_at: None, revoked_at: None, last_used_at: None, })?; let approved = api.store.approve_device_login_flow( &flow.device_code, &actor.user_id, &token_id, &access_token, &crate::auth::now_rfc3339(), )?; if !approved { return Err(auth_error( "device_login_already_used", "device login code is already used", ) .into()); } Ok(Json(DeviceLoginApproveResponse { status: "approved".to_string(), user: actor.user(), })) } async fn post_device_login_poll( State(api): State, Json(request): Json, ) -> ApiResult> { let Some(flow) = api .store .get_device_login_flow_by_device_code(&request.device_code)? else { return Err(auth_error("unknown_device_login", "device login flow does not exist").into()); }; if is_expired(&flow.expires_at) { return Ok(Json(DeviceLoginPollResponse { status: "expired".to_string(), access_token: None, token_type: None, })); } if flow.approved_at.is_none() { return Ok(Json(DeviceLoginPollResponse { status: "pending".to_string(), access_token: None, token_type: None, })); } let Some(consumed) = api .store .consume_device_login_token(&request.device_code, &crate::auth::now_rfc3339())? else { return Ok(Json(DeviceLoginPollResponse { status: "consumed".to_string(), access_token: None, token_type: None, })); }; Ok(Json(DeviceLoginPollResponse { status: "approved".to_string(), access_token: consumed.issued_access_token, token_type: Some("Bearer".to_string()), })) } async fn get_auth_whoami( State(api): State, headers: HeaderMap, ) -> ApiResult> { Ok(Json(WhoamiResponse { actor: resolve_actor(&api, &headers).await?, })) } async fn post_auth_logout( State(api): State, headers: HeaderMap, ) -> ApiResult { let auth = auth_public_config(&api.config); if let Some(session_token) = parse_cookie(&headers, &auth.cookie_name) { let _ = api .store .revoke_browser_session(&token_hash(&session_token), &crate::auth::now_rfc3339())?; } let mut response_headers = HeaderMap::new(); response_headers.insert( SET_COOKIE, session_set_cookie(&auth.cookie_name, "", 0) .parse() .map_err(|error| { auth_error( "invalid_session_cookie", &format!("failed to build logout cookie: {error}"), ) })?, ); Ok(( response_headers, Json(LogoutResponse { status: "logged_out".to_string(), }), ) .into_response()) } fn request_origin(headers: &HeaderMap) -> Option { headers .get(ORIGIN) .and_then(|value| value.to_str().ok()) .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) } fn auth_config_for_origin( config: &ServerConfig, request_origin: Option<&str>, ) -> ApiResult { let mut auth = auth_public_config(config); let Some(request_origin) = request_origin else { return Ok(auth); }; let origin = Url::parse(request_origin) .map_err(|error| auth_error("invalid_webauthn_request_origin", &error.to_string()))?; let host = origin.host_str().ok_or_else(|| { auth_error( "invalid_webauthn_request_origin", "request Origin header does not contain a host", ) })?; if host != auth.rp_id { return Err(auth_error( "webauthn_origin_rp_id_mismatch", &format!( "browser origin host {host} does not match configured RP ID {}", auth.rp_id ), ) .into()); } auth.origin = request_origin.to_string(); Ok(auth) } fn webauthn_for_challenge( config: &ServerConfig, challenge: &AuthChallengeRecord, ) -> ApiResult { let mut auth = auth_public_config(config); auth.rp_id = challenge.rp_id.clone(); auth.origin = challenge.origin.clone(); webauthn_for_auth(&auth) } fn webauthn_for_auth(auth: &AuthPublicConfig) -> ApiResult { let origin = Url::parse(&auth.origin) .map_err(|error| auth_error("invalid_webauthn_origin", &error.to_string()))?; WebauthnBuilder::new(&auth.rp_id, &origin) .map_err(|error| auth_error("webauthn_builder_failed", &error.to_string()))? .rp_name("Yoi Workspace") .build() .map_err(|error| auth_error("webauthn_builder_failed", &error.to_string()).into()) } fn passkeys_for_user(api: &WorkspaceApi, user_id: &str) -> ApiResult> { api.store .list_passkey_credentials_for_user(user_id)? .into_iter() .map(|record| { serde_json::from_str::(&record.public_key_cose).map_err(|error| { auth_error("webauthn_passkey_deserialize_failed", &error.to_string()).into() }) }) .collect() } fn passkey_credential_id(passkey: &Passkey) -> ApiResult { serde_json::to_value(passkey.cred_id()) .ok() .and_then(|value| value.as_str().map(ToOwned::to_owned)) .ok_or_else(|| { auth_error( "invalid_webauthn_credential_id", "verified passkey credential id was not serializable", ) .into() }) } fn auth_public_config(config: &ServerConfig) -> AuthPublicConfig { match &config.auth { AuthConfig::Passkey { rp_id, origin, public_base_url, cookie_name, } => AuthPublicConfig { rp_id: rp_id.clone(), origin: origin.clone(), public_base_url: public_base_url.clone(), cookie_name: cookie_name.clone(), }, } } fn ensure_user_account( api: &WorkspaceApi, handle: &str, display_name: Option<&str>, ) -> ApiResult { let handle = normalize_handle(handle)?; if let Some(user) = api.store.get_user_by_handle(&handle)? { return Ok(user_response(user)); } let now = crate::auth::now_rfc3339(); let display_name = display_name .map(str::trim) .filter(|value| !value.is_empty()) .unwrap_or(handle.as_str()) .to_string(); let account = AccountRecord { account_id: new_id("acct-user"), kind: "user".to_string(), handle: handle.clone(), display_name: display_name.clone(), created_at: now.clone(), updated_at: now.clone(), }; api.store.upsert_account(&account)?; let user = UserRecord { user_id: new_id("user"), account_id: account.account_id, handle, display_name, created_at: now.clone(), updated_at: now, }; api.store.upsert_user(&user)?; Ok(user_response(user)) } fn user_response(user: UserRecord) -> AuthenticatedUser { AuthenticatedUser { user_id: user.user_id, account_id: user.account_id, handle: user.handle, display_name: user.display_name, } } async fn resolve_actor(api: &WorkspaceApi, headers: &HeaderMap) -> ApiResult> { let cookie_name = auth_public_config(&api.config).cookie_name; Ok(resolve_request_actor(api.store.as_ref(), headers, &cookie_name).await?) } async fn require_actor(api: &WorkspaceApi, headers: &HeaderMap) -> ApiResult { resolve_actor(api, headers).await?.ok_or_else(|| { auth_error( "auth_required", "request requires a browser session or Bearer API token", ) .into() }) } async fn get_workspace(State(api): State) -> ApiResult> { let schema_version = api.store.schema_version().await?; let stored = api.store.get_workspace(api.workspace_id()).await?; let display_name = stored .as_ref() .map(|record| record.display_name.clone()) .unwrap_or_else(|| api.config.workspace_display_name.clone()); let companion_status = api.companion.status(); let companion_console = companion_console_extension_point(&companion_status); Ok(Json(WorkspaceResponse { workspace_id: api.config.workspace_id.clone(), display_name, record_authority: "local_yoi_project_records".to_string(), schema_version, auth: api.config.auth.clone(), extension_points: ExtensionPoints { store: "sqlite".to_string(), event_stream: ExtensionPointState { status: "backend_proxy".to_string(), note: "Worker observation streams are exposed only through the Workspace server proxy keyed by runtime_id + worker_id; browser clients never receive raw Runtime endpoints or socket paths.".to_string(), diagnostics: Vec::new(), }, host_worker_bridge: ExtensionPointState { status: "runtime_registry".to_string(), note: "Hosts and Workers are projected from the Workspace RuntimeRegistry; raw Runtime endpoints, sockets, and local metadata paths are not exposed.".to_string(), diagnostics: Vec::new(), }, companion_console, }, })) } fn companion_console_extension_point(status: &CompanionStatusResponse) -> ExtensionPointState { let completion = status.transport.completion.clone(); let note = match completion.as_str() { "connected" => "Workspace Companion is input-capable and browser input is dispatched through the normal Worker runtime path.".to_string(), "not_input_capable" => { let diagnostic_codes = status .diagnostics .iter() .map(|diagnostic| diagnostic.code.as_str()) .collect::>() .join(", "); if diagnostic_codes.is_empty() { "Workspace Companion is not input-capable; check provider, config, profile, secret, and authority diagnostics.".to_string() } else { format!( "Workspace Companion is not input-capable; check typed diagnostics: {diagnostic_codes}." ) } } "disabled" => "Workspace Companion auto-start has been removed; create an explicit Worker instead.".to_string(), other => format!( "Workspace Companion transport reports {other}; browser input follows the Companion Worker runtime capability state." ), }; ExtensionPointState { status: completion, note, diagnostics: status.diagnostics.clone(), } } async fn list_tickets( State(api): State, Query(query): Query, ) -> ApiResult>> { let requested_limit = query.limit.unwrap_or(api.config.max_records); let limit = requested_limit.min(1000); let ProjectRecordList { items, invalid_records, record_authority, } = api.authority.list_tickets(limit)?; Ok(Json(ListResponse { workspace_id: api.config.workspace_id, limit, items, invalid_records, record_authority, })) } async fn get_ticket( State(api): State, AxumPath(id): AxumPath, ) -> ApiResult> { Ok(Json(api.authority.ticket(&id)?)) } async fn list_objectives( State(api): State, Query(query): Query, ) -> ApiResult>> { let limit = query.limit.unwrap_or(api.config.max_records).min(1000); let ProjectRecordList { items, invalid_records, record_authority, } = api.authority.list_objectives(limit)?; Ok(Json(ListResponse { workspace_id: api.config.workspace_id, limit, items, invalid_records, record_authority, })) } async fn get_objective( State(api): State, AxumPath(id): AxumPath, ) -> ApiResult> { Ok(Json(api.authority.objective(&id)?)) } async fn list_repositories( State(api): State, ) -> ApiResult> { let RepositoryListProjection { items, diagnostics } = api.repository_reader().list(); Ok(Json(RepositoryListResponse { workspace_id: api.config.workspace_id, items, source: "workspace-control-plane".to_string(), diagnostics: repository_diagnostics(diagnostics), })) } async fn repository_detail( State(api): State, AxumPath(repository_id): AxumPath, ) -> ApiResult> { let item = repository_lookup(api.repository_reader().summary(&repository_id))?; Ok(Json(RepositoryDetailResponse { workspace_id: api.config.workspace_id.clone(), item, source: "workspace-control-plane".to_string(), })) } async fn repository_log( State(api): State, AxumPath(repository_id): AxumPath, Query(query): Query, ) -> ApiResult> { let RepositoryLogRead { repository_id, default_selector, limit, commits, diagnostics, } = repository_lookup( api.repository_reader() .recent_log(&repository_id, query.limit), )?; Ok(Json(RepositoryLogResponse { workspace_id: api.config.workspace_id, repository_id, default_selector, limit, items: commits, diagnostics: repository_diagnostics(diagnostics), })) } async fn repository_tickets( State(api): State, AxumPath(repository_id): AxumPath, Query(query): Query, ) -> ApiResult> { repository_lookup(api.repository_reader().summary(&repository_id))?; let canonical_repository_id = repository_id; let limit = query.limit.unwrap_or(api.config.max_records).min(200); let ProjectRecordList { items, invalid_records, record_authority, } = api.authority.list_tickets(limit)?; Ok(Json(RepositoryTicketsResponse { workspace_id: api.config.workspace_id, repository_id: canonical_repository_id, limit, columns: ticket_kanban_columns(items), invalid_records, record_authority, source: "workspace_local_ticket_fallback".to_string(), diagnostics: vec![RuntimeDiagnostic { code: "repository_ticket_target_metadata_absent".to_string(), severity: DiagnosticSeverity::Info, message: "Ticket target Repository metadata is not available yet; Kanban groups all workspace-local Tickets by state as a read-only fallback.".to_string(), }], })) } async fn list_hosts( State(api): State, ) -> ApiResult>> { let limit = api.config.max_records.min(200); let runtime_hosts = api.runtime.list_hosts(limit); Ok(Json(RuntimeListResponse { workspace_id: api.config.workspace_id, limit, items: runtime_hosts.items, source: "worker_runtime_registry".to_string(), diagnostics: runtime_hosts.diagnostics, })) } async fn list_runtimes( State(api): State, ) -> ApiResult>> { let limit = api.config.max_records.min(200); let runtimes = api.runtime.list_runtimes(limit); Ok(Json(RuntimeListResponse { workspace_id: api.config.workspace_id, limit, items: runtimes.items, source: "worker_runtime_registry".to_string(), diagnostics: runtimes.diagnostics, })) } async fn list_workers( State(api): State, ) -> ApiResult>> { workers_response(api).map(Json) } async fn get_runtime_connection_settings( State(api): State, ) -> ApiResult> { let runtime_config = load_backend_runtimes_config_for_settings(&api)?; Ok(Json(runtime_connection_settings_response( &api, &runtime_config, ))) } async fn add_remote_runtime_connection( State(api): State, Json(request): Json, ) -> ApiResult> { validate_runtime_connection_request(&request)?; let mut runtime_config = load_backend_runtimes_config_for_settings(&api)?; let id = request.runtime_id.trim().to_string(); if id == EMBEDDED_WORKER_RUNTIME_ID { return Err(settings_bad_request( "embedded_runtime_not_config_managed", "the embedded Runtime is built in and cannot be managed from local remote Runtime config", )); } if request .token_ref .as_ref() .is_some_and(|value| !value.trim().is_empty()) { return Err(settings_bad_request( "remote_runtime_token_ref_unsupported", "remote Runtime token_ref persistence is not supported by this v0 browser settings surface", )); } if runtime_config .runtimes .remote .iter() .any(|remote| remote.id == id) { return Err(settings_bad_request( "remote_runtime_already_exists", "a remote Runtime connection with that id is already configured", )); } let remote_config = RemoteRuntimeConfigFile { id, endpoint: request.endpoint.trim().to_string(), display_name: request .display_name .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned), token_ref: None, }; let active_config = remote_runtime_config_from_file(&remote_config).map_err(|diagnostic| { ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: remote_config.id.clone(), code: diagnostic.code.clone(), message: diagnostic.message.clone(), }, vec![diagnostic], ) })?; let active_runtime = RemoteWorkerRuntime::new( active_config, api.config.workspace_id.clone(), api.config .backend_base_url .clone() .unwrap_or_else(|| "http://127.0.0.1:8787".to_string()), ) .map(|host| host.with_resource_broker(api.resource_broker.clone())) .map_err(|err| err.into_error())?; runtime_config.runtimes.remote.push(remote_config); write_backend_runtimes_config_for_settings(&api, &runtime_config)?; api.runtime.register_or_replace(active_runtime); let mut response = runtime_connection_mutation_response( &api, &runtime_config, vec![settings_diagnostic( "runtime_registry_applied", DiagnosticSeverity::Info, "Remote Runtime config was persisted and applied to the active Runtime registry without restarting the Workspace backend.", )], ); response.diagnostics.push(settings_diagnostic( "backend_runtimes_config_rewritten", DiagnosticSeverity::Info, "Backend runtimes config was rewritten from the typed schema; comments and formatting are not preserved in v0.", )); Ok(Json(response)) } async fn delete_remote_runtime_connection( State(api): State, AxumPath(runtime_id): AxumPath, ) -> ApiResult> { if runtime_id == EMBEDDED_WORKER_RUNTIME_ID { return Err(settings_bad_request( "embedded_runtime_not_config_managed", "the embedded Runtime is built in and cannot be deleted from remote Runtime config", )); } let mut runtime_config = load_backend_runtimes_config_for_settings(&api)?; let before = runtime_config.runtimes.remote.len(); runtime_config .runtimes .remote .retain(|remote| remote.id != runtime_id); if before == runtime_config.runtimes.remote.len() { return Err(Error::UnknownRuntime(runtime_id).into()); } match api .runtime .unregister_if_idle(&runtime_id, api.config.max_records.min(200)) .map_err(|err| err.into_error())? { RuntimeRegistryUnregisterResult::Removed | RuntimeRegistryUnregisterResult::NotFound => {} RuntimeRegistryUnregisterResult::BlockedByWorkers { worker_count, diagnostics, } => { let mut diagnostics = diagnostics; diagnostics.push(settings_diagnostic( "remote_runtime_delete_blocked", DiagnosticSeverity::Error, format!( "Remote Runtime '{runtime_id}' has {worker_count} active worker(s); stop or move them before deleting the connection." ), )); return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id, code: "remote_runtime_delete_blocked".to_string(), message: "Remote Runtime connection has active workers".to_string(), }, diagnostics, )); } } write_backend_runtimes_config_for_settings(&api, &runtime_config)?; let mut response = runtime_connection_mutation_response( &api, &runtime_config, vec![settings_diagnostic( "runtime_registry_applied", DiagnosticSeverity::Info, "Remote Runtime config was removed from persisted config and the active Runtime registry without restarting the Workspace backend.", )], ); response.diagnostics.push(settings_diagnostic( "backend_runtimes_config_rewritten", DiagnosticSeverity::Info, "Backend runtimes config was rewritten from the typed schema; comments and formatting are not preserved in v0.", )); Ok(Json(response)) } async fn test_remote_runtime_connection( State(api): State, AxumPath(runtime_id): AxumPath, ) -> ApiResult> { let runtime_config = load_backend_runtimes_config_for_settings(&api)?; let remote = runtime_config .runtimes .remote .iter() .find(|remote| remote.id == runtime_id) .ok_or_else(|| Error::UnknownRuntime(runtime_id.clone()))?; Ok(Json(test_remote_runtime_config(&api, remote).await)) } async fn get_worker_launch_options( State(api): State, ) -> ApiResult> { Ok(Json(worker_launch_options_response(&api))) } fn working_directory_request_from_repository( repository: &ConfiguredRepository, selector: Option<&str>, ) -> WorkingDirectoryRequest { WorkingDirectoryRequest { repository: WorkingDirectoryRepository { id: repository.id.clone(), provider: repository.provider.clone(), uri: repository.uri.clone(), local_path: Some(repository.path.clone()), selector: selector .map(|selector| RuntimeRepositorySelector::from(selector.to_string())) .or_else(|| { repository .default_selector .clone() .map(RuntimeRepositorySelector) }) .or_else(|| Some(RuntimeRepositorySelector::from("HEAD"))), }, materializer: MaterializerKind::LocalGitWorktree, backend_workdir_id: None, } } fn configured_working_directory_request( config: &ServerConfig, request: &WorkerSpawnWorkingDirectoryRequest, ) -> Result { let repository = config .repositories .iter() .find(|repository| repository.id == request.repository_id) .ok_or_else(|| { Error::Config(format!( "unknown repository id `{}` for Worker working directory", request.repository_id )) })?; Ok(working_directory_request_from_repository( repository, request.selector.as_deref(), )) } async fn create_workspace_worker( State(api): State, Json(request): Json, ) -> ApiResult> { let profile_selector = profile_selector_for_candidate_with_root(&api.config.workspace_root, &request.profile) .ok_or_else(|| { settings_bad_request( "unsupported_worker_profile", "profile must be selected from Backend-published worker profile candidates", ) })?; let resolved_config_bundle = if request.profile.starts_with("project:") { crate::profile_settings::build_workspace_profile_config_bundle( &api.config.workspace_root, &api.config.workspace_id, &api.config.workspace_created_at, &request.profile, )? } else { None }; let display_name = sanitize_worker_display_name(&request.display_name).ok_or_else(|| { settings_bad_request( "invalid_worker_display_name", "display_name must contain at least one non-control character", ) })?; let initial_text = request.initial_text.trim().to_string(); let initial_input = if initial_text.is_empty() { None } else { Some(EmbeddedWorkerInput { kind: EmbeddedWorkerInputKind::User, content: initial_text, segments: None, }) }; let selected_working_directory_id = request .working_directory .as_ref() .map(|selection| selection.working_directory_id.clone()); let resolved_working_directory = request .working_directory .map(|selection| WorkingDirectoryClaim { working_directory_id: selection.working_directory_id, relative_cwd: selection.relative_cwd, }); validate_working_directory_claim_for_browser(resolved_working_directory.as_ref())?; if resolved_working_directory.is_none() { reject_no_workdir_for_non_embedded_runtime(&request.runtime_id)?; } let result = api .runtime .spawn_worker( &request.runtime_id, WorkerSpawnRequest { requested_worker_name: Some(display_name.clone()), intent: WorkerSpawnIntent::WorkspaceCoding, acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments: if initial_input.is_some() { 1 } else { 0 }, }, profile: Some(profile_selector), initial_input, working_directory_request: None, resolved_working_directory_request: None, resolved_working_directory, resolved_config_bundle, }, ) .map_err(|err| err.into_error())?; if result.state != WorkerOperationState::Accepted { return Err(worker_create_not_accepted_error( request.runtime_id.clone(), result.diagnostics, )); } let worker = result.worker.ok_or_else(|| Error::RuntimeOperationFailed { runtime_id: request.runtime_id.clone(), code: "workspace_worker_create_missing_summary".to_string(), message: "Runtime completed worker creation without returning a Worker summary".to_string(), })?; let worker_record = record_worker_summary( &api, &worker, display_name.as_str(), worker.profile.clone(), WorkerRegistryDisplayNamePolicy::UseProvided, )?; if let Some(working_directory) = worker.working_directory.as_ref() { let workdir_record = workdir_record_from_summary(&api, worker.runtime_id.as_str(), working_directory); api.store.upsert_workdir_registry(&workdir_record)?; link_worker_to_workdir( &api, &worker_record, &working_directory.working_directory_id, )?; } if let Some(workdir_id) = selected_working_directory_id.as_deref() { if api .store .get_workdir_registry(&api.config.workspace_id, workdir_id)? .is_none() { if let Ok(result) = api .runtime .working_directory(worker.runtime_id.as_str(), workdir_id) .map_err(|err| err.into_error()) { if let Some(status) = result.working_directory { let record = workdir_record_from_summary( &api, worker.runtime_id.as_str(), &status.summary, ); api.store.upsert_workdir_registry(&record)?; } } } if api .store .get_workdir_registry(&api.config.workspace_id, workdir_id)? .is_some() { link_worker_to_workdir(&api, &worker_record, workdir_id)?; } } let runtime_id = worker.runtime_id.clone(); let worker_id = worker.worker_id.clone(); let workspace_id = api.workspace_id().to_string(); let console_href = format!( "/w/{}/runtimes/{}/workers/{}/console", encode_path_segment(&workspace_id), encode_path_segment(&runtime_id), encode_path_segment(&worker_id) ); Ok(Json(BrowserCreateWorkerResponse { workspace_id, runtime_id, worker_id, console_href, worker, diagnostics: result.diagnostics, })) } async fn post_internal_runtime_resource_fetch( State(api): State, Json(request): Json, ) -> std::result::Result< Json, (StatusCode, Json), > { api.resource_broker .fetch_profile_source_archive(request) .map(Json) .map_err(|error| (backend_resource_error_status(&error), Json(error))) } fn backend_resource_error_status(error: &BackendResourceError) -> StatusCode { match error { BackendResourceError::Expired => StatusCode::GONE, BackendResourceError::Unauthorized { .. } => StatusCode::UNAUTHORIZED, BackendResourceError::MissingResource => StatusCode::NOT_FOUND, BackendResourceError::UnsupportedKind | BackendResourceError::DigestMismatch { .. } | BackendResourceError::Oversized { .. } | BackendResourceError::ContentTypeMismatch { .. } | BackendResourceError::InvalidResponse { .. } => StatusCode::BAD_REQUEST, BackendResourceError::Transport { .. } => StatusCode::BAD_GATEWAY, } } async fn get_companion_status( State(api): State, ) -> ApiResult> { Ok(Json(api.companion.status())) } async fn get_companion_transcript( State(api): State, Query(query): Query, ) -> ApiResult> { let limit = query.limit.unwrap_or(api.config.max_records).min(200); let start = query.start.unwrap_or(0); Ok(Json(api.companion.transcript(start, limit))) } async fn post_companion_message( State(api): State, Json(request): Json, ) -> ApiResult> { Ok(Json(api.companion.send_message(request))) } async fn post_companion_cancel( State(api): State, Json(request): Json, ) -> ApiResult> { Ok(Json(api.companion.cancel(request))) } async fn get_runtime_worker( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, ) -> ApiResult> { let worker = api .runtime .worker(&runtime_id, &worker_id) .map_err(|err| err.into_error())?; let record = sync_worker_observation(&api, &worker)?; let links = api.store.list_worker_workdir_links( &api.config.workspace_id, record.runtime_id.as_str(), record.runtime_worker_id, )?; let workdirs = api .store .list_workdir_registry(&api.config.workspace_id, 500)?; Ok(Json(merge_worker_registry_projection( Some(&worker), &record, links, &workdirs, ))) } #[derive(Debug, Serialize, Deserialize)] pub struct RuntimeConfigBundleSyncRequest { pub bundle: ConfigBundle, } #[derive(Debug, Deserialize)] struct RuntimeConfigBundleAvailabilityQuery { digest: String, } fn reject_workdir_for_embedded_runtime( runtime_id: &str, has_workdir: bool, ) -> std::result::Result<(), ApiError> { if runtime_id != EMBEDDED_WORKER_RUNTIME_ID || !has_workdir { return Ok(()); } Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "embedded_worker_workdir_unsupported".to_string(), message: "The embedded Runtime does not accept working directories".to_string(), }, vec![RuntimeDiagnostic { code: "embedded_worker_workdir_unsupported".to_string(), severity: DiagnosticSeverity::Error, message: "Choose a non-embedded Runtime for workspace-file Workers; embedded Workers are no-workdir Workspace-API workers.".to_string(), }], )) } fn reject_no_workdir_for_non_embedded_runtime( runtime_id: &str, ) -> std::result::Result<(), ApiError> { if runtime_id == EMBEDDED_WORKER_RUNTIME_ID { return Ok(()); } Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_worker_workdir_required".to_string(), message: "Only the embedded Runtime can launch a Worker without a working directory" .to_string(), }, vec![RuntimeDiagnostic { code: "workspace_worker_workdir_required".to_string(), severity: DiagnosticSeverity::Error, message: "Select a working directory for this Runtime, or choose the embedded Runtime for a conversation-only Worker." .to_string(), }], )) } async fn list_runtime_workers( State(api): State, AxumPath(runtime_id): AxumPath, ) -> ApiResult>> { let limit = api.config.max_records.min(200); let worker_list = api .runtime .list_workers_for_runtime(&runtime_id, limit) .map_err(|err| err.into_error())?; Ok(Json(RuntimeListResponse { workspace_id: api.workspace_id().to_string(), limit, items: worker_list.items, source: "runtime_registry".to_string(), diagnostics: worker_list.diagnostics, })) } async fn create_runtime_worker( State(api): State, AxumPath(runtime_id): AxumPath, Json(mut request): Json, ) -> ApiResult> { reject_workdir_for_embedded_runtime( &runtime_id, request.working_directory_request.is_some() || request.resolved_working_directory.is_some(), )?; if request.working_directory_request.is_none() && request.resolved_working_directory.is_none() { reject_no_workdir_for_non_embedded_runtime(&runtime_id)?; } request.resolved_working_directory_request = request .working_directory_request .as_ref() .map(|working_directory| { configured_working_directory_request(&api.config, working_directory) }) .transpose()?; let prepared_workdir_id = if let Some(working_directory_request) = request.resolved_working_directory_request.as_mut() { Some(upsert_pending_backend_workdir( &api, &runtime_id, working_directory_request, )?) } else { request .resolved_working_directory .as_ref() .map(|claim| claim.working_directory_id.clone()) }; let requested_worker_name = request.requested_worker_name.clone(); let result = api .runtime .spawn_worker(&runtime_id, request) .map_err(|err| err.into_error())?; if let Some(worker) = result.worker.as_ref() { let display_name = requested_worker_name .as_deref() .filter(|name| !name.trim().is_empty()) .unwrap_or(worker.label.as_str()) .to_string(); let record = record_worker_summary( &api, worker, display_name.as_str(), worker.profile.clone(), WorkerRegistryDisplayNamePolicy::UseProvided, )?; if worker.working_directory.is_none() { if let Some(workdir_id) = prepared_workdir_id.as_deref() { if api .store .get_workdir_registry(&api.config.workspace_id, workdir_id)? .is_some() { link_worker_to_workdir(&api, &record, workdir_id)?; } } } } else if let Some(workdir_id) = prepared_workdir_id.as_deref() { if let Some(mut record) = api .store .get_workdir_registry(&api.config.workspace_id, workdir_id)? { record.materialization_status = "failed".to_string(); record.updated_at = now_registry_timestamp(); api.store.upsert_workdir_registry(&record)?; } } Ok(Json(result)) } async fn sync_runtime_config_bundle( State(api): State, AxumPath(runtime_id): AxumPath, Json(request): Json, ) -> ApiResult> { let result = api .runtime .sync_config_bundle(&runtime_id, request.bundle) .map_err(|err| err.into_error())?; Ok(Json(result)) } async fn check_runtime_config_bundle( State(api): State, AxumPath((runtime_id, bundle_id)): AxumPath<(String, String)>, Query(query): Query, ) -> ApiResult> { let result = api .runtime .check_config_bundle( &runtime_id, ConfigBundleRef { id: bundle_id, digest: query.digest, }, ) .map_err(|err| err.into_error())?; Ok(Json(result)) } async fn send_runtime_worker_input( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { let result = api .runtime .send_input(&runtime_id, &worker_id, request) .map_err(|err| err.into_error())?; Ok(Json(result)) } async fn runtime_worker_completions( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { let result = api .runtime .worker_completions(&runtime_id, &worker_id, request) .map_err(|err| err.into_error())?; Ok(Json(result)) } async fn stop_runtime_worker( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { let result = api .runtime .stop_worker(&runtime_id, &worker_id, request) .map_err(|err| err.into_error())?; let runtime_worker_id = parse_runtime_worker_id_for_registry(&worker_id)?; if let Some(record) = api.store .get_worker_registry(&api.config.workspace_id, &runtime_id, runtime_worker_id)? { sync_linked_workdir_after_worker_stop(&api, &runtime_id, &record)?; } Ok(Json(result)) } async fn cancel_runtime_worker( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, Json(request): Json, ) -> ApiResult> { let result = api .runtime .cancel_worker(&runtime_id, &worker_id, request) .map_err(|err| err.into_error())?; Ok(Json(result)) } async fn worker_protocol_ws( State(api): State, AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>, ws: WebSocketUpgrade, ) -> impl IntoResponse { let source = match api.observation_proxy.source(&runtime_id, &worker_id) { Ok(source) => source, Err(ObservationProxyError::WorkerNotFound(_)) => { match api.runtime.observation_source(&runtime_id, &worker_id) { Ok(source) => source, Err(error) => return ApiError::from(error.into_error()).into_response(), } } Err(error) => { return ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": error.code(), "message": error.message(), })), ) .into_response(); } }; ws.on_upgrade(move |socket| worker_protocol_ws_session(source, socket)) } async fn worker_protocol_ws_session(source: RuntimeObservationSource, socket: WebSocket) { match source { RuntimeObservationSource::RemoteWs(config) => { remote_worker_protocol_ws_session(config, socket).await; } RuntimeObservationSource::Embedded(source) => { embedded_worker_protocol_ws_session(source, socket).await; } } } async fn remote_worker_protocol_ws_session( config: RuntimeObservationSourceConfig, socket: WebSocket, ) { let mut request = match config.endpoint.clone().into_client_request() { Ok(request) => request, Err(error) => { let mut socket = socket; let event = protocol_error_event(format!( "failed to build runtime protocol WebSocket request: {error}" )); let _ = send_protocol_event(&mut socket, &event).await; return; } }; if let Some(token) = &config.bearer_token { match format!("Bearer {token}").parse() { Ok(value) => { request.headers_mut().insert("authorization", value); } Err(error) => { let mut socket = socket; let event = protocol_error_event(format!( "failed to build runtime authorization header: {error}" )); let _ = send_protocol_event(&mut socket, &event).await; return; } } } let (upstream, _) = match connect_async(request).await { Ok(connection) => connection, Err(error) => { let mut socket = socket; let event = protocol_error_event(format!( "failed to connect runtime protocol WebSocket: {error}" )); let _ = send_protocol_event(&mut socket, &event).await; return; } }; let (mut client_sink, mut client_stream) = socket.split(); let (mut upstream_sink, mut upstream_stream) = upstream.split(); loop { tokio::select! { inbound = client_stream.next() => { match inbound { Some(Ok(WsMessage::Text(text))) => { if upstream_sink.send(TungsteniteMessage::Text(text.to_string().into())).await.is_err() { break; } } Some(Ok(WsMessage::Binary(binary))) => { if upstream_sink.send(TungsteniteMessage::Binary(binary.to_vec().into())).await.is_err() { break; } } Some(Ok(WsMessage::Close(_))) | None => { let _ = upstream_sink.send(TungsteniteMessage::Close(None)).await; break; } Some(Ok(WsMessage::Ping(payload))) => { if upstream_sink.send(TungsteniteMessage::Ping(payload.to_vec().into())).await.is_err() { break; } } Some(Ok(WsMessage::Pong(payload))) => { if upstream_sink.send(TungsteniteMessage::Pong(payload.to_vec().into())).await.is_err() { break; } } Some(Err(_)) => break, } } outbound = upstream_stream.next() => { match outbound { Some(Ok(TungsteniteMessage::Text(text))) => { if client_sink.send(WsMessage::Text(text.to_string().into())).await.is_err() { break; } } Some(Ok(TungsteniteMessage::Binary(binary))) => { if client_sink.send(WsMessage::Binary(binary.to_vec().into())).await.is_err() { break; } } Some(Ok(TungsteniteMessage::Close(_))) | None => { let _ = client_sink.send(WsMessage::Close(None)).await; break; } Some(Ok(TungsteniteMessage::Ping(payload))) => { if client_sink.send(WsMessage::Ping(payload.to_vec().into())).await.is_err() { break; } } Some(Ok(TungsteniteMessage::Pong(payload))) => { if client_sink.send(WsMessage::Pong(payload.to_vec().into())).await.is_err() { break; } } Some(Ok(TungsteniteMessage::Frame(_))) => {} Some(Err(_)) => break, } } } } } async fn embedded_worker_protocol_ws_session( source: crate::observation::EmbeddedRuntimeObservationSource, mut socket: WebSocket, ) { let mut upstream = match RuntimeObservationClient::connect(&RuntimeObservationSource::Embedded( source.clone(), )) .await { Ok(client) => client, Err(error) => { let event = protocol_error_event(error.message()); let _ = send_protocol_event(&mut socket, &event).await; return; } }; loop { tokio::select! { inbound = socket.next() => { match inbound { Some(Ok(WsMessage::Text(text))) => match decode_method(&text) { Ok(method) => match source.runtime.send_protocol_method(&source.worker_ref, method) { Ok(events) => { for event in events { if !send_protocol_event(&mut socket, &event).await { return; } } } Err(error) => { let event = protocol_error_event(error.to_string()); if !send_protocol_event(&mut socket, &event).await { return; } } }, Err(error) => { let event = protocol_error_event(format!("malformed protocol method frame: {error}")); if !send_protocol_event(&mut socket, &event).await { return; } } }, Some(Ok(WsMessage::Close(_))) | None => return, Some(Ok(WsMessage::Ping(payload))) => { if socket.send(WsMessage::Pong(payload)).await.is_err() { return; } } Some(Ok(WsMessage::Pong(_))) | Some(Ok(WsMessage::Binary(_))) => {} Some(Err(error)) => { let event = protocol_error_event(format!("protocol WebSocket error: {error}")); let _ = send_protocol_event(&mut socket, &event).await; return; } } } upstream_event = upstream.next_event() => { match upstream_event { Ok(event) => { if !send_protocol_event(&mut socket, &event.payload).await { return; } } Err(error) => { let event = protocol_error_event(error.message()); let _ = send_protocol_event(&mut socket, &event).await; return; } } } } } } async fn send_protocol_event(socket: &mut WebSocket, event: &protocol::Event) -> bool { match encode_event(event) { Ok(text) => socket.send(WsMessage::Text(text.into())).await.is_ok(), Err(error) => { let fallback = protocol_error_event(format!( "failed to serialize protocol response event: {error}" )); let Ok(text) = encode_event(&fallback) else { return false; }; socket.send(WsMessage::Text(text.into())).await.is_ok() } } } fn protocol_error_event(message: impl Into) -> protocol::Event { protocol::Event::Error { code: protocol::ErrorCode::Internal, message: message.into(), } } async fn list_host_workers( State(api): State, AxumPath(host_id): AxumPath, ) -> ApiResult>> { let limit = api.config.max_records.min(200); let runtime_workers = api .runtime .list_workers_for_host(&host_id, limit) .map_err(|err| err.into_error())?; Ok(Json(RuntimeListResponse { workspace_id: api.config.workspace_id, limit, items: runtime_workers.items, source: "worker_runtime_registry".to_string(), diagnostics: runtime_workers.diagnostics, })) } fn workers_response(api: WorkspaceApi) -> ApiResult> { let limit = api.config.max_records.min(200); let runtime_workers = api.runtime.list_workers(limit); let mut observed = std::collections::BTreeMap::new(); for worker in &runtime_workers.items { let _ = sync_worker_observation(&api, worker); if let Ok(runtime_worker_id) = parse_runtime_worker_id_for_registry(worker.worker_id.as_str()) { observed.insert( (worker.runtime_id.clone(), runtime_worker_id), worker.clone(), ); } } let mut diagnostics = runtime_workers.diagnostics; let worker_records = api .store .list_worker_registry(&api.config.workspace_id, limit)?; let workdir_records = api .store .list_workdir_registry(&api.config.workspace_id, 500)?; let mut items = Vec::new(); for record in worker_records { if !observed.contains_key(&(record.runtime_id.clone(), record.runtime_worker_id)) { match api.runtime.worker( record.runtime_id.as_str(), &record.runtime_worker_id.to_string(), ) { Ok(worker) => { let _ = sync_worker_observation(&api, &worker); observed.insert( (worker.runtime_id.clone(), record.runtime_worker_id), worker, ); } Err(RuntimeRegistryError::UnknownWorker { .. }) => {} Err(error) => diagnostics.push(RuntimeDiagnostic { code: "worker_detail_probe_failed".to_string(), severity: DiagnosticSeverity::Info, message: format!( "Could not verify Worker {} on Runtime {}: {}", record.runtime_worker_id, record.runtime_id, sanitize_backend_error(&error.into_error().to_string()) ), }), } } let links = api.store.list_worker_workdir_links( &api.config.workspace_id, record.runtime_id.as_str(), record.runtime_worker_id, )?; items.push(merge_worker_registry_projection( observed.get(&(record.runtime_id.clone(), record.runtime_worker_id)), &record, links, &workdir_records, )); } Ok(RuntimeListResponse { workspace_id: api.config.workspace_id, limit, items, source: "backend_worker_registry".to_string(), diagnostics, }) } fn load_backend_runtimes_config_for_settings( api: &WorkspaceApi, ) -> ApiResult { api.config .runtime_config_path .as_ref() .map(BackendRuntimesConfigFile::load_from_path) .transpose() .map_err(|error| { Error::Config(format!( "failed to read Backend runtimes config for Runtime connections: {}", sanitize_backend_error(&error.to_string()) )) .into() }) .map(|config| config.unwrap_or_default()) } fn write_backend_runtimes_config_for_settings( api: &WorkspaceApi, runtime_config: &BackendRuntimesConfigFile, ) -> ApiResult<()> { let path = api.config.runtime_config_path.as_ref().ok_or_else(|| { Error::Config( "Backend runtimes config path is unavailable; set YOI_CONFIG_DIR, YOI_HOME, XDG_CONFIG_HOME, or HOME" .to_string(), ) })?; runtime_config.write_to_path(path).map_err(|error| { Error::Config(format!( "failed to write Backend runtimes config for Runtime connections: {}", sanitize_backend_error(&error.to_string()) )) .into() }) } fn runtime_connection_settings_response( api: &WorkspaceApi, runtime_config: &BackendRuntimesConfigFile, ) -> RuntimeConnectionSettingsResponse { RuntimeConnectionSettingsResponse { workspace_id: api.config.workspace_id.clone(), embedded: embedded_runtime_connection_summary(api), remotes: remote_runtime_connection_summaries(api, runtime_config, false), diagnostics: Vec::new(), } } fn runtime_connection_mutation_response( api: &WorkspaceApi, runtime_config: &BackendRuntimesConfigFile, diagnostics: Vec, ) -> RuntimeConnectionMutationResponse { RuntimeConnectionMutationResponse { workspace_id: api.config.workspace_id.clone(), restart_required: false, remotes: remote_runtime_connection_summaries(api, runtime_config, false), diagnostics, } } fn embedded_runtime_connection_summary(api: &WorkspaceApi) -> RuntimeConnectionSummary { let active = api .runtime .list_runtimes(api.config.max_records.min(200)) .items .into_iter() .find(|runtime| runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID); match active { Some(runtime) => RuntimeConnectionSummary { runtime_id: runtime.runtime_id, display_name: runtime.label, kind: runtime.kind, built_in: true, config_managed: false, active: runtime.status == "active", can_spawn_worker: runtime.capabilities.can_spawn_worker, restart_required: false, status: runtime.status, diagnostics: runtime.diagnostics, }, None => RuntimeConnectionSummary { runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), display_name: "Embedded Runtime".to_string(), kind: "embedded_worker_runtime".to_string(), built_in: true, config_managed: false, active: false, can_spawn_worker: false, restart_required: false, status: "unavailable".to_string(), diagnostics: vec![settings_diagnostic( "embedded_runtime_unavailable", DiagnosticSeverity::Warning, "The built-in embedded Runtime is not active in the current Runtime registry projection.", )], }, } } fn remote_runtime_connection_summaries( api: &WorkspaceApi, runtime_config: &BackendRuntimesConfigFile, restart_required: bool, ) -> Vec { let live_runtimes = api .runtime .list_runtimes(api.config.max_records.min(200)) .items; runtime_config .runtimes .remote .iter() .map(|remote| { let live = live_runtimes .iter() .find(|runtime| runtime.runtime_id == remote.id); let (display_name, kind, active, can_spawn_worker, status, diagnostics) = match live { Some(runtime) => ( runtime.label.clone(), runtime.kind.clone(), runtime.status == "active", runtime.capabilities.can_spawn_worker, runtime.status.clone(), runtime.diagnostics.clone(), ), None => ( remote .display_name .clone() .unwrap_or_else(|| remote.id.clone()), "remote_http".to_string(), false, false, "configured_restart_required".to_string(), if restart_required { vec![settings_diagnostic( "runtime_registry_restart_required", DiagnosticSeverity::Warning, "This remote Runtime config is persisted but not active until the Workspace backend restarts.", )] } else { Vec::new() }, ), }; RemoteRuntimeConnectionSummary { summary: RuntimeConnectionSummary { runtime_id: remote.id.clone(), display_name, kind, built_in: false, config_managed: true, active, can_spawn_worker, restart_required, status, diagnostics, }, endpoint_configured: !remote.endpoint.trim().is_empty(), token_ref_configured: remote .token_ref .as_deref() .is_some_and(|value| !value.trim().is_empty()), } }) .collect() } fn validate_runtime_connection_request( request: &AddRemoteRuntimeConnectionRequest, ) -> ApiResult<()> { validate_public_runtime_id(request.runtime_id.trim())?; let endpoint = request.endpoint.trim(); if endpoint.is_empty() || !(endpoint.starts_with("http://") || endpoint.starts_with("https://")) { return Err(settings_bad_request( "invalid_remote_runtime_endpoint", "endpoint must be an absolute http or https URL", )); } if request .display_name .as_deref() .is_some_and(|value| value.chars().any(char::is_control)) { return Err(settings_bad_request( "invalid_remote_runtime_display_name", "display_name cannot contain control characters", )); } Ok(()) } fn validate_public_runtime_id(runtime_id: &str) -> ApiResult<()> { if runtime_id.is_empty() { return Err(settings_bad_request( "invalid_runtime_id", "runtime_id must not be empty", )); } if runtime_id.len() > 96 || !runtime_id .chars() .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')) { return Err(settings_bad_request( "invalid_runtime_id", "runtime_id may contain only ASCII letters, digits, '-', '_' and '.' and must be at most 96 characters", )); } Ok(()) } fn remote_runtime_config_from_file( remote: &RemoteRuntimeConfigFile, ) -> std::result::Result { resolve_remote_runtime(remote).map_err(|err| { settings_diagnostic( "remote_runtime_apply_failed", DiagnosticSeverity::Error, err.to_string(), ) }) } async fn test_remote_runtime_config( api: &WorkspaceApi, remote: &RemoteRuntimeConfigFile, ) -> RemoteRuntimeTestResponse { let checked_at = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true); if remote .token_ref .as_deref() .is_some_and(|value| !value.trim().is_empty()) { return RemoteRuntimeTestResponse { workspace_id: api.config.workspace_id.clone(), runtime_id: remote.id.clone(), checked_at, state: "rejected".to_string(), protocol_version: None, compatibility_basis: "not_checked_token_ref_unsupported".to_string(), capabilities: Vec::new(), health_result: "not_checked".to_string(), diagnostics: vec![settings_diagnostic( "remote_runtime_token_ref_unsupported", DiagnosticSeverity::Error, "Remote Runtime test cannot use token_ref in v0; no token or secret value was exposed to the Browser.", )], }; } let client = match reqwest::Client::builder() .timeout(std::time::Duration::from_secs(5)) .build() { Ok(client) => client, Err(_) => { return remote_runtime_test_failed( api, remote, checked_at, "remote_runtime_test_client_unavailable", "Remote Runtime test client could not be initialized.", ); } }; let mut observation = RuntimeCompatibilityObservation::default(); let summary_url = match remote_probe_url(remote, "/v1/runtime") { Ok(url) => url, Err(diagnostic) => { return remote_runtime_test_failed( api, remote, checked_at, diagnostic.code, diagnostic.message, ); } }; let summary_payload = match probe_remote_json(&client, summary_url, "runtime.summary", "Runtime summary").await { Ok(payload) => payload, Err(diagnostic) => { return remote_runtime_test_failed( api, remote, checked_at, diagnostic.code, diagnostic.message, ); } }; let protocol_version = summary_payload .get("protocol_version") .and_then(|value| value.as_str()) .map(ToOwned::to_owned); let summary = match serde_json::from_value::(summary_payload) { Ok(summary) => summary, Err(_) => { return remote_runtime_test_failed( api, remote, checked_at, "remote_runtime_malformed_summary", "Remote Runtime summary responded, but the payload was not recognized.", ); } }; observation.available( "runtime.summary", "Connected: /v1/runtime responded with a recognized worker-runtime summary.", ); let workers_url = match remote_probe_url(remote, "/v1/workers") { Ok(url) => url, Err(diagnostic) => { observation.incompatible("workers.list", diagnostic); String::new() } }; let workers = if workers_url.is_empty() { None } else { match probe_remote_json(&client, workers_url, "workers.list", "Worker list").await { Ok(payload) => match serde_json::from_value::(payload) { Ok(workers) => { observation.available( "workers.list", "Verified: /v1/workers responded with a recognized worker list.", ); Some(workers) } Err(_) => { observation.incompatible( "workers.list", settings_diagnostic( "remote_runtime_workers_malformed", DiagnosticSeverity::Error, "Remote Runtime worker list responded, but the payload was not recognized.", ), ); None } }, Err(diagnostic) => { observation.incompatible("workers.list", diagnostic); None } } }; if let Some(worker) = workers.as_ref().and_then(|workers| workers.workers.first()) { let path = format!( "/v1/workers/{}", encode_path_segment(&worker.worker_id.to_string()) ); match remote_probe_url(remote, &path) { Ok(url) => match probe_remote_json(&client, url, "workers.detail", "Worker detail").await { Ok(payload) => match serde_json::from_value::(payload) { Ok(_) => observation.available( "workers.detail", "Verified: worker detail responded for an existing worker reported by the remote Runtime.", ), Err(_) => observation.incompatible( "workers.detail", settings_diagnostic( "remote_runtime_worker_detail_malformed", DiagnosticSeverity::Error, "Remote Runtime worker detail responded, but the payload was not recognized.", ), ), }, Err(diagnostic) => observation.incompatible("workers.detail", diagnostic), }, Err(diagnostic) => observation.incompatible("workers.detail", diagnostic), } } else { observation.unknown( "workers.detail", "No connection problem found. Worker detail was not checked because the remote Runtime reported no workers during the lightweight probe.", ); } observation.available( "workers.events_ws.construct", "Verified: worker event websocket URL can be constructed from the configured HTTP(S) Runtime endpoint. The lightweight test does not open a websocket stream.", ); let bundles_url = match remote_probe_url(remote, "/v1/config-bundles") { Ok(url) => url, Err(diagnostic) => { observation.incompatible("config_bundles.list", diagnostic); String::new() } }; let bundles = if bundles_url.is_empty() { None } else { match probe_remote_json( &client, bundles_url, "config_bundles.list", "Config-bundle list", ) .await { Ok(payload) => { match serde_json::from_value::(payload) { Ok(bundles) => { observation.available( "config_bundles.list", "Verified: /v1/config-bundles responded with a recognized config-bundle list.", ); Some(bundles) } Err(_) => { observation.incompatible( "config_bundles.list", settings_diagnostic( "remote_runtime_config_bundles_malformed", DiagnosticSeverity::Error, "Remote Runtime config-bundle list responded, but the payload was not recognized.", ), ); None } } } Err(diagnostic) => { observation.incompatible("config_bundles.list", diagnostic); None } } }; if let Some(bundle) = bundles.as_ref().and_then(|bundles| bundles.bundles.first()) { let path = format!( "/v1/config-bundles/{}/availability?digest={}", encode_path_segment(&bundle.id), encode_path_segment(&bundle.digest) ); match remote_probe_url(remote, &path) { Ok(url) => match probe_remote_json( &client, url, "config_bundles.availability", "Config-bundle availability", ) .await { Ok(payload) => { match serde_json::from_value::(payload) { Ok(_) => observation.available( "config_bundles.availability", "Verified: config-bundle availability was confirmed for an advertised bundle.", ), Err(_) => observation.incompatible( "config_bundles.availability", settings_diagnostic( "remote_runtime_config_bundle_availability_malformed", DiagnosticSeverity::Error, "Remote Runtime config-bundle availability responded, but the payload was not recognized.", ), ), } } Err(diagnostic) => { observation.incompatible("config_bundles.availability", diagnostic) } }, Err(diagnostic) => observation.incompatible("config_bundles.availability", diagnostic), } } else { observation.unknown( "config_bundles.availability", "No connection problem found. Config-bundle availability was not checked because the remote Runtime advertised no bundles during the lightweight probe.", ); } if summary.runtime.worker_creation_available { observation.available( "workers.spawn", "Verified: /v1/runtime reports worker creation is enabled by a Runtime execution backend. The lightweight test does not create a worker.", ); } else { observation.incompatible( "workers.spawn", settings_diagnostic( "remote_runtime_worker_creation_unavailable", DiagnosticSeverity::Error, "Connected to the Runtime, but worker creation is unavailable because this Runtime process has no execution backend attached.", ), ); } observation.unknown( "workers.input_dispatch", "No connection problem found. Worker input dispatch was not checked because this lightweight test does not send model-visible input as a side effect.", ); observation.unknown( "config_bundles.sync", "No connection problem found. Config-bundle sync was not checked because this lightweight test does not upload bundles as a side effect.", ); RemoteRuntimeTestResponse { workspace_id: api.config.workspace_id.clone(), runtime_id: remote.id.clone(), checked_at, state: observation.state().to_string(), protocol_version, compatibility_basis: "Connected to /v1/runtime and verified non-side-effecting worker-runtime HTTP endpoints. No incompatible operation was found; warning items below are unproven optional or side-effecting checks, not connection failures.".to_string(), capabilities: observation.capabilities, health_result: format!( "connected=true; runtime_status={:?}; available={}; incompatible={}; warnings={}", summary.runtime.status, observation.available_count, observation.incompatible_count, observation.unknown_count ), diagnostics: observation.diagnostics, } } fn remote_runtime_test_failed( api: &WorkspaceApi, remote: &RemoteRuntimeConfigFile, checked_at: String, code: impl Into, message: impl Into, ) -> RemoteRuntimeTestResponse { RemoteRuntimeTestResponse { workspace_id: api.config.workspace_id.clone(), runtime_id: remote.id.clone(), checked_at, state: "failed".to_string(), protocol_version: None, compatibility_basis: "worker-runtime lightweight HTTP compatibility probes".to_string(), capabilities: Vec::new(), health_result: "failed".to_string(), diagnostics: vec![settings_diagnostic( code, DiagnosticSeverity::Error, message, )], } } #[derive(Default)] struct RuntimeCompatibilityObservation { capabilities: Vec, diagnostics: Vec, available_count: usize, incompatible_count: usize, unknown_count: usize, } impl RuntimeCompatibilityObservation { fn available(&mut self, operation: &str, message: impl Into) { self.available_count += 1; self.capabilities.push(format!("{operation}:available")); self.diagnostics.push(settings_diagnostic( format!("{operation}.available"), DiagnosticSeverity::Info, message, )); } fn unknown(&mut self, operation: &str, message: impl Into) { self.unknown_count += 1; self.capabilities.push(format!("{operation}:unknown")); self.diagnostics.push(settings_diagnostic( format!("{operation}.unknown"), DiagnosticSeverity::Warning, message, )); } fn incompatible(&mut self, operation: &str, diagnostic: RuntimeDiagnostic) { self.incompatible_count += 1; self.capabilities.push(format!("{operation}:incompatible")); self.diagnostics.push(diagnostic); } fn state(&self) -> &'static str { if self.incompatible_count > 0 { "incompatible" } else { "compatible" } } } fn remote_probe_url( remote: &RemoteRuntimeConfigFile, path: &str, ) -> std::result::Result { let endpoint = remote.endpoint.trim(); if !(endpoint.starts_with("http://") || endpoint.starts_with("https://")) { return Err(settings_diagnostic( "remote_runtime_endpoint_invalid", DiagnosticSeverity::Error, "Configured remote Runtime endpoint is not an absolute HTTP(S) URL.", )); } Ok(format!("{}{}", endpoint.trim_end_matches('/'), path)) } async fn probe_remote_json( client: &reqwest::Client, url: String, operation: &'static str, label: &'static str, ) -> std::result::Result { let response = client.get(url).send().await.map_err(|error| { let (code, message) = if error.is_timeout() { ( format!("{operation}.timeout"), format!("Remote Runtime probe for {label} timed out."), ) } else if error.is_connect() { ( format!("{operation}.connect_failed"), format!("Remote Runtime probe for {label} could not connect."), ) } else { ( format!("{operation}.request_failed"), format!("Remote Runtime probe for {label} failed before a response was received."), ) }; settings_diagnostic(code, DiagnosticSeverity::Error, message) })?; if !response.status().is_success() { return Err(settings_diagnostic( format!("{operation}.http_status"), DiagnosticSeverity::Error, format!( "Remote Runtime probe for {label} returned HTTP status {}.", response.status().as_u16() ), )); } response.json::().await.map_err(|_| { settings_diagnostic( format!("{operation}.malformed_json"), DiagnosticSeverity::Error, format!("Remote Runtime probe for {label} returned an unrecognized JSON payload."), ) }) } fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResponse { let runtimes = api .runtime .list_runtimes(api.config.max_records.min(200)) .items .into_iter() .map(|runtime| { let built_in = runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID; WorkerLaunchRuntimeOption { runtime_id: runtime.runtime_id, display_name: runtime.label, built_in, can_spawn_worker: runtime.capabilities.can_spawn_worker, working_directory_required: !built_in, status: runtime.status, diagnostics: runtime.diagnostics, } }) .collect(); WorkerLaunchOptionsResponse { workspace_id: api.config.workspace_id.clone(), runtimes, profiles: worker_profile_candidates_for_root(&api.config.workspace_root), repositories: working_directory_repository_options(api), working_directories: available_working_directory_summaries(api).unwrap_or_default(), diagnostics: Vec::new(), } } fn working_directory_repository_options( api: &WorkspaceApi, ) -> Vec { api.config .repositories .iter() .map(|repository| WorkingDirectoryRepositoryOption { id: repository.id.clone(), display_name: repository .display_name .clone() .unwrap_or_else(|| repository.id.clone()), default_selector: repository.default_selector.clone(), }) .collect() } fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult> { let _ = sync_all_runtime_workdir_observations(api); let records = api .store .list_workdir_registry(&api.config.workspace_id, 200)?; records .iter() .map(|record| projected_workdir_summary_from_record(api, record)) .collect::>>() .map_err(ApiError::from) } fn available_working_directory_summaries( api: &WorkspaceApi, ) -> ApiResult> { let limit = api.config.max_records.min(200); for worker in api.runtime.list_workers(limit).items { let _ = sync_worker_observation(api, &worker); } let records = working_directory_summaries(api)?; let mut available = Vec::new(); for summary in records { if summary.status != WorkingDirectoryStatusKind::Active || summary.cleanliness.as_deref() != Some("clean") { continue; } if summary.occupied_by.is_none() && summary.primary_worker_id.is_none() { available.push(summary); } } Ok(available) } fn runtime_working_directory_summaries( api: &WorkspaceApi, runtime_id: &str, ) -> ApiResult<(Vec, Vec)> { let diagnostics = sync_runtime_workdir_observations(api, runtime_id)?; let records = api .store .list_workdir_registry(&api.config.workspace_id, 200)?; let items = records .iter() .filter(|record| record.runtime_id == runtime_id) .map(|record| projected_workdir_summary_from_record(api, record)) .collect::>>() .map_err(ApiError::from)?; Ok((items, diagnostics)) } static BACKEND_WORKDIR_ID_SEQUENCE: AtomicU64 = AtomicU64::new(0); fn now_registry_timestamp() -> String { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|duration| duration.as_millis().to_string()) .unwrap_or_else(|_| "0".to_string()) } fn next_backend_workdir_id(_repository_id: &str) -> String { let timestamp_ms = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|duration| duration.as_millis() as u64) .unwrap_or(0); let sequence = BACKEND_WORKDIR_ID_SEQUENCE.fetch_add(1, Ordering::Relaxed) & 0x00ff_ffff; format!("{timestamp_ms:013x}{sequence:06x}") } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum WorkerRegistryDisplayNamePolicy { PreserveExisting, UseProvided, } fn record_worker_summary( api: &WorkspaceApi, worker: &WorkerSummary, display_name: &str, profile: Option, display_name_policy: WorkerRegistryDisplayNamePolicy, ) -> ApiResult { let timestamp = now_registry_timestamp(); let runtime_worker_id = parse_runtime_worker_id_for_registry(worker.worker_id.as_str())?; let existing = api.store.get_worker_registry( &api.config.workspace_id, worker.runtime_id.as_str(), runtime_worker_id, )?; let display_name = match (display_name_policy, existing.as_ref()) { (WorkerRegistryDisplayNamePolicy::PreserveExisting, Some(record)) => { record.display_name.clone() } _ => display_name.to_string(), }; let record = WorkerRegistryRecord { workspace_id: api.config.workspace_id.clone(), runtime_id: worker.runtime_id.as_str().to_string(), runtime_worker_id, display_name, profile, retention_state: existing .as_ref() .map(|record| record.retention_state.clone()) .unwrap_or_else(|| "normal".to_string()), transcript_ref: Some(format!( "runtime://{}/workers/{}/transcript", worker.runtime_id.as_str(), worker.worker_id.as_str() )), session_ref: None, summary_ref: None, diagnostics_ref: None, created_at: timestamp.clone(), updated_at: timestamp, }; api.store.upsert_worker_registry(&record)?; Ok(api .store .get_worker_registry( &api.config.workspace_id, worker.runtime_id.as_str(), runtime_worker_id, )? .unwrap_or(record)) } fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary { WorkerSummary { worker_id: record.runtime_worker_id.to_string(), runtime_id: record.runtime_id.clone(), host_id: "backend-registry".to_string(), role: None, label: record.display_name.clone(), state: "missing".to_string(), last_seen_at: Some(record.updated_at.clone()), pinned: record.retention_state == "pinned", retention_state: record.retention_state.clone(), capabilities: WorkerCapabilitySummary { can_stop: false, can_spawn_followup: false, }, workspace: WorkerWorkspaceSummary { visibility: "backend_registry".to_string(), identity: record.workspace_id.clone(), }, profile: record.profile.clone(), implementation: WorkerImplementationSummary { kind: "backend_worker_registry".to_string(), display_hint: "Missing Worker".to_string(), }, working_directory: None, diagnostics: vec![RuntimeDiagnostic { code: "backend_worker_missing".to_string(), severity: DiagnosticSeverity::Info, message: "Worker is preserved in the Backend registry but the Runtime did not find it by id" .to_string(), }], } } fn merge_worker_registry_projection( live: Option<&WorkerSummary>, record: &WorkerRegistryRecord, links: Vec, workdirs: &[WorkdirRegistryRecord], ) -> WorkerSummary { let mut summary = live .cloned() .unwrap_or_else(|| worker_summary_from_registry(record)); summary.label = record.display_name.clone(); summary.profile = record.profile.clone(); summary.pinned = record.retention_state == "pinned"; summary.retention_state = record.retention_state.clone(); summary.working_directory = links.iter().find_map(|link| { workdirs .iter() .find(|workdir| workdir.workdir_id == link.workdir_id) .map(|workdir| { let mut workdir_summary = workdir_summary_from_record(workdir); workdir_summary.primary_worker_id = Some(WorkerId::new(record.runtime_worker_id)); workdir_summary.occupied_by = Some(WorkingDirectoryOccupancy { runtime_id: record.runtime_id.clone(), runtime_worker_id: record.runtime_worker_id, worker_id: format!("{}:{}", record.runtime_id, record.runtime_worker_id), display_name: record.display_name.clone(), linked_at: link.linked_at.clone(), }); workdir_summary }) }); summary } fn sync_worker_observation( api: &WorkspaceApi, worker: &WorkerSummary, ) -> ApiResult { let record = record_worker_summary( api, worker, worker.label.as_str(), worker.profile.clone(), WorkerRegistryDisplayNamePolicy::PreserveExisting, )?; if let Some(working_directory) = worker.working_directory.as_ref() { let workdir_record = workdir_record_from_summary(api, worker.runtime_id.as_str(), working_directory); api.store.upsert_workdir_registry(&workdir_record)?; link_worker_to_workdir(api, &record, &working_directory.working_directory_id)?; } Ok(record) } fn upsert_pending_backend_workdir( api: &WorkspaceApi, runtime_id: &str, request: &mut WorkingDirectoryRequest, ) -> ApiResult { let workdir_id = request .backend_workdir_id .clone() .unwrap_or_else(|| next_backend_workdir_id(&request.repository.id)); request.backend_workdir_id = Some(workdir_id.clone()); let timestamp = now_registry_timestamp(); api.store.upsert_workdir_registry(&WorkdirRegistryRecord { workspace_id: api.config.workspace_id.clone(), workdir_id: workdir_id.clone(), runtime_id: runtime_id.to_string(), repository_id: request.repository.id.clone(), selector: request .repository .selector .as_ref() .map(|selector| selector.as_ref().to_string()), resolved_commit: None, materialization_status: "pending".to_string(), cleanliness: "unknown".to_string(), created_at: timestamp.clone(), updated_at: timestamp, })?; Ok(workdir_id) } fn sync_runtime_workdir_observations( api: &WorkspaceApi, runtime_id: &str, ) -> ApiResult> { let response = api .runtime .list_working_directories(runtime_id) .map_err(|err| err.into_error())?; let mut observed = std::collections::BTreeSet::new(); for status in &response.items { observed.insert(status.summary.working_directory_id.clone()); if status.summary.status == WorkingDirectoryStatusKind::NotFound { api.store.delete_workdir_registry( &api.config.workspace_id, &status.summary.working_directory_id, )?; continue; } let existing = api.store.get_workdir_registry( &api.config.workspace_id, &status.summary.working_directory_id, )?; let mut record = workdir_record_from_summary(api, runtime_id, &status.summary); preserve_workdir_identity_for_corrupted_summary(&mut record, existing.as_ref()); api.store.upsert_workdir_registry(&record)?; } for mut record in api .store .list_workdir_registry(&api.config.workspace_id, 500)? .into_iter() .filter(|record| record.runtime_id == runtime_id && !observed.contains(&record.workdir_id)) { match api .runtime .working_directory(runtime_id, record.workdir_id.as_str()) { Ok(result) => { if let Some(status) = result.working_directory { if status.summary.status == WorkingDirectoryStatusKind::NotFound { api.store.delete_workdir_registry( &api.config.workspace_id, record.workdir_id.as_str(), )?; } else { let mut updated = workdir_record_from_summary(api, runtime_id, &status.summary); preserve_workdir_identity_for_corrupted_summary( &mut updated, Some(&record), ); api.store.upsert_workdir_registry(&updated)?; } } else { record.materialization_status = workdir_status_from_runtime_miss(result.diagnostics.as_slice()).to_string(); record.cleanliness = "unknown".to_string(); record.updated_at = now_registry_timestamp(); api.store.upsert_workdir_registry(&record)?; } } Err(_) => { record.materialization_status = "unknown".to_string(); record.cleanliness = "unknown".to_string(); record.updated_at = now_registry_timestamp(); api.store.upsert_workdir_registry(&record)?; } } } Ok(response.diagnostics) } fn workdir_status_from_runtime_miss(diagnostics: &[RuntimeDiagnostic]) -> &'static str { if diagnostics .iter() .any(|diagnostic| diagnostic.code == "working_directory_not_found") { "not_found" } else { "unknown" } } fn sync_all_runtime_workdir_observations(api: &WorkspaceApi) -> Vec { let mut diagnostics = Vec::new(); let runtimes = api.runtime.list_runtimes(api.config.max_records.min(200)); for runtime in runtimes.items { if runtime.capabilities.supports_worktrees { match sync_runtime_workdir_observations(api, runtime.runtime_id.as_str()) { Ok(mut runtime_diagnostics) => diagnostics.append(&mut runtime_diagnostics), Err(err) => diagnostics.extend(err.diagnostics), } } } diagnostics } fn sync_linked_workdir_after_worker_stop( api: &WorkspaceApi, runtime_id: &str, worker_record: &WorkerRegistryRecord, ) -> ApiResult<()> { let links = api.store.list_worker_workdir_links( &api.config.workspace_id, worker_record.runtime_id.as_str(), worker_record.runtime_worker_id, )?; for link in links { let result = api .runtime .working_directory(runtime_id, link.workdir_id.as_str()) .map_err(|err| err.into_error())?; if let Some(status) = result.working_directory { let record = workdir_record_from_summary(api, runtime_id, &status.summary); api.store.upsert_workdir_registry(&record)?; } else if let Some(mut record) = api .store .get_workdir_registry(&api.config.workspace_id, link.workdir_id.as_str())? { record.materialization_status = workdir_status_from_runtime_miss(result.diagnostics.as_slice()).to_string(); record.cleanliness = "unknown".to_string(); record.updated_at = now_registry_timestamp(); api.store.upsert_workdir_registry(&record)?; } } Ok(()) } fn workdir_record_from_summary( api: &WorkspaceApi, runtime_id: &str, summary: &WorkingDirectorySummary, ) -> WorkdirRegistryRecord { let timestamp = now_registry_timestamp(); WorkdirRegistryRecord { workspace_id: api.config.workspace_id.clone(), workdir_id: summary.working_directory_id.clone(), runtime_id: runtime_id.to_string(), repository_id: summary.repository_id.clone(), selector: summary.requested_selector.clone(), resolved_commit: summary.resolved_commit.clone(), materialization_status: match summary.status { WorkingDirectoryStatusKind::Active => "present", WorkingDirectoryStatusKind::CleanupPending => "pending", WorkingDirectoryStatusKind::Corrupted => "corrupted", WorkingDirectoryStatusKind::NotFound => "not_found", WorkingDirectoryStatusKind::Unknown => "unknown", } .to_string(), cleanliness: summary .cleanliness .clone() .unwrap_or_else(|| "unknown".to_string()), created_at: timestamp.clone(), updated_at: timestamp, } } fn preserve_workdir_identity_for_corrupted_summary( record: &mut WorkdirRegistryRecord, existing: Option<&WorkdirRegistryRecord>, ) { if record.materialization_status != "corrupted" { return; } let Some(existing) = existing else { return; }; if record.repository_id == "unknown" { record.repository_id = existing.repository_id.clone(); } if record.selector.is_none() { record.selector = existing.selector.clone(); } if record.resolved_commit.is_none() { record.resolved_commit = existing.resolved_commit.clone(); } } fn workdir_summary_from_record(record: &WorkdirRegistryRecord) -> WorkingDirectorySummary { let status = match record.materialization_status.as_str() { "present" => WorkingDirectoryStatusKind::Active, "pending" => WorkingDirectoryStatusKind::CleanupPending, "corrupted" => WorkingDirectoryStatusKind::Corrupted, "not_found" | "missing" => WorkingDirectoryStatusKind::NotFound, "unknown" => WorkingDirectoryStatusKind::Unknown, _ => WorkingDirectoryStatusKind::Unknown, }; WorkingDirectorySummary { working_directory_id: record.workdir_id.clone(), repository_id: record.repository_id.clone(), requested_selector: record.selector.clone(), materializer_kind: MaterializerKind::LocalGitWorktree, resolved_commit: record.resolved_commit.clone(), resolved_tree: None, cleanup_target: Some(worker_runtime::catalog::WorkingDirectoryCleanupTarget { kind: "local_git_worktree".to_string(), working_directory_id: record.workdir_id.clone(), repository_id: record.repository_id.clone(), }), status, cleanliness: Some(record.cleanliness.clone()), primary_worker_id: None, occupied_by: None, } } fn apply_workdir_occupancy_projection( api: &WorkspaceApi, summary: &mut WorkingDirectorySummary, ) -> Result<()> { let links = api .store .list_workdir_worker_links(&api.config.workspace_id, &summary.working_directory_id)?; let Some(link) = links.first() else { summary.primary_worker_id = None; summary.occupied_by = None; return Ok(()); }; let worker = api.store.get_worker_registry( &api.config.workspace_id, &link.runtime_id, link.runtime_worker_id, )?; let display_name = worker .as_ref() .map(|worker| worker.display_name.clone()) .unwrap_or_else(|| format!("{}:{}", link.runtime_id, link.runtime_worker_id)); summary.primary_worker_id = Some(WorkerId::new(link.runtime_worker_id)); summary.occupied_by = Some(WorkingDirectoryOccupancy { runtime_id: link.runtime_id.clone(), runtime_worker_id: link.runtime_worker_id, worker_id: format!("{}:{}", link.runtime_id, link.runtime_worker_id), display_name, linked_at: link.linked_at.clone(), }); Ok(()) } fn projected_workdir_summary_from_record( api: &WorkspaceApi, record: &WorkdirRegistryRecord, ) -> Result { let mut summary = workdir_summary_from_record(record); apply_workdir_occupancy_projection(api, &mut summary)?; Ok(summary) } fn link_worker_to_workdir( api: &WorkspaceApi, worker_record: &WorkerRegistryRecord, workdir_id: &str, ) -> ApiResult<()> { let timestamp = now_registry_timestamp(); api.store .upsert_worker_workdir_link(&WorkerWorkdirLinkRecord { workspace_id: api.config.workspace_id.clone(), runtime_id: worker_record.runtime_id.clone(), runtime_worker_id: worker_record.runtime_worker_id.clone(), workdir_id: workdir_id.to_string(), role: "primary_cwd".to_string(), linked_at: timestamp, unlinked_at: None, })?; Ok(()) } fn validate_working_directory_claim_for_browser( claim: Option<&WorkingDirectoryClaim>, ) -> ApiResult<()> { let Some(claim) = claim else { return Ok(()); }; if let Some(relative_cwd) = claim.relative_cwd.as_deref() { let path = Path::new(relative_cwd); if path.is_absolute() || path .components() .any(|component| !matches!(component, Component::CurDir | Component::Normal(_))) { return Err(ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), code: "working_directory_relative_cwd_invalid".to_string(), message: "working directory relative_cwd must stay inside the Runtime-owned working directory".to_string(), }, vec![RuntimeDiagnostic { code: "working_directory_relative_cwd_invalid".to_string(), severity: DiagnosticSeverity::Error, message: "relative_cwd must be a relative path without parent traversal".to_string(), }], )); } } Ok(()) } fn working_directory_request_for_browser( api: &WorkspaceApi, request: BrowserWorkingDirectoryCreateRequest, ) -> ApiResult { let repository = api .config .repositories .iter() .find(|repository| repository.id == request.repository_id) .ok_or_else(|| Error::UnknownRepository(request.repository_id.clone()))?; let selector = request .selector .or_else(|| repository.default_selector.clone()) .filter(|selector| !selector.trim().is_empty()); Ok(WorkingDirectoryRequest { repository: WorkingDirectoryRepository { id: repository.id.clone(), provider: "git".to_string(), uri: repository.path.to_string_lossy().to_string(), local_path: Some(repository.path.clone()), selector: selector.map(RuntimeRepositorySelector), }, materializer: MaterializerKind::LocalGitWorktree, backend_workdir_id: None, }) } #[cfg(test)] fn worker_profile_candidates() -> Vec { worker_profile_candidates_for_root(Path::new(".")) .into_iter() .filter(|candidate| candidate.id == "builtin:coder" || candidate.id == "runtime_default") .collect() } fn worker_profile_candidates_for_root(workspace_root: &Path) -> Vec { let mut candidates = vec![ WorkerLaunchProfileCandidate { id: "builtin:coder".to_string(), label: "Coding Worker".to_string(), description: "Built-in coding role profile for implementation work.".to_string(), }, WorkerLaunchProfileCandidate { id: "runtime_default".to_string(), label: "Runtime default".to_string(), description: "Use the selected Runtime's default profile.".to_string(), }, ]; candidates.extend( crate::profile_settings::project_profile_candidates(workspace_root) .into_iter() .filter(|profile| profile.diagnostics.is_empty()) .map(|profile| WorkerLaunchProfileCandidate { id: profile.profile_id, label: profile.label, description: profile .description .unwrap_or_else(|| "Workspace Decodal profile source.".to_string()), }), ); candidates } fn profile_selector_for_candidate(profile: &str) -> Option { crate::profile_settings::selector_for_builtin_candidate(profile) .filter(|_| matches!(profile, "builtin:coder" | "runtime_default")) } fn profile_selector_for_candidate_with_root( workspace_root: &Path, profile: &str, ) -> Option { if profile_selector_for_candidate(profile).is_some() { profile_selector_for_candidate(profile) } else if crate::profile_settings::is_profile_candidate(workspace_root, profile) { crate::profile_settings::selector_for_builtin_candidate(profile) } else { None } } fn parse_runtime_worker_id_for_registry(worker_id: &str) -> ApiResult { worker_id.parse::().map_err(|_| { settings_bad_request( "workspace_worker_id_invalid", "Runtime Worker id must be an unsigned integer", ) }) } fn sanitize_worker_display_name(value: &str) -> Option { let display_name = value.trim(); if display_name.chars().any(char::is_control) { None } else if display_name.is_empty() { Some("Coding Worker".to_string()) } else { Some(display_name.chars().take(80).collect()) } } fn encode_path_segment(value: &str) -> String { value .bytes() .flat_map(|byte| match byte { b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { vec![byte as char] } _ => format!("%{byte:02X}").chars().collect(), }) .collect() } fn worker_create_not_accepted_error( runtime_id: String, mut diagnostics: Vec, ) -> ApiError { diagnostics.push(settings_diagnostic( "workspace_worker_create_not_accepted", DiagnosticSeverity::Error, "Runtime did not accept worker creation; see diagnostics for sanitized Runtime compatibility details.", )); ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id, code: "workspace_worker_create_failed".to_string(), message: "Runtime did not accept worker creation".to_string(), }, diagnostics, ) } fn settings_bad_request(code: &'static str, message: &'static str) -> ApiError { Error::RuntimeOperationFailed { runtime_id: "workspace-backend".to_string(), code: code.to_string(), message: message.to_string(), } .into() } fn settings_diagnostic( code: impl Into, severity: DiagnosticSeverity, message: impl Into, ) -> RuntimeDiagnostic { RuntimeDiagnostic { code: code.into(), severity, message: message.into(), } } fn sanitize_backend_error(message: &str) -> String { message.to_string() } fn repository_diagnostics( diagnostics: Vec, ) -> Vec { diagnostics .into_iter() .map(|diagnostic| RuntimeDiagnostic { code: diagnostic.code, severity: match diagnostic.severity.as_str() { "error" => DiagnosticSeverity::Error, "warning" => DiagnosticSeverity::Warning, _ => DiagnosticSeverity::Info, }, message: diagnostic.message, }) .collect() } fn repository_lookup(result: std::result::Result) -> ApiResult { result.map_err(|error| match error { RepositoryLookupError::UnknownRepository { id } => { let message = format!("repository `{id}` is not configured for this workspace"); ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: "workspace-repository-registry".to_string(), code: "repository_not_configured".to_string(), message: message.clone(), }, vec![RuntimeDiagnostic { code: "repository_not_configured".to_string(), severity: DiagnosticSeverity::Error, message, }], ) } RepositoryLookupError::UnsupportedProvider { id, provider } => { let message = format!( "repository `{id}` uses unsupported provider `{provider}` for this operation" ); ApiError::with_diagnostics( Error::RuntimeOperationFailed { runtime_id: "workspace-repository-registry".to_string(), code: "repository_provider_unsupported".to_string(), message: message.clone(), }, vec![RuntimeDiagnostic { code: "repository_provider_unsupported".to_string(), severity: DiagnosticSeverity::Error, message, }], ) } }) } fn ticket_kanban_columns(items: Vec) -> Vec { let mut columns = vec![ TicketKanbanColumn { state: "planning".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "ready".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "queued".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "inprogress".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "done".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "closed".to_string(), items: Vec::new(), }, TicketKanbanColumn { state: "other".to_string(), items: Vec::new(), }, ]; for item in items { let index = match item.state.as_str() { "planning" => 0, "ready" => 1, "queued" => 2, "inprogress" => 3, "done" => 4, "closed" => 5, _ => 6, }; columns[index].items.push(item); } columns } async fn static_or_spa_fallback(State(api): State, uri: Uri) -> Response { if uri.path().starts_with("/api/") || uri.path() == "/api" { return ( StatusCode::NOT_FOUND, [(CONTENT_TYPE, "application/json")], Json(serde_json::json!({ "error": "not_found", "message": "unknown api route" })) .to_string(), ) .into_response(); } if let Some(workspace_id) = workspace_id_from_ui_path(uri.path()) { if workspace_id != api.workspace_id() { return workspace_id_mismatch_error().into_response(); } } if let Some(location) = unscoped_workspace_ui_redirect(uri.path(), uri.query(), api.workspace_id()) { return (StatusCode::TEMPORARY_REDIRECT, [(LOCATION, location)]).into_response(); } let Some(static_root) = api.config.static_assets_dir.as_ref() else { return StatusCode::NOT_FOUND.into_response(); }; match read_static_or_index(static_root, uri.path()).await { Ok(StaticAsset { bytes, content_type, }) => (StatusCode::OK, [(CONTENT_TYPE, content_type)], bytes).into_response(), Err(error) => { tracing::debug!(%error, path = %uri.path(), "failed to serve static asset"); StatusCode::NOT_FOUND.into_response() } } } fn unscoped_workspace_ui_redirect( path: &str, query: Option<&str>, workspace_id: &str, ) -> Option { let scoped_tail = if path == "/" { "" } else if ["/repositories", "/objectives", "/settings", "/runtimes"] .iter() .any(|prefix| path == *prefix || path.starts_with(&format!("{prefix}/"))) { path } else { return None; }; let mut location = format!("/w/{}{}", encode_path_segment(workspace_id), scoped_tail); if let Some(query) = query.filter(|query| !query.is_empty()) { location.push('?'); location.push_str(query); } Some(location) } fn workspace_id_from_ui_path(path: &str) -> Option<&str> { let tail = path.strip_prefix("/w/")?; let workspace_id = tail.split('/').next().unwrap_or_default(); if workspace_id.is_empty() { None } else { Some(workspace_id) } } struct StaticAsset { bytes: Vec, content_type: &'static str, } async fn read_static_or_index(root: &Path, request_path: &str) -> Result { let candidate = safe_static_candidate(root, request_path)?; let file = if tokio::fs::metadata(&candidate) .await .map(|m| m.is_file()) .unwrap_or(false) { candidate } else { root.join("index.html") }; let content_type = content_type_for(&file); let bytes = tokio::fs::read(file).await?; Ok(StaticAsset { bytes, content_type, }) } fn safe_static_candidate(root: &Path, request_path: &str) -> Result { let mut path = root.to_path_buf(); let clean = request_path.trim_start_matches('/'); if clean.is_empty() { path.push("index.html"); return Ok(path); } for component in Path::new(clean).components() { match component { Component::Normal(part) => path.push(part), Component::CurDir => {} _ => return Err(Error::Store("static path escape rejected".to_string())), } } Ok(path) } fn content_type_for(path: &Path) -> &'static str { match path .extension() .and_then(|ext| ext.to_str()) .unwrap_or_default() { "css" => "text/css; charset=utf-8", "js" => "text/javascript; charset=utf-8", "json" => "application/json", "svg" => "image/svg+xml", "html" | "" => "text/html; charset=utf-8", _ => "application/octet-stream", } } type ApiResult = std::result::Result; fn workspace_id_mismatch_error() -> ApiError { let message = "workspace id does not match this Workspace backend".to_string(); ApiError::with_diagnostics( Error::WorkspaceIdMismatch, vec![RuntimeDiagnostic { code: "workspace_id_mismatch".to_string(), severity: DiagnosticSeverity::Error, message, }], ) } struct ApiError { error: Error, diagnostics: Vec, } impl From for ApiError { fn from(error: Error) -> Self { let diagnostics = match &error { Error::RuntimeOperationFailed { code, message, .. } => vec![RuntimeDiagnostic { code: code.clone(), severity: DiagnosticSeverity::Error, message: sanitize_backend_error(message), }], _ => Vec::new(), }; Self { error, diagnostics } } } impl ApiError { fn with_diagnostics(error: Error, diagnostics: Vec) -> Self { Self { error, diagnostics } } } impl IntoResponse for ApiError { fn into_response(self) -> Response { let status = match &self.error { Error::InvalidRuntimeIdentifier { .. } => StatusCode::BAD_REQUEST, Error::InvalidRecordId(_) | Error::MissingFrontmatter(_) | Error::UnknownHost(_) | Error::UnknownRuntime(_) | Error::UnknownWorker { .. } | Error::UnknownRepository(_) | Error::WorkspaceIdMismatch => StatusCode::NOT_FOUND, Error::RuntimeOperationFailed { code, .. } if code == "skill_not_found" => { StatusCode::NOT_FOUND } Error::RuntimeCapabilityUnsupported { .. } => StatusCode::NOT_IMPLEMENTED, Error::RuntimeOperationFailed { code, .. } if code == "repository_not_configured" => { StatusCode::NOT_FOUND } Error::RuntimeOperationFailed { code, .. } if code == "repository_provider_unsupported" => { StatusCode::BAD_REQUEST } Error::RuntimeOperationFailed { code, .. } if code == "remote_runtime_auth_failed" => { StatusCode::UNAUTHORIZED } Error::RuntimeOperationFailed { code, .. } if code == "remote_runtime_timeout" => { StatusCode::GATEWAY_TIMEOUT } Error::RuntimeOperationFailed { code, .. } if code == "remote_runtime_unsupported" => { StatusCode::NOT_IMPLEMENTED } Error::RuntimeOperationFailed { code, .. } if code == "profile_registry_revision_conflict" || code == "profile_source_revision_conflict" || code == "workspace_metadata_revision_conflict" || code == "workspace_cleanup_plan_stale" || code == "workspace_cleanup_worker_blocked" || code == "workspace_cleanup_workdir_blocked" || code == "workspace_cleanup_worker_pinned" => { StatusCode::CONFLICT } Error::RuntimeOperationFailed { code, .. } if code == "unknown_profile_source" || code == "unknown_profile_selector" => { StatusCode::NOT_FOUND } Error::RuntimeOperationFailed { code, .. } if code == "workspace_display_name_invalid" || code.starts_with("profile_") => { StatusCode::BAD_REQUEST } Error::RuntimeOperationFailed { code, .. } if code.ends_with("_blocked") => { StatusCode::CONFLICT } Error::RuntimeOperationFailed { code, .. } if code.starts_with("workspace_settings_") || code.starts_with("invalid_") || code.starts_with("unsupported_worker_profile") || code.starts_with("working_directory_") || code.starts_with("workspace_cleanup_") || code == "workspace_worker_workdir_required" || code.ends_with("_already_exists") || code.ends_with("_not_config_managed") || code.ends_with("_unsupported") => { StatusCode::BAD_REQUEST } Error::RuntimeOperationFailed { .. } => StatusCode::BAD_GATEWAY, _ => StatusCode::INTERNAL_SERVER_ERROR, }; let response_message = match &self.error { Error::RuntimeOperationFailed { code, message, .. } => { format!("{code}: {}", sanitize_backend_error(message)) } _ => sanitize_backend_error(&self.error.to_string()), }; ( status, [(CONTENT_TYPE, "application/json")], Json(serde_json::json!({ "error": status.canonical_reason().unwrap_or("error"), "message": response_message, "diagnostics": self.diagnostics, })) .to_string(), ) .into_response() } } #[cfg(test)] mod tests { use super::*; use crate::config::WorkspaceBackendRuntimesConfig; use axum::body::{Body, to_bytes}; use axum::http::Request; use futures::{SinkExt, StreamExt}; use serde_json::{Value, json}; use std::{fs, sync::Arc}; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; use tower::ServiceExt; use worker_runtime::resource::BackendResourceClient; use worker_runtime::working_directory::WorkingDirectoryMaterializer; use crate::hosts::{ TicketWorkerRole, WorkerInputKind, WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, }; use crate::store::{ MemoryDocumentRecord, MemoryStagingRecord, ObjectiveRecord, ObjectiveResourceRecord, ObjectiveTicketLinkRecord, SqliteWorkspaceStore, }; const TEST_WORKSPACE_ID: &str = "0192f0e8-4d84-7d6e-a000-000000000001"; const TEST_REPOSITORY_ID: &str = "main"; const TEST_CREATED_AT: &str = "2026-06-23T06:43:28Z"; #[test] fn backend_worker_projection_preserves_missing_rows_links_and_redacts_paths() { let worker = WorkerRegistryRecord { workspace_id: "workspace-1".to_string(), runtime_id: "embedded".to_string(), runtime_worker_id: 1, display_name: "Missing Worker".to_string(), profile: Some("builtin:coder".to_string()), retention_state: "pinned".to_string(), transcript_ref: Some("runtime://embedded/workers/worker-1/transcript".to_string()), session_ref: None, summary_ref: None, diagnostics_ref: None, created_at: "1".to_string(), updated_at: "2".to_string(), }; let workdir = WorkdirRegistryRecord { workspace_id: "workspace-1".to_string(), workdir_id: "0000019a00000000000".to_string(), runtime_id: "embedded".to_string(), repository_id: "repo".to_string(), selector: Some("develop".to_string()), resolved_commit: Some("abcdef".to_string()), materialization_status: "missing".to_string(), cleanliness: "clean".to_string(), created_at: "1".to_string(), updated_at: "3".to_string(), }; let link = WorkerWorkdirLinkRecord { workspace_id: "workspace-1".to_string(), runtime_id: worker.runtime_id.clone(), runtime_worker_id: worker.runtime_worker_id.clone(), workdir_id: workdir.workdir_id.clone(), role: "primary_cwd".to_string(), linked_at: "4".to_string(), unlinked_at: None, }; let projected = merge_worker_registry_projection(None, &worker, vec![link], &[workdir]); assert_eq!(projected.state, "missing"); let working_directory = projected.working_directory.as_ref().unwrap(); assert_eq!( working_directory.status, WorkingDirectoryStatusKind::NotFound ); let occupied_by = working_directory.occupied_by.as_ref().unwrap(); assert_eq!(occupied_by.runtime_id, "embedded"); assert_eq!(occupied_by.runtime_worker_id, 1); assert_eq!(occupied_by.display_name, "Missing Worker"); assert_eq!(occupied_by.linked_at, "4"); let serialized = serde_json::to_string(&projected).unwrap(); assert!(!serialized.contains("/tmp/")); assert!(!serialized.contains("materialized_path")); } #[tokio::test] async fn workspace_workdir_summaries_include_runtime_observed_rows() { let dir = tempfile::tempdir().unwrap(); let api = test_api(dir.path()).await; api.store .upsert_workdir_registry(&WorkdirRegistryRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), workdir_id: "managed".to_string(), runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), repository_id: "repo".to_string(), selector: None, resolved_commit: None, materialization_status: "present".to_string(), cleanliness: "clean".to_string(), created_at: "1".to_string(), updated_at: "1".to_string(), }) .unwrap(); api.store .upsert_workdir_registry(&WorkdirRegistryRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), workdir_id: "runtime-direct".to_string(), runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), repository_id: "repo".to_string(), selector: None, resolved_commit: None, materialization_status: "present".to_string(), cleanliness: "unknown".to_string(), created_at: "1".to_string(), updated_at: "2".to_string(), }) .unwrap(); api.store .upsert_worker_registry(&WorkerRegistryRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), runtime_worker_id: 7, display_name: "Worker Seven".to_string(), profile: Some("builtin:coder".to_string()), retention_state: "normal".to_string(), transcript_ref: None, session_ref: None, summary_ref: None, diagnostics_ref: None, created_at: "1".to_string(), updated_at: "2".to_string(), }) .unwrap(); api.store .upsert_worker_workdir_link(&WorkerWorkdirLinkRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), runtime_worker_id: 7, workdir_id: "managed".to_string(), role: "primary_cwd".to_string(), linked_at: "3".to_string(), unlinked_at: None, }) .unwrap(); let summaries = working_directory_summaries(&api) .unwrap_or_else(|err| panic!("working_directory_summaries failed: {}", err.error)); let ids = summaries .iter() .map(|summary| summary.working_directory_id.as_str()) .collect::>(); assert!(ids.contains(&"managed")); assert!(ids.contains(&"runtime-direct")); let managed = summaries .iter() .find(|summary| summary.working_directory_id == "managed") .unwrap(); let occupied_by = managed.occupied_by.as_ref().unwrap(); assert_eq!(occupied_by.runtime_id, EMBEDDED_WORKER_RUNTIME_ID); assert_eq!(occupied_by.runtime_worker_id, 7); assert_eq!(occupied_by.display_name, "Worker Seven"); assert_eq!(occupied_by.linked_at, "3"); let (runtime_projection, _) = runtime_working_directory_summaries(&api, EMBEDDED_WORKER_RUNTIME_ID).unwrap_or_else( |err| panic!("runtime_working_directory_summaries failed: {}", err.error), ); assert!( runtime_projection .iter() .any(|summary| summary.working_directory_id == "runtime-direct") ); } #[test] fn unmanaged_runtime_workdir_projection_is_typed_and_diagnostic_safe() { let workdir = WorkdirRegistryRecord { workspace_id: "workspace-1".to_string(), workdir_id: "runtime-direct".to_string(), runtime_id: "embedded".to_string(), repository_id: "repo".to_string(), selector: None, resolved_commit: None, materialization_status: "present".to_string(), cleanliness: "unknown".to_string(), created_at: "1".to_string(), updated_at: "2".to_string(), }; let projected = workdir_summary_from_record(&workdir); assert_eq!(projected.status, WorkingDirectoryStatusKind::Active); let serialized = serde_json::to_string(&projected).unwrap(); assert!(!serialized.contains("/tmp/")); assert!(!serialized.contains("materialized_path")); } #[test] fn worker_profile_candidates_are_backend_published_and_mapped() { let candidates = worker_profile_candidates(); assert!( candidates .iter() .any(|candidate| candidate.id == "builtin:coder") ); assert!(matches!( profile_selector_for_candidate("builtin:coder"), Some(ProfileSelector::Builtin(value)) if value == "builtin:coder" )); assert!(profile_selector_for_candidate("free-text-profile").is_none()); } #[test] fn runtime_connection_request_validation_bounds_browser_input() { let ok = AddRemoteRuntimeConnectionRequest { runtime_id: "team-runtime_1".to_string(), display_name: Some("Team Runtime".to_string()), endpoint: "https://runtime.example".to_string(), token_ref: None, }; assert!(validate_runtime_connection_request(&ok).is_ok()); let bad_endpoint = AddRemoteRuntimeConnectionRequest { endpoint: "/tmp/socket".to_string(), ..ok }; assert!(validate_runtime_connection_request(&bad_endpoint).is_err()); } #[test] fn backend_errors_preserve_operation_details() { let sanitized = sanitize_backend_error( "failed to open /home/example/.yoi/workspace-backend.local.toml", ); assert_eq!( sanitized, "failed to open /home/example/.yoi/workspace-backend.local.toml" ); } #[test] fn workdir_runtime_miss_uses_exact_typed_code() { assert_eq!( workdir_status_from_runtime_miss(&[RuntimeDiagnostic { code: "working_directory_not_found".to_string(), severity: DiagnosticSeverity::Warning, message: "missing".to_string(), }]), "not_found" ); assert_eq!( workdir_status_from_runtime_miss(&[RuntimeDiagnostic { code: "some_other_not_found".to_string(), severity: DiagnosticSeverity::Warning, message: "not a typed workdir miss".to_string(), }]), "unknown" ); } #[tokio::test] async fn profile_settings_api_returns_typed_diagnostics_for_duplicate_selector() { let dir = tempfile::tempdir().unwrap(); let response = profile_settings_request( dir.path(), "POST", "/settings/profiles", json!({ "name": "coder", "content": valid_profile_source("coder"), "registry_revision": "missing" }), ) .await; assert_eq!(response.0, StatusCode::BAD_REQUEST); assert_diagnostic(&response.1, "profile_selector_duplicate"); } #[tokio::test] async fn profile_settings_api_returns_typed_diagnostics_for_invalid_decodal() { let dir = tempfile::tempdir().unwrap(); let response = profile_settings_request( dir.path(), "POST", "/settings/profiles", json!({ "name": "bad", "content": "not decodal", "registry_revision": "missing" }), ) .await; assert_eq!(response.0, StatusCode::BAD_REQUEST); assert!( diagnostic_codes(&response.1) .iter() .any(|code| code.starts_with("profile_source_")) ); } #[tokio::test] async fn profile_settings_api_returns_typed_diagnostic_for_invalid_registry_schema() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join(".yoi")).unwrap(); fs::write(dir.path().join(".yoi/profiles.toml"), "[profile\n").unwrap(); let response = profile_settings_request( dir.path(), "POST", "/settings/profiles", json!({ "name": "schema", "content": valid_profile_source("schema"), "registry_revision": test_file_revision(&dir.path().join(".yoi/profiles.toml")) }), ) .await; assert_eq!(response.0, StatusCode::BAD_REQUEST); assert_diagnostic(&response.1, "profile_registry_schema_invalid"); } #[tokio::test] async fn profile_settings_api_returns_conflict_diagnostic_for_stale_revision() { let dir = tempfile::tempdir().unwrap(); let response = profile_settings_request( dir.path(), "POST", "/settings/profiles", json!({ "name": "alpha", "content": valid_profile_source("alpha"), "registry_revision": "stale" }), ) .await; assert_eq!(response.0, StatusCode::CONFLICT); assert_diagnostic(&response.1, "profile_registry_revision_conflict"); } #[tokio::test] async fn profile_settings_api_returns_typed_diagnostic_for_too_large_source() { let dir = tempfile::tempdir().unwrap(); let response = profile_settings_request( dir.path(), "POST", "/settings/profiles", json!({ "name": "large", "content": "x".repeat((256 * 1024) + 1), "registry_revision": "missing" }), ) .await; assert_eq!(response.0, StatusCode::BAD_REQUEST); assert_diagnostic(&response.1, "profile_source_too_large"); } #[cfg(unix)] #[tokio::test] async fn profile_settings_api_redacts_symlink_escape_response() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join(".yoi/profiles")).unwrap(); fs::write(dir.path().join(".yoi/profiles.toml"), "").unwrap(); let outside = dir.path().join("outside.dcdl"); fs::write(&outside, valid_profile_source("escape")).unwrap(); std::os::unix::fs::symlink(&outside, dir.path().join(".yoi/profiles/escape.dcdl")).unwrap(); let response = profile_settings_request( dir.path(), "PUT", "/settings/profiles/registry", json!({ "registry_revision": test_file_revision(&dir.path().join(".yoi/profiles.toml")), "default_profile": null, "profiles": [{ "name": "escape", "profile_source_id": "project:escape" }] }), ) .await; assert_eq!(response.0, StatusCode::BAD_REQUEST); assert_diagnostic(&response.1, "profile_source_symlink_escape"); let rendered = response.1.to_string(); assert!(!rendered.contains(dir.path().to_string_lossy().as_ref())); } #[tokio::test] async fn worker_launch_rejects_invalid_project_profile_candidate() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join(".yoi/profiles")).unwrap(); fs::write( dir.path().join(".yoi/profiles.toml"), "[profile.bad]\npath = \"profiles/bad.dcdl\"\n", ) .unwrap(); fs::write(dir.path().join(".yoi/profiles/bad.dcdl"), "not decodal").unwrap(); assert!( worker_profile_candidates_for_root(dir.path()) .iter() .all(|candidate| candidate.id != "project:bad") ); assert!(profile_selector_for_candidate_with_root(dir.path(), "project:bad").is_none()); } fn valid_profile_source(slug: &str) -> String { format!( r#"{{ slug = "{slug}"; description = "Test"; scope = "workspace_read"; }}"# ) } fn test_file_revision(path: &Path) -> String { let Ok(metadata) = fs::metadata(path) else { return "missing".to_string(); }; let modified = metadata .modified() .ok() .and_then(|time| time.duration_since(std::time::UNIX_EPOCH).ok()) .map(|duration| duration.as_nanos()) .unwrap_or_default(); format!("rev:{modified}:{}", metadata.len()) } async fn profile_settings_request( workspace_root: &Path, method: &str, path: &str, body: Value, ) -> (StatusCode, Value) { let app = test_app(workspace_root.to_path_buf()).await; let request = Request::builder() .method(method) .uri(format!("/api/w/{TEST_WORKSPACE_ID}{path}")) .header(CONTENT_TYPE, "application/json") .body(Body::from(body.to_string())) .unwrap(); let response = app.oneshot(request).await.unwrap(); let status = response.status(); let bytes = to_bytes(response.into_body(), 1024 * 1024).await.unwrap(); let json = serde_json::from_slice::(&bytes).unwrap(); (status, json) } fn diagnostic_codes(response: &Value) -> Vec { response["diagnostics"] .as_array() .expect("diagnostics array") .iter() .map(|diagnostic| diagnostic["code"].as_str().unwrap().to_string()) .collect() } fn assert_diagnostic(response: &Value, code: &str) { let codes = diagnostic_codes(response); assert!( !codes.is_empty(), "diagnostics must not be empty: {response}" ); assert!( codes.iter().any(|actual| actual == code), "missing {code}: {codes:?}" ); } struct DeterministicExecutionBackend { contexts: std::sync::Mutex< std::collections::HashMap< worker_runtime::identity::WorkerRef, worker_runtime::execution::WorkerExecutionContext, >, >, materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer, } impl Default for DeterministicExecutionBackend { fn default() -> Self { let unique = format!( "yoi-deterministic-wd-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() ); Self { contexts: std::sync::Mutex::new(std::collections::HashMap::new()), materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer::new( std::env::temp_dir().join(unique), ), } } } impl worker_runtime::execution::WorkerExecutionBackend for DeterministicExecutionBackend { fn backend_id(&self) -> &str { "deterministic-workspace-server-test" } fn create_working_directory( &self, request: &worker_runtime::catalog::WorkingDirectoryRequest, ) -> std::result::Result< worker_runtime::catalog::WorkingDirectoryStatus, worker_runtime::working_directory::WorkingDirectoryDiagnostic, > { Ok(self.materializer.create(request)?.status()) } fn list_working_directories(&self) -> Vec { self.materializer .list_working_directories() .unwrap_or_default() } fn working_directory( &self, working_directory_id: &str, ) -> std::result::Result< worker_runtime::catalog::WorkingDirectoryStatus, worker_runtime::working_directory::WorkingDirectoryDiagnostic, > { self.materializer .working_directory_status(working_directory_id) } fn cleanup_working_directory( &self, working_directory_id: &str, ) -> std::result::Result< worker_runtime::catalog::WorkingDirectoryStatus, worker_runtime::working_directory::WorkingDirectoryDiagnostic, > { self.materializer .cleanup_working_directory(working_directory_id) } fn dry_restore_worker( &self, _request: worker_runtime::execution::WorkerExecutionRestoreDryRequest, ) -> worker_runtime::execution::WorkerRestoreDryCheck { worker_runtime::execution::WorkerRestoreDryCheck::valid( "deterministic test backend dry restore ok", ) } fn spawn_worker( &self, request: worker_runtime::execution::WorkerExecutionSpawnRequest, ) -> worker_runtime::execution::WorkerExecutionSpawnResult { let working_directory = match request.request.working_directory.as_ref() { Some(claim) => match self.materializer.bind_working_directory( &claim.working_directory_id, claim.relative_cwd.as_deref(), ) { Ok(binding) => Some(binding.status()), Err(diagnostic) => { return worker_runtime::execution::WorkerExecutionSpawnResult::Rejected( worker_runtime::execution::WorkerExecutionResult::rejected( worker_runtime::execution::WorkerExecutionOperation::Spawn, diagnostic.to_string(), ), ); } }, None => None, }; self.contexts .lock() .unwrap() .insert(request.worker_ref.clone(), request.context); worker_runtime::execution::WorkerExecutionSpawnResult::Connected { handle: worker_runtime::execution::WorkerExecutionHandle::new( request.worker_ref, self.backend_id(), ), run_state: worker_runtime::execution::WorkerExecutionRunState::Idle, working_directory, } } fn dispatch_input( &self, handle: &worker_runtime::execution::WorkerExecutionHandle, input: worker_runtime::interaction::WorkerInput, ) -> worker_runtime::execution::WorkerExecutionResult { let context = self .contexts .lock() .unwrap() .get(handle.worker_ref()) .cloned() .expect("execution context"); let content = input.content.clone(); std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(25)); let _ = context.publish_protocol_event(protocol::Event::TextDone { text: format!("server companion echoed: {content}"), }); }); worker_runtime::execution::WorkerExecutionResult::accepted( worker_runtime::execution::WorkerExecutionOperation::Input, worker_runtime::execution::WorkerExecutionRunState::Idle, ) } } fn test_identity() -> WorkspaceIdentity { WorkspaceIdentity { workspace_id: TEST_WORKSPACE_ID.to_string(), display_name: "Test Workspace".to_string(), created_at: TEST_CREATED_AT.to_string(), } } fn test_server_config(workspace_root: impl Into) -> ServerConfig { let workspace_root = workspace_root.into(); let store_root = workspace_root.join(".test-embedded-runtime-store"); let mut config = ServerConfig::local_dev(workspace_root.clone(), test_identity()) .with_embedded_runtime_store_root(store_root); config.database_path = workspace_root.join(".test-yoi-server.db"); config.runtime_config_path = Some(workspace_root.join(".test-config/runtimes.toml")); config.repositories = vec![ConfiguredRepository { id: TEST_REPOSITORY_ID.to_string(), provider: "git".to_string(), uri: ".".to_string(), path: workspace_root, display_name: Some("Test Repository".to_string()), default_selector: Some("HEAD".to_string()), }]; config } fn memory_staging_record_json(id: &str, claim: &str) -> String { json!({ "schema_version": 1, "id": id, "extract_run_id": "extract-run-1", "source": { "segment_id": "segment-1", "range": [0, 10], }, "kind": "working_assumption", "claim": claim, "why_useful": "useful for future work", "staleness": null, "evidence": [], "source_refs": [], }) .to_string() } #[tokio::test] async fn memory_consolidation_backlog_ignores_legacy_filesystem_staging() { let dir = tempfile::tempdir().unwrap(); let config = test_server_config(dir.path()); let store = SqliteWorkspaceStore::open(&config.database_path).unwrap(); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let legacy_staging_dir = dir.path().join(".yoi/memory/_staging"); fs::create_dir_all(&legacy_staging_dir).unwrap(); fs::write( legacy_staging_dir.join("00000000001J4.json"), memory_staging_record_json("00000000001J4", "legacy filesystem candidate"), ) .unwrap(); let output = match start_memory_staging_consolidation( api, MemoryConsolidateStagingOperation { force: true, threshold_files: None, threshold_bytes: None, }, ) { Ok(output) => output, Err(_) => panic!("unexpected ApiError from memory consolidation trigger"), }; assert_eq!(output.status, "skipped_empty"); assert_eq!(output.candidate_count, 0); assert_eq!(output.total_bytes, 0); } #[tokio::test] async fn memory_consolidation_reuses_existing_idle_worker() { let dir = tempfile::tempdir().unwrap(); let config = test_server_config(dir.path()); let store = SqliteWorkspaceStore::open(&config.database_path).unwrap(); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); api.authority .upsert_memory_staging_record( "00000000000000000000000001", &memory_staging_record_json("00000000000000000000000001", "first candidate"), None, ) .unwrap(); let resolved_config_bundle = crate::profile_settings::build_workspace_profile_config_bundle( &api.config.workspace_root, &api.config.workspace_id, &api.config.workspace_created_at, MEMORY_CONSOLIDATION_PROFILE, ) .unwrap(); let existing = api .runtime .spawn_worker( EMBEDDED_WORKER_RUNTIME_ID, WorkerSpawnRequest { requested_worker_name: Some(MEMORY_CONSOLIDATION_PROFILE.to_string()), intent: WorkerSpawnIntent::WorkspaceOrchestrator, acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments: 0, }, profile: Some(ProfileSelector::Builtin( MEMORY_CONSOLIDATION_PROFILE.to_string(), )), initial_input: None, working_directory_request: None, resolved_working_directory_request: None, resolved_working_directory: None, resolved_config_bundle, }, ) .unwrap(); assert_eq!(existing.state, WorkerOperationState::Accepted); let worker_id = existing.worker.unwrap().worker_id; let worker = api .runtime .worker(EMBEDDED_WORKER_RUNTIME_ID, &worker_id) .unwrap(); assert_eq!(worker.state, "idle"); let second = match start_memory_staging_consolidation( api.clone(), MemoryConsolidateStagingOperation { force: true, threshold_files: None, threshold_bytes: None, }, ) { Ok(output) => output, Err(_) => panic!("unexpected ApiError from second memory consolidation trigger"), }; assert_eq!(second.status, "reused"); assert!(second.summary.contains(&worker_id)); let workers_after_second = api .runtime .list_workers_for_runtime(EMBEDDED_WORKER_RUNTIME_ID, 20) .unwrap(); let consolidaters_after_second = workers_after_second .items .iter() .filter(|worker| is_memory_consolidation_worker(worker)) .collect::>(); assert_eq!(consolidaters_after_second.len(), 1); assert_eq!(consolidaters_after_second[0].worker_id, worker_id); } fn init_clean_git_workspace(path: &std::path::Path) { for args in [ vec!["init"], vec!["config", "user.email", "test@example.invalid"], vec!["config", "user.name", "Yoi Test"], ] { let status = std::process::Command::new("git") .arg("-C") .arg(path) .args(args) .status() .unwrap(); assert!(status.success()); } std::fs::write(path.join("README.md"), "clean\n").unwrap(); std::fs::write( path.join(".gitignore"), ".yoi/\n.test-embedded-runtime-store/\n", ) .unwrap(); for args in [ vec!["add", "README.md", ".gitignore"], vec!["commit", "-m", "init"], ] { let status = std::process::Command::new("git") .arg("-C") .arg(path) .args(args) .status() .unwrap(); assert!(status.success()); } } #[tokio::test] async fn ticket_backend_endpoint_uses_workspace_sqlite_backend() { let dir = tempfile::tempdir().unwrap(); fs::create_dir_all(dir.path().join(".yoi")).unwrap(); fs::write( dir.path().join(".yoi/workspace.toml"), format!( "workspace_id = \"{TEST_WORKSPACE_ID}\"\ncreated_at = \"{TEST_CREATED_AT}\"\ndisplay_name = \"Endpoint Test\"\n\n[ticket]\nlanguage = \"Japanese\"\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \"server-tickets\"\n" ), ) .unwrap(); let api = test_api(dir.path()).await; let Json(response) = scoped_ticket_backend_operation( State(api.clone()), AxumPath(ScopedWorkspacePath { workspace_id: TEST_WORKSPACE_ID.to_string(), }), Json(TicketBackendOperation::Create { input: ticket::NewTicket::new("Endpoint configured root"), }), ) .await .unwrap_or_else(|error| panic!("ticket backend operation failed: {}", error.error)); let ticket_ref = match response { TicketBackendHttpResponse::Ok { result: ticket::TicketBackendOperationResult::TicketRef(ticket_ref), } => ticket_ref, other => panic!("unexpected ticket backend response: {other:?}"), }; assert!(api.config.database_path.is_file()); assert!( !dir.path() .join("server-tickets") .join(&ticket_ref.id) .join("item.md") .exists() ); assert!( !dir.path() .join(".yoi/tickets") .join(&ticket_ref.id) .join("item.md") .exists() ); } #[tokio::test] async fn skills_endpoints_use_workspace_backend_catalog_and_progressive_detail() { let dir = tempfile::tempdir().unwrap(); let skill_dir = dir.path().join(".yoi/skills/triage-errors"); fs::create_dir_all(&skill_dir).unwrap(); fs::write( skill_dir.join("SKILL.md"), "---\nname: triage-errors\ndescription: Use when triaging backend errors and choosing safe diagnostics.\n---\n\n# Triage Errors\n\nInspect logs before changing code.", ) .unwrap(); let api = test_api(dir.path()).await; let Json(catalog) = scoped_list_skills( State(api.clone()), AxumPath(ScopedWorkspacePath { workspace_id: TEST_WORKSPACE_ID.to_string(), }), ) .await .unwrap_or_else(|error| panic!("skill catalog failed: {}", error.error)); let entry = catalog .entries .iter() .find(|entry| entry.name == "triage-errors") .expect("workspace Skill catalog entry"); assert_eq!(entry.provenance.id, "workspace:triage-errors"); assert!( !serde_json::to_string(&catalog) .unwrap() .contains("# Triage Errors") ); let Json(detail) = scoped_get_skill( State(api), AxumPath(ScopedSkillPath { workspace_id: TEST_WORKSPACE_ID.to_string(), name: "triage-errors".to_string(), }), ) .await .unwrap_or_else(|error| panic!("skill detail failed: {}", error.error)); assert!(detail.body.contains("# Triage Errors")); assert_eq!(detail.provenance.id, "workspace:triage-errors"); } async fn test_api(workspace_root: impl Into) -> WorkspaceApi { let store = SqliteWorkspaceStore::in_memory().unwrap(); WorkspaceApi::new_with_execution_backend( test_server_config(workspace_root), Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap() } fn seed_cleanup_worker( api: &WorkspaceApi, runtime_worker_id: u64, retention_state: &str, ) -> String { let now = now_registry_timestamp(); api.store .upsert_worker_registry(&WorkerRegistryRecord { workspace_id: api.config.workspace_id.clone(), runtime_id: "runtime-test".to_string(), runtime_worker_id, display_name: runtime_worker_id.to_string(), profile: None, retention_state: retention_state.to_string(), transcript_ref: None, session_ref: None, summary_ref: None, diagnostics_ref: None, created_at: now.clone(), updated_at: now, }) .unwrap(); runtime_worker_id.to_string() } fn seed_cleanup_workdir(api: &WorkspaceApi, workdir_id: &str, status: &str, cleanliness: &str) { let now = now_registry_timestamp(); api.store .upsert_workdir_registry(&WorkdirRegistryRecord { workspace_id: api.config.workspace_id.clone(), workdir_id: workdir_id.to_string(), runtime_id: "runtime-test".to_string(), repository_id: "repo-test".to_string(), selector: Some("HEAD".to_string()), resolved_commit: None, materialization_status: status.to_string(), cleanliness: cleanliness.to_string(), created_at: now.clone(), updated_at: now, }) .unwrap(); } fn seed_cleanup_link(api: &WorkspaceApi, runtime_worker_id: &str, workdir_id: &str) { let runtime_worker_id = runtime_worker_id.parse::().unwrap(); api.store .upsert_worker_workdir_link(&WorkerWorkdirLinkRecord { workspace_id: api.config.workspace_id.clone(), runtime_id: "runtime-test".to_string(), runtime_worker_id, workdir_id: workdir_id.to_string(), role: "primary".to_string(), linked_at: now_registry_timestamp(), unlinked_at: None, }) .unwrap(); } #[tokio::test] async fn synthetic_verified_clean_workdir_can_still_use_clean_cleanup_path() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let api = test_api(workspace.path()).await; seed_cleanup_workdir(&api, "verified-clean", "present", "clean"); let plan = build_runtime_cleanup_plan(&api, "runtime-test") .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); let candidate = plan .workdirs .iter() .find(|candidate| candidate.workdir_id == "verified-clean") .expect("cleanup candidate"); assert_eq!(candidate.cleanliness, CleanupWorkdirCleanliness::Clean); assert_eq!(candidate.action, CleanupTargetKind::WorkdirCleanCleanup); } #[tokio::test] async fn cleanup_plan_reports_pinned_running_dirty_not_found_and_redacts_paths() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let api = test_api(workspace.path()).await; let pinned = seed_cleanup_worker(&api, 1, "pinned"); let unobserved = seed_cleanup_worker(&api, 2, "normal"); seed_cleanup_workdir(&api, "workdir-dirty", "present", "dirty"); seed_cleanup_workdir(&api, "workdir-not-found", "not_found", "clean"); seed_cleanup_link(&api, pinned.as_str(), "workdir-dirty"); seed_cleanup_link(&api, unobserved.as_str(), "workdir-not-found"); let plan = build_runtime_cleanup_plan(&api, "runtime-test") .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); let pinned_worker = plan .workers .iter() .find(|candidate| candidate.worker_id == pinned) .unwrap(); assert!(pinned_worker.pinned); assert_eq!( pinned_worker.blocking_reason.as_deref(), Some("worker is pinned") ); let running_linked_workdir = plan .workdirs .iter() .find(|candidate| candidate.workdir_id == "workdir-not-found") .unwrap(); assert_eq!( running_linked_workdir.file_status, CleanupWorkdirFileStatus::NotFound ); assert_eq!( running_linked_workdir.action, CleanupTargetKind::WorkdirRecordDelete ); assert!(!running_linked_workdir.running_linked); assert_eq!(running_linked_workdir.blocking_reason.as_deref(), None); let dirty_workdir = plan .workdirs .iter() .find(|candidate| candidate.workdir_id == "workdir-dirty") .unwrap(); assert_eq!(dirty_workdir.action, CleanupTargetKind::WorkdirDirtyDiscard); assert!(dirty_workdir.pinned_linked); let serialized = serde_json::to_string(&plan).unwrap(); assert!(!serialized.contains("/tmp/secret-runtime-path")); } #[tokio::test] async fn cleanup_execution_rejects_stale_plan_and_pinned_worker_delete() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let api = test_api(workspace.path()).await; let worker = seed_cleanup_worker(&api, 1, "pinned"); let plan = build_runtime_cleanup_plan(&api, "runtime-test") .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); let target = plan .workers .iter() .find(|candidate| candidate.worker_id == worker) .unwrap() .target_id .clone(); let stale = ExecuteRuntimeCleanupRequest { expected_plan_revision: "stale".to_string(), expected_plan_digest: plan.digest.clone(), worker_target_ids: vec![target.clone()], workdir_target_ids: Vec::new(), confirm_dirty_discard_target_ids: Vec::new(), }; assert!(execute_runtime_cleanup(&api, "runtime-test", stale).is_err()); let pinned = ExecuteRuntimeCleanupRequest { expected_plan_revision: plan.revision, expected_plan_digest: plan.digest, worker_target_ids: vec![target], workdir_target_ids: Vec::new(), confirm_dirty_discard_target_ids: Vec::new(), }; assert!(execute_runtime_cleanup(&api, "runtime-test", pinned).is_err()); } #[tokio::test] async fn cleanup_execution_requires_dirty_confirmation_and_deletes_removed_record() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let api = test_api(workspace.path()).await; seed_cleanup_workdir(&api, "workdir-dirty", "present", "dirty"); seed_cleanup_workdir(&api, "workdir-not-found", "not_found", "clean"); let plan = build_runtime_cleanup_plan(&api, "runtime-test") .unwrap_or_else(|err| panic!("cleanup plan: {}", err.error)); let dirty_target = plan .workdirs .iter() .find(|candidate| candidate.workdir_id == "workdir-dirty") .unwrap() .target_id .clone(); let removed_target = plan .workdirs .iter() .find(|candidate| candidate.workdir_id == "workdir-not-found") .unwrap() .target_id .clone(); let missing_confirmation = ExecuteRuntimeCleanupRequest { expected_plan_revision: plan.revision.clone(), expected_plan_digest: plan.digest.clone(), worker_target_ids: Vec::new(), workdir_target_ids: vec![dirty_target], confirm_dirty_discard_target_ids: Vec::new(), }; assert!(execute_runtime_cleanup(&api, "runtime-test", missing_confirmation).is_err()); let delete_removed = ExecuteRuntimeCleanupRequest { expected_plan_revision: plan.revision, expected_plan_digest: plan.digest, worker_target_ids: Vec::new(), workdir_target_ids: vec![removed_target], confirm_dirty_discard_target_ids: Vec::new(), }; let response = execute_runtime_cleanup(&api, "runtime-test", delete_removed) .unwrap_or_else(|err| panic!("cleanup execution: {}", err.error)); assert_eq!(response.results[0].status, "deleted"); assert!( api.store .get_workdir_registry(&api.config.workspace_id, "workdir-not-found") .unwrap() .is_none() ); } async fn test_app(workspace_root: impl Into) -> Router { build_router(test_api(workspace_root).await) } fn test_profile_archive() -> worker_runtime::profile_archive::ProfileSourceArchive { use worker_runtime::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveInput}; ProfileSourceArchive::build(ProfileSourceArchiveInput { id: "profile-source-archive:server-test".to_string(), entrypoints: std::collections::BTreeMap::from([( "default".to_string(), "profiles/default.dcdl".to_string(), )]), imports: std::collections::BTreeMap::new(), sources: std::collections::BTreeMap::from([( "profiles/default.dcdl".to_string(), r#"{ slug = "default"; description = "Default"; scope = "workspace_read"; }"# .to_string(), )]), }) .unwrap() } fn missing_resource_handle() -> worker_runtime::resource::BackendResourceHandle { worker_runtime::resource::BackendResourceHandle { kind: worker_runtime::resource::BackendResourceKind::ProfileSourceArchive, workspace_id: "workspace-test".to_string(), scope_id: Some("workspace-profile-source".to_string()), runtime_id: Some("runtime-test".to_string()), worker_id: None, resource_id: "profile-source-archive:missing".to_string(), digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000" .to_string(), operation: worker_runtime::resource::BackendResourceOperation::FetchArchive, expires_at_unix_seconds: 4_102_444_800, nonce: "missing-nonce".to_string(), revision: "missing-revision".to_string(), generation: None, max_bytes: worker_runtime::resource::DEFAULT_PROFILE_SOURCE_ARCHIVE_MAX_BYTES, content_type: worker_runtime::resource::PROFILE_SOURCE_ARCHIVE_CONTENT_TYPE.to_string(), redaction: worker_runtime::resource::ResourceRedactionPolicy::RuntimeInternalOnly, audit_correlation_id: "audit-missing".to_string(), profile_source_graph: Some( worker_runtime::profile_archive::ProfileSourceGraphSummary { entrypoints: std::collections::BTreeMap::from([( "default".to_string(), "profiles/default.dcdl".to_string(), )]), source_count: 1, import_count: 0, total_source_bytes: 0, }, ), } } #[tokio::test] async fn internal_resource_fetch_rest_returns_typed_missing_resource() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let app = test_app(workspace.path()).await; let handle = missing_resource_handle(); let response = app .oneshot( Request::post("/internal/runtime/resources/fetch") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec( &worker_runtime::resource::BackendResourceFetchRequest { audit_correlation_id: handle.audit_correlation_id.clone(), runtime_id: "runtime-test".to_string(), worker_id: None, handle, }, ) .unwrap(), )) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let error: worker_runtime::resource::BackendResourceError = serde_json::from_slice(&bytes).unwrap(); assert!(matches!( error, worker_runtime::resource::BackendResourceError::MissingResource )); } #[tokio::test] async fn remote_http_resource_fetch_uses_backend_resource_contract() { let workspace = tempfile::tempdir().unwrap(); init_clean_git_workspace(workspace.path()); let api = test_api(workspace.path()).await; let broker = api.resource_broker.clone(); let archive = test_profile_archive(); let runtime_id = "runtime-test"; let handle = broker.issue_profile_source_archive_handle( "workspace-test", Some(runtime_id), None, archive, ); let app = build_router(api); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() }); let client = worker_runtime::resource::HttpBackendResourceClient::new( format!("http://{addr}/internal/runtime/resources/fetch"), None, ); let response = client .fetch_resource(worker_runtime::resource::BackendResourceFetchRequest { audit_correlation_id: handle.audit_correlation_id.clone(), runtime_id: runtime_id.to_string(), worker_id: None, handle: handle.clone(), }) .await .expect("remote HTTP resource fetch succeeds"); assert_eq!(response.digest, handle.digest); let mut tampered = handle; tampered.scope_id = Some("tampered".to_string()); let error = client .fetch_resource(worker_runtime::resource::BackendResourceFetchRequest { audit_correlation_id: tampered.audit_correlation_id.clone(), runtime_id: runtime_id.to_string(), worker_id: None, handle: tampered, }) .await .unwrap_err(); assert!(matches!( error, worker_runtime::resource::BackendResourceError::Unauthorized { .. } )); server.abort(); } fn runtime_test_bundle() -> worker_runtime::config_bundle::ConfigBundle { worker_runtime::config_bundle::ConfigBundle { metadata: worker_runtime::config_bundle::ConfigBundleMetadata { id: "server-test-bundle".to_string(), digest: String::new(), revision: "test".to_string(), workspace_id: "test".to_string(), created_at: "test".to_string(), provenance: worker_runtime::config_bundle::ConfigBundleProvenance { source: "test".to_string(), detail: None, }, }, profiles: vec![worker_runtime::config_bundle::ConfigProfileDescriptor { selector: worker_runtime::catalog::ProfileSelector::RuntimeDefault, label: Some("server-test".to_string()), }], declarations: Vec::new(), profile_source_archive: None, profile_source_archive_handle: None, } .with_computed_digest() } fn runtime_create_request() -> worker_runtime::catalog::CreateWorkerRequest { let bundle = runtime_test_bundle(); worker_runtime::catalog::CreateWorkerRequest { profile: worker_runtime::catalog::ProfileSelector::RuntimeDefault, profile_source: worker_runtime::catalog::ProfileSourceArchiveSource::Http { location: worker_runtime::catalog::ProfileSourceArchiveHttpRef { url: "http://127.0.0.1/profile-source.tar".to_string(), etag: None, archive: worker_runtime::profile_archive::ProfileSourceArchiveRef { id: "test-profile-source".to_string(), digest: "test-digest".to_string(), size_bytes: 0, source_graph: worker_runtime::profile_archive::ProfileSourceGraphSummary { source_count: 0, total_source_bytes: 0, entrypoints: std::collections::BTreeMap::new(), import_count: 0, }, }, }, }, config_bundle: Some(worker_runtime::catalog::ConfigBundleRef { id: bundle.metadata.id, digest: bundle.metadata.digest, }), initial_input: None, working_directory_request: None, working_directory: None, workspace_api: None, } } fn runtime_with_worker() -> (worker_runtime::Runtime, worker_runtime::identity::WorkerRef) { let runtime = worker_runtime::Runtime::with_execution_backend( worker_runtime::RuntimeOptions::default(), Arc::new(DeterministicExecutionBackend::default()), ) .unwrap(); runtime.store_config_bundle(runtime_test_bundle()).unwrap(); let worker = runtime.create_worker(runtime_create_request()).unwrap(); (runtime, worker.worker_ref) } #[tokio::test] async fn browser_working_directory_create_is_rejected_as_backend_lifecycle() { let dir = tempfile::tempdir().unwrap(); init_clean_git_workspace(dir.path()); let app = test_app(dir.path()).await; let workspace_path = format!("/api/w/{TEST_WORKSPACE_ID}/working-directories"); let response = request_json( app, "POST", &workspace_path, Some(serde_json::json!({ "repository_id": TEST_REPOSITORY_ID, "selector": "HEAD", })), StatusCode::BAD_REQUEST, ) .await; assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| diagnostic["code"] == "backend_workdir_create_unsupported"), "expected backend lifecycle diagnostic, got {response}" ); let projected = serde_json::to_string(&response).unwrap(); assert!(!projected.contains(dir.path().to_string_lossy().as_ref())); } #[tokio::test] async fn scoped_worker_create_invalid_relative_cwd_returns_typed_diagnostic() { let dir = tempfile::tempdir().unwrap(); init_clean_git_workspace(dir.path()); let app = test_app(dir.path()).await; let working_directory_id = "test-workdir"; let response = request_json( app, "POST", &format!("/api/w/{TEST_WORKSPACE_ID}/workers"), Some(serde_json::json!({ "runtime_id": "remote-runtime", "display_name": "Coding Worker", "profile": "builtin:coder", "initial_text": "", "working_directory": { "working_directory_id": working_directory_id, "relative_cwd": "../escape" } })), StatusCode::BAD_REQUEST, ) .await; assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| diagnostic["code"] == "working_directory_relative_cwd_invalid"), "expected typed relative_cwd diagnostic, got {response}" ); assert!( response["message"] .as_str() .unwrap_or_default() .contains("working_directory_relative_cwd_invalid") ); let projected = serde_json::to_string(&response).unwrap(); assert!(!projected.contains(dir.path().to_string_lossy().as_ref())); } #[tokio::test] async fn runtime_connection_settings_add_delete_apply_live_registry() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let settings = get_json(app.clone(), "/api/settings/runtime-connections").await; assert_eq!(settings["embedded"]["built_in"], true); assert_eq!(settings["embedded"]["config_managed"], false); let added = post_json( app.clone(), "/api/settings/runtime-connections/remotes", serde_json::json!({ "runtime_id": "team-runtime", "display_name": "Team Runtime", "endpoint": "https://runtime.example.invalid" }), ) .await; assert_eq!(added["restart_required"], false); assert_eq!(added["remotes"][0]["runtime_id"], "team-runtime"); assert_eq!(added["remotes"][0]["endpoint_configured"], true); assert!( added["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| diagnostic["code"] == "runtime_registry_applied") ); let projected = serde_json::to_string(&added).unwrap(); assert!(!projected.contains("runtime.example.invalid")); let persisted = BackendRuntimesConfigFile::load_from_path( dir.path().join(".test-config/runtimes.toml"), ) .unwrap(); assert_eq!(persisted.runtimes.remote.len(), 1); assert_eq!(persisted.runtimes.remote[0].id, "team-runtime"); assert_eq!( persisted.runtimes.remote[0].endpoint, "https://runtime.example.invalid" ); let launch_options = get_json(app.clone(), "/api/workers/launch-options").await; let runtimes = launch_options["runtimes"].as_array().unwrap(); let embedded_runtime = runtimes .iter() .find(|runtime| runtime["runtime_id"] == EMBEDDED_WORKER_RUNTIME_ID) .expect("embedded runtime launch option"); assert_eq!(embedded_runtime["working_directory_required"], false); let team_runtime = runtimes .iter() .find(|runtime| runtime["runtime_id"] == "team-runtime") .expect("team runtime launch option"); assert_eq!(team_runtime["working_directory_required"], true); let deleted = request_json( app.clone(), "DELETE", "/api/settings/runtime-connections/remotes/team-runtime", None, StatusCode::OK, ) .await; assert_eq!(deleted["restart_required"], false); assert_eq!(deleted["remotes"].as_array().unwrap().len(), 0); let launch_options = get_json(app.clone(), "/api/workers/launch-options").await; assert!( !launch_options["runtimes"] .as_array() .unwrap() .iter() .any(|runtime| runtime["runtime_id"] == "team-runtime") ); let persisted = BackendRuntimesConfigFile::load_from_path( dir.path().join(".test-config/runtimes.toml"), ) .unwrap(); assert!(persisted.runtimes.remote.is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn runtime_connection_delete_rejects_active_remote_workers() { let (runtime, _worker_ref) = runtime_with_worker(); let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let runtime_addr = runtime_listener.local_addr().unwrap(); tokio::spawn({ let runtime = runtime.clone(); async move { worker_runtime::http_server::serve_runtime_http(runtime, runtime_listener, None) .await .unwrap() } }); let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let added = post_json( app.clone(), "/api/settings/runtime-connections/remotes", serde_json::json!({ "runtime_id": "busy-runtime", "display_name": "Busy Runtime", "endpoint": format!("http://{runtime_addr}") }), ) .await; assert_eq!(added["restart_required"], false); let workers = get_json(app.clone(), "/api/workers").await; assert!( workers["items"] .as_array() .unwrap() .iter() .any(|worker| worker["runtime_id"] == "busy-runtime"), "expected remote runtime to report at least one worker, got {workers}" ); let response = request_json( app, "DELETE", "/api/settings/runtime-connections/remotes/busy-runtime", None, StatusCode::CONFLICT, ) .await; assert!( response["message"] .as_str() .unwrap() .contains("remote_runtime_delete_blocked") ); assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| { diagnostic["code"] == "remote_runtime_delete_blocked" }) ); let persisted = BackendRuntimesConfigFile::load_from_path( dir.path().join(".test-config/runtimes.toml"), ) .unwrap(); assert_eq!(persisted.runtimes.remote.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn runtime_connection_test_reports_compatible_with_unknown_warnings_without_endpoint_leak() { let (runtime, _worker_ref) = runtime_with_worker(); let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let runtime_addr = runtime_listener.local_addr().unwrap(); tokio::spawn({ let runtime = runtime.clone(); async move { worker_runtime::http_server::serve_runtime_http(runtime, runtime_listener, None) .await .unwrap() } }); let dir = tempfile::tempdir().unwrap(); let endpoint = format!("http://{runtime_addr}"); BackendRuntimesConfigFile { runtimes: WorkspaceBackendRuntimesConfig { remote: vec![RemoteRuntimeConfigFile { id: "probe-runtime".to_string(), endpoint: endpoint.clone(), display_name: Some("Probe Runtime".to_string()), token_ref: None, }], }, } .write_to_path(dir.path().join(".test-config/runtimes.toml")) .unwrap(); let app = test_app(dir.path()).await; let response = post_json( app, "/api/settings/runtime-connections/remotes/probe-runtime/test", serde_json::json!({}), ) .await; assert_eq!(response["state"], "compatible"); let capabilities = response["capabilities"].as_array().unwrap(); assert!( capabilities .iter() .any(|value| value == "runtime.summary:available") ); assert!( capabilities .iter() .any(|value| value == "workers.list:available") ); assert!( capabilities .iter() .any(|value| value == "workers.spawn:available") ); assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| { diagnostic["code"] == "workers.spawn.available" }) ); let projected = serde_json::to_string(&response).unwrap(); assert!(!projected.contains(&endpoint)); assert!(!projected.contains(&runtime_addr.to_string())); assert_eq!(response["protocol_version"], serde_json::Value::Null); } #[tokio::test(flavor = "multi_thread")] async fn runtime_connection_test_marks_missing_execution_backend_incompatible() { let runtime = worker_runtime::Runtime::with_options(worker_runtime::RuntimeOptions::default()); let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let runtime_addr = runtime_listener.local_addr().unwrap(); tokio::spawn(async move { worker_runtime::http_server::serve_runtime_http(runtime, runtime_listener, None) .await .unwrap() }); let dir = tempfile::tempdir().unwrap(); let endpoint = format!("http://{runtime_addr}"); BackendRuntimesConfigFile { runtimes: WorkspaceBackendRuntimesConfig { remote: vec![RemoteRuntimeConfigFile { id: "control-only-runtime".to_string(), display_name: Some("Control-only Runtime".to_string()), endpoint, token_ref: None, }], }, } .write_to_path(dir.path().join(".test-config/runtimes.toml")) .unwrap(); let app = test_app(dir.path()).await; let response = post_json( app, "/api/settings/runtime-connections/remotes/control-only-runtime/test", serde_json::json!({}), ) .await; assert_eq!(response["state"], "incompatible"); assert!( response["capabilities"] .as_array() .unwrap() .iter() .any(|value| { value == "workers.spawn:incompatible" }) ); assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| { diagnostic["code"] == "remote_runtime_worker_creation_unavailable" }) ); } #[tokio::test] async fn browser_worker_create_succeeds_and_preserves_unsupported_diagnostics() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let created = post_json( app.clone(), "/api/workers", serde_json::json!({ "runtime_id": "embedded-worker-runtime", "display_name": "", "profile": "runtime_default", "initial_text": "" }), ) .await; assert_eq!(created["runtime_id"], "embedded-worker-runtime"); let workers = get_json(app.clone(), "/api/workers").await; let worker = workers["items"] .as_array() .unwrap() .iter() .find(|worker| worker["worker_id"] == created["worker_id"]) .expect("created Worker should be listed"); assert_eq!(worker["label"], "Coding Worker"); assert_eq!(worker["worker_id"], created["worker_id"]); let detail_path = format!( "/api/runtimes/{}/workers/{}", created["runtime_id"].as_str().unwrap(), created["worker_id"].as_str().unwrap() ); let detail = get_json(app.clone(), detail_path.as_str()).await; assert_eq!(detail["label"], "Coding Worker"); assert_eq!(detail["worker_id"], created["worker_id"]); assert!( created["console_href"] .as_str() .unwrap() .contains("/console") ); let response = worker_create_not_accepted_error( "unsupported-runtime".to_string(), vec![settings_diagnostic( "remote_runtime_unsupported", DiagnosticSeverity::Warning, "Remote Runtime provisioning is unsupported by this v0 worker launch path.", )], ) .into_response(); assert_eq!(response.status(), StatusCode::BAD_GATEWAY); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let response: Value = serde_json::from_slice(&bytes).unwrap(); let diagnostics = response["diagnostics"].as_array().unwrap(); assert!(diagnostics.len() >= 2); assert!( diagnostics .iter() .any(|diagnostic| { diagnostic["code"] == "workspace_worker_create_not_accepted" }) ); let projected = serde_json::to_string(&response).unwrap(); assert!(!projected.contains("http://")); } #[tokio::test] async fn browser_worker_create_rejects_non_embedded_no_workdir() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let response = request_json( app, "POST", "/api/workers", Some(serde_json::json!({ "runtime_id": "remote-runtime", "display_name": "Remote Worker", "profile": "runtime_default", "initial_text": "" })), StatusCode::BAD_REQUEST, ) .await; assert!( response["message"] .as_str() .unwrap_or_default() .contains("workspace_worker_workdir_required") ); assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| { diagnostic["code"] == "workspace_worker_workdir_required" }) ); } #[tokio::test] async fn runtime_worker_spawn_rejects_raw_working_directory_fields() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let response = request_json( app, "POST", "/api/runtimes/embedded-worker-runtime/workers", Some(serde_json::json!({ "intent": { "kind": "ticket_role", "ticket_id": "00001KVZSGT0Q", "role": "coder" }, "acceptance": { "kind": "run_accepted", "expected_segments": 0 }, "working_directory_request": { "repository_id": TEST_REPOSITORY_ID, "local_path": dir.path().display().to_string() } })), StatusCode::UNPROCESSABLE_ENTITY, ) .await; assert!( response["message"] .as_str() .unwrap_or_default() .contains("unknown field"), "raw working directory field should be rejected: {response}" ); } #[tokio::test] async fn runtime_worker_spawn_rejects_embedded_workdir_request() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let response = request_json( app, "POST", "/api/runtimes/embedded-worker-runtime/workers", Some(serde_json::json!({ "intent": { "kind": "ticket_role", "ticket_id": "00001KVZSGT0Q", "role": "coder" }, "acceptance": { "kind": "run_accepted", "expected_segments": 0 }, "working_directory_request": { "repository_id": TEST_REPOSITORY_ID, "selector": "HEAD" } })), StatusCode::BAD_REQUEST, ) .await; assert!( response["diagnostics"] .as_array() .unwrap() .iter() .any(|diagnostic| diagnostic["code"] == "embedded_worker_workdir_unsupported"), "expected embedded workdir diagnostic, got {response}" ); let projected = serde_json::to_string(&response).unwrap(); assert!(!projected.contains(dir.path().to_string_lossy().as_ref())); } #[tokio::test] async fn browser_worker_create_rejects_extra_request_fields() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let response = request_json( app, "POST", "/api/workers", Some(serde_json::json!({ "runtime_id": "embedded-worker-runtime", "display_name": "Coding Worker", "profile": "builtin:coder", "initial_text": "", "kind": "internal" })), StatusCode::UNPROCESSABLE_ENTITY, ) .await; assert!( response["message"] .as_str() .unwrap_or_default() .contains("unknown field") ); } #[tokio::test] async fn serves_bounded_read_apis_and_static_spa_separately() { let dir = tempfile::tempdir().unwrap(); let static_dir = dir.path().join("static"); std::fs::create_dir_all(static_dir.join("assets")).unwrap(); std::fs::write(static_dir.join("index.html"), "
Yoi Workspace
").unwrap(); std::fs::write(static_dir.join("assets/app.js"), "console.log('yoi');").unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); let mut config = test_server_config(dir.path()); write_ticket( &config.database_path, TEST_WORKSPACE_ID, "API Ticket", ticket::TicketWorkflowState::Ready, ); let sqlite_store = SqliteWorkspaceStore::open(&config.database_path).unwrap(); sqlite_store .upsert_workspace(&WorkspaceRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), owner_account_id: None, display_name: "Test Workspace".to_string(), state: "active".to_string(), created_at: "2026-01-01T00:00:00Z".to_string(), updated_at: "2026-01-01T00:00:00Z".to_string(), }) .await .unwrap(); sqlite_store .upsert_objective(&ObjectiveRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), objective_id: "00000000001J3".to_string(), title: "API Objective".to_string(), state: "active".to_string(), body_md: "Objective body.\n".to_string(), created_at: "2026-01-01T00:00:00Z".to_string(), updated_at: "2026-01-02T00:00:00Z".to_string(), }) .unwrap(); sqlite_store .replace_objective_ticket_links( TEST_WORKSPACE_ID, "00000000001J3", &[ObjectiveTicketLinkRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), objective_id: "00000000001J3".to_string(), ticket_id: "00000000001J2".to_string(), kind: "linked".to_string(), created_at: "2026-01-01T00:00:00Z".to_string(), }], ) .unwrap(); sqlite_store .upsert_objective_resource(&ObjectiveResourceRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), objective_id: "00000000001J3".to_string(), resource_path: "memory-architecture-overview.md".to_string(), body: "# Memory architecture\n\nResource body.\n".to_string(), media_type: Some("text/markdown".to_string()), created_at: "2026-01-01T00:00:00Z".to_string(), updated_at: "2026-01-02T00:00:00Z".to_string(), }) .unwrap(); sqlite_store .upsert_memory_document(&MemoryDocumentRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), body_md: "# Memory\n\n## Project facts\n\n- Frontend can read this document.\n" .to_string(), created_at: "2026-01-01T00:00:00Z".to_string(), updated_at: "2026-01-02T00:00:00Z".to_string(), }) .unwrap(); sqlite_store .upsert_memory_staging_record(&MemoryStagingRecord { workspace_id: TEST_WORKSPACE_ID.to_string(), candidate_id: "00000000001J4".to_string(), raw_json: memory_staging_record_json("00000000001J4", "SQLite memory candidate"), source_path: None, imported_at: "2026-01-01T00:00:00Z".to_string(), }) .unwrap(); assert_eq!( sqlite_store .count_memory_staging_records(TEST_WORKSPACE_ID) .unwrap(), 1 ); write_objective( dir.path(), "00000000001J5", "Filesystem Only Objective", "active", ); config.static_assets_dir = Some(static_dir); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app = build_router(api); let workspace = get_json(app.clone(), "/api/workspace").await; assert_eq!(workspace["workspace_id"], TEST_WORKSPACE_ID); assert_eq!(workspace["display_name"], "Test Workspace"); assert_eq!(workspace["record_authority"], "local_yoi_project_records"); assert_eq!( workspace["extension_points"]["host_worker_bridge"]["status"], "runtime_registry" ); let workspace_companion = &workspace["extension_points"]["companion_console"]; assert_ne!(workspace_companion["status"], "not_connected"); assert!( !workspace_companion["note"] .as_str() .unwrap() .contains("browser input remains disabled"), "stale Companion Console note returned: {workspace_companion}" ); if workspace_companion["status"] == "not_input_capable" { assert!( !workspace_companion["diagnostics"] .as_array() .unwrap() .is_empty(), "not_input_capable workspace companion_console lacks typed diagnostics: {workspace_companion}" ); } let scoped_workspace = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/workspace"), ) .await; assert_eq!(scoped_workspace["workspace_id"], TEST_WORKSPACE_ID); let mismatched_workspace = request_json( app.clone(), "GET", "/api/w/not-this-workspace/workspace", None, StatusCode::NOT_FOUND, ) .await; assert_eq!( mismatched_workspace["diagnostics"][0]["code"], "workspace_id_mismatch" ); assert!( !mismatched_workspace .to_string() .contains(dir.path().to_string_lossy().as_ref()) ); let unscoped_objectives = app .clone() .oneshot( Request::builder() .uri("/objectives?focus=active") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(unscoped_objectives.status(), StatusCode::TEMPORARY_REDIRECT); let expected_location = format!("/w/{TEST_WORKSPACE_ID}/objectives?focus=active"); assert_eq!( unscoped_objectives .headers() .get(LOCATION) .and_then(|value| value.to_str().ok()), Some(expected_location.as_str()) ); let tickets = get_json(app.clone(), "/api/tickets").await; assert_eq!(tickets["items"][0]["title"], "API Ticket"); assert_eq!(tickets["items"][0]["state"], "ready"); let objectives = get_json(app.clone(), "/api/objectives").await; assert_eq!(objectives["items"].as_array().unwrap().len(), 1); assert_eq!(objectives["items"][0]["id"], "00000000001J3"); assert_eq!(objectives["items"][0]["summary"], "Objective body."); assert_eq!(objectives["record_authority"], "workspace-sqlite"); let scoped_objectives = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/objectives"), ) .await; assert_eq!(scoped_objectives["items"][0]["id"], "00000000001J3"); let limited_objectives = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/objectives?limit=0"), ) .await; assert_eq!(limited_objectives["items"].as_array().unwrap().len(), 0); let scoped_objective = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/objectives/00000000001J3"), ) .await; assert_eq!(scoped_objective["id"], "00000000001J3"); assert_eq!(scoped_objective["record_source"], "workspace-sqlite"); assert_eq!( scoped_objective["resources"][0]["path"], "memory-architecture-overview.md" ); let memory_document = get_json(app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/memory")).await; assert_eq!(memory_document["created_at"], "2026-01-01T00:00:00Z"); assert_eq!(memory_document["updated_at"], "2026-01-02T00:00:00Z"); assert_eq!(memory_document["bytes"], 63); assert_eq!(memory_document["record_source"], "workspace-sqlite"); assert!( memory_document["body_md"] .as_str() .unwrap() .contains("Frontend can read this document.") ); let memory_staging = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/memory/staging?limit=10"), ) .await; assert_eq!( memory_staging["record_authority"], "sqlite_workspace_authority.memory_staging" ); assert_eq!(memory_staging["total_valid_count"], 1); assert_eq!(memory_staging["invalid_count"], 0); assert_eq!( memory_staging["items"][0]["record"]["claim"], "SQLite memory candidate" ); let repositories = get_json(app.clone(), "/api/repositories").await; assert_eq!(repositories["items"][0]["id"], TEST_REPOSITORY_ID); assert_eq!(repositories["items"][0]["kind"], "git"); assert_eq!( repositories["items"][0]["record_authority"], "workspace-control-plane" ); assert!( repositories .to_string() .contains("repository_git_unavailable") ); let repository_detail = get_json(app.clone(), "/api/repositories/main").await; assert_eq!(repository_detail["item"]["id"], TEST_REPOSITORY_ID); let scoped_repository_detail = get_json( app.clone(), &format!("/api/w/{TEST_WORKSPACE_ID}/repositories/main"), ) .await; assert_eq!(scoped_repository_detail["item"]["id"], TEST_REPOSITORY_ID); let repository_log = get_json(app.clone(), "/api/repositories/main/log?limit=3").await; assert_eq!(repository_log["repository_id"], TEST_REPOSITORY_ID); assert_eq!(repository_log["default_selector"], "HEAD"); assert_eq!(repository_log["limit"], 3); let repository_tickets = get_json(app.clone(), "/api/repositories/main/tickets").await; assert_eq!(repository_tickets["repository_id"], TEST_REPOSITORY_ID); let ready_column = repository_tickets["columns"] .as_array() .unwrap() .iter() .find(|column| column["state"] == "ready") .unwrap(); assert_eq!(ready_column["items"][0]["title"], "API Ticket"); assert_eq!( repository_tickets["diagnostics"][0]["code"], "repository_ticket_target_metadata_absent" ); let unknown_repository_response = app .clone() .oneshot( Request::builder() .uri("/api/repositories/nope") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(unknown_repository_response.status(), StatusCode::NOT_FOUND); let hosts = get_json(app.clone(), "/api/hosts").await; assert_eq!(hosts["source"], "worker_runtime_registry"); assert_eq!(hosts["items"][0]["runtime_id"], "embedded-worker-runtime"); let host_id = hosts["items"][0]["host_id"].as_str().unwrap().to_string(); assert_eq!(hosts["items"][0]["kind"], "embedded-worker-runtime-host"); assert_eq!( hosts["items"][0]["capabilities"]["workspace_scope"], "backend_internal" ); assert!(!hosts.to_string().contains("metadata.json")); let runtimes = get_json(app.clone(), "/api/runtimes").await; assert_eq!(runtimes["source"], "worker_runtime_registry"); assert_eq!( runtimes["items"][0]["runtime_id"], "embedded-worker-runtime" ); assert_eq!( runtimes["items"][0]["source"]["kind"], "embedded_worker_runtime" ); 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); let workers = get_json(app.clone(), "/api/workers").await; let worker_items = workers["items"].as_array().unwrap(); assert!( worker_items .iter() .all(|worker| worker["role"] != "builtin:companion"), "companion auto-start should not create runtime workers: {workers}" ); let companion_status = get_json(app.clone(), "/api/companion/status").await; assert_eq!(companion_status["state"], "disabled"); assert!(companion_status["worker"].is_null()); assert_eq!(companion_status["transport"]["kind"], "none"); assert_eq!(companion_status["transport"]["completion"], "disabled"); assert!(!companion_status.to_string().contains("/workspace/demo")); let companion_message = post_json( app.clone(), "/api/companion/messages", json!({ "content": "hello companion" }), ) .await; assert_eq!(companion_message["state"], "rejected"); assert_eq!( companion_message["diagnostics"][0]["code"], "companion_disabled" ); assert!(companion_message["user_item"].is_null()); assert!(companion_message["assistant_item"].is_null()); assert!(!companion_message.to_string().contains("/workspace/demo")); let companion_transcript = get_json(app.clone(), "/api/companion/transcript").await; assert_eq!(companion_transcript["total_items"], 0); let host_workers = get_json(app.clone(), &format!("/api/hosts/{host_id}/workers")).await; assert!( host_workers["items"] .as_array() .unwrap() .iter() .all(|worker| worker["role"] != "builtin:companion") ); let runs_response = app .clone() .oneshot( Request::builder() .uri("/api/runs") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(runs_response.status(), StatusCode::NOT_FOUND); let runners_response = app .clone() .oneshot( Request::builder() .uri("/api/runners") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(runners_response.status(), StatusCode::NOT_FOUND); let static_response = app .clone() .oneshot( Request::builder() .uri("/assets/app.js") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(static_response.status(), StatusCode::OK); assert_eq!( static_response.headers().get(CONTENT_TYPE).unwrap(), "text/javascript; charset=utf-8" ); let spa_response = app .clone() .oneshot( Request::builder() .uri("/tickets/00000000001J2") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(spa_response.status(), StatusCode::OK); let bytes = to_bytes(spa_response.into_body(), usize::MAX) .await .unwrap(); assert!( String::from_utf8(bytes.to_vec()) .unwrap() .contains("Yoi Workspace") ); let api_miss = app .oneshot( Request::builder() .uri("/api/nope") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(api_miss.status(), StatusCode::NOT_FOUND); let bytes = to_bytes(api_miss.into_body(), usize::MAX).await.unwrap(); assert!( !String::from_utf8(bytes.to_vec()) .unwrap() .contains("Yoi Workspace") ); } #[tokio::test] async fn companion_routes_report_disabled_without_spawning_worker() { let temp = tempfile::tempdir().unwrap(); let config = test_server_config(temp.path().join("workspace")); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app = build_router(api); let workspace = get_json(app.clone(), "/api/workspace").await; let workspace_companion = &workspace["extension_points"]["companion_console"]; assert_eq!(workspace_companion["status"], "disabled"); assert_eq!( workspace_companion["diagnostics"][0]["code"], "companion_disabled" ); assert!( workspace_companion["note"] .as_str() .unwrap() .contains("auto-start has been removed") ); let status = get_json(app.clone(), "/api/companion/status").await; assert_eq!(status["state"], "disabled"); assert_eq!(status["transport"]["completion"], "disabled"); assert!(status["worker"].is_null()); let response = post_json( app.clone(), "/api/companion/messages", serde_json::json!({ "content": "from legacy route" }), ) .await; assert_eq!(response["state"], "rejected"); assert_eq!(response["diagnostics"][0]["code"], "companion_disabled"); assert!(response["user_item"].is_null()); assert!(response["assistant_item"].is_null()); let transcript = get_json(app.clone(), "/api/companion/transcript").await; assert_eq!(transcript["state"], "disabled"); assert_eq!(transcript["total_items"], 0); let workers = get_json(app, "/api/workers").await; assert!( workers["items"] .as_array() .unwrap() .iter() .all(|worker| worker["role"] != "builtin:companion"), "disabled companion route should not spawn workers: {workers}" ); } #[tokio::test] async fn embedded_runtime_fs_store_restores_catalog_config_bundle_and_corrupts_failed_execution() { let dir = tempfile::tempdir().unwrap(); let config = test_server_config(dir.path().join("workspace")); let store_root = config.embedded_runtime_store_root.clone(); let bundle = runtime_test_bundle(); let bundle_id = bundle.metadata.id.clone(); let api = WorkspaceApi::new_with_execution_backend( config.clone(), Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .expect("fs-backed api starts"); let synced = api .runtime .sync_config_bundle("embedded-worker-runtime", bundle) .expect("sync config bundle"); assert_eq!(synced.state, WorkerOperationState::Accepted); assert!(store_root.exists(), "fs-store root should be created"); let spawned = api .runtime .spawn_worker( "embedded-worker-runtime", WorkerSpawnRequest { intent: WorkerSpawnIntent::TicketRole { ticket_id: "00001KVZSGT0Q".to_string(), role: TicketWorkerRole::Coder, }, requested_worker_name: None, acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments: 0, }, profile: None, initial_input: None, working_directory_request: None, resolved_working_directory_request: None, resolved_working_directory: None, resolved_config_bundle: None, }, ) .expect("spawn worker"); assert_eq!(spawned.state, WorkerOperationState::Accepted); let worker_id = spawned.worker.expect("created worker").worker_id; let sent = api .runtime .send_input( "embedded-worker-runtime", &worker_id, WorkerInputRequest { kind: WorkerInputKind::User, content: "persist me".to_string(), segments: None, }, ) .expect("send input"); assert_eq!(sent.state, WorkerOperationState::Accepted); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); loop { let detail = api .runtime .worker("embedded-worker-runtime", &worker_id) .expect("worker detail"); if detail.state == "idle" { break; } assert!( std::time::Instant::now() < deadline, "timed out waiting for deterministic worker completion" ); tokio::time::sleep(std::time::Duration::from_millis(10)).await; } drop(api); let restored = WorkspaceApi::new_with_execution_backend( config, Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .expect("restored fs-backed api starts"); let restored_worker = restored .runtime .worker("embedded-worker-runtime", &worker_id) .expect("restored worker"); assert_eq!(restored_worker.state, "corrupted"); assert!(!restored_worker.capabilities.can_stop); assert!( restored_worker .diagnostics .iter() .any(|diagnostic| diagnostic.code == "embedded_worker_execution_corrupted") ); let bundles = restored .runtime .list_config_bundles("embedded-worker-runtime") .expect("config bundle list"); assert!( bundles .bundles .iter() .any(|summary| summary.id == bundle_id) ); let rejected_input = restored .runtime .send_input( "embedded-worker-runtime", &worker_id, WorkerInputRequest { kind: WorkerInputKind::User, content: "should not be routed to corrupted handle".to_string(), segments: None, }, ) .expect("stale worker input is projected as an operation result"); assert_eq!(rejected_input.state, WorkerOperationState::Rejected); } #[tokio::test] async fn embedded_runtime_store_root_is_isolated_and_not_exposed_by_browser_api() { let dir = tempfile::tempdir().unwrap(); let data_dir = dir.path().join("user-data"); let workspace_root = dir.path().join("workspace"); let default_root = ServerConfig::embedded_runtime_store_root_for_data_dir(&data_dir, TEST_WORKSPACE_ID); assert_eq!( default_root, data_dir .join("server") .join("workspaces") .join(TEST_WORKSPACE_ID) .join("embedded-runtime") ); assert!(!default_root.starts_with(workspace_root.join(".yoi"))); let config = ServerConfig::local_dev(workspace_root, test_identity()) .with_embedded_runtime_store_root(default_root.clone()); let app = build_router( WorkspaceApi::new_with_execution_backend( config, Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(), ); let raw_store_root = default_root.to_string_lossy().to_string(); for uri in [ "/api/workspace", "/api/hosts", "/api/runtimes", "/api/workers", ] { let body = get_json(app.clone(), uri).await; let serialized = serde_json::to_string(&body).unwrap(); assert!( !serialized.contains(&raw_store_root), "{uri} leaked embedded runtime store root: {serialized}" ); } } #[tokio::test] async fn empty_repository_config_returns_empty_list_with_warning() { let root = tempfile::tempdir().unwrap(); let mut config = test_server_config(root.path()); config.repositories.clear(); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app = build_router(api); let repositories = get_json(app, "/api/repositories").await; assert!(repositories["items"].as_array().unwrap().is_empty()); assert_eq!( repositories["diagnostics"][0]["code"], "repository_config_empty" ); } #[tokio::test] async fn repository_log_rejects_unknown_or_unsupported_configured_repository() { let root = tempfile::tempdir().unwrap(); let mut config = test_server_config(root.path()); config.repositories = vec![ConfiguredRepository { id: "files".to_string(), provider: "local_fs".to_string(), uri: ".".to_string(), path: root.path().to_path_buf(), display_name: None, default_selector: None, }]; let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(SqliteWorkspaceStore::in_memory().unwrap()), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app = build_router(api); let unknown = request_json( app.clone(), "GET", "/api/repositories/main/log", None, StatusCode::NOT_FOUND, ) .await; assert_eq!( unknown["diagnostics"][0]["code"], "repository_not_configured" ); let unsupported = request_json( app, "GET", "/api/repositories/files/log", None, StatusCode::BAD_REQUEST, ) .await; assert_eq!( unsupported["diagnostics"][0]["code"], "repository_provider_unsupported" ); } #[tokio::test] async fn embedded_runtime_api_routes_by_runtime_and_worker_ids_without_leaking_internals() { let dir = tempfile::tempdir().unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); let config = test_server_config(dir.path()); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app = build_router(api); let runtimes = get_json(app.clone(), "/api/runtimes").await; let embedded_summary = runtimes["items"] .as_array() .unwrap() .iter() .find(|runtime| runtime["runtime_id"] == "embedded-worker-runtime") .expect("embedded runtime summary"); assert_eq!( embedded_summary["source"]["kind"], "embedded_worker_runtime" ); assert_eq!(embedded_summary["source"]["status"], "active"); assert_eq!( embedded_summary["capabilities"]["workspace_scope"], "backend_internal" ); assert_eq!(embedded_summary["capabilities"]["has_workspace_fs"], false); let spawned = post_json( app.clone(), "/api/runtimes/embedded-worker-runtime/workers", json!({ "intent": { "kind": "ticket_role", "ticket_id": "00001KVZSGT0Q", "role": "coder" }, "requested_worker_name": "api-friendly-name", "acceptance": { "kind": "run_accepted", "expected_segments": 0 } }), ) .await; assert_eq!(spawned["state"], "accepted"); let diagnostics = spawned["diagnostics"].as_array().unwrap(); assert!(diagnostics.iter().all(|diagnostic| { !diagnostic["message"] .as_str() .unwrap_or_default() .contains("/workspace/demo") })); let worker_id = spawned["worker"]["worker_id"].as_str().unwrap().to_string(); assert_eq!(spawned["worker"]["runtime_id"], "embedded-worker-runtime"); assert_eq!( spawned["worker"]["workspace"]["visibility"], "backend_internal" ); assert_eq!( spawned["worker"]["implementation"]["kind"], "embedded_worker_runtime" ); let worker = get_json( app.clone(), &format!("/api/runtimes/embedded-worker-runtime/workers/{worker_id}"), ) .await; assert_eq!(worker["worker_id"], worker_id); assert_eq!(worker["runtime_id"], "embedded-worker-runtime"); let accepted = post_json( app.clone(), &format!("/api/runtimes/embedded-worker-runtime/workers/{worker_id}/input"), json!({ "kind": "user", "content": "hello from browser-facing api" }), ) .await; assert_eq!(accepted["state"], "accepted"); assert_eq!(accepted["runtime_id"], "embedded-worker-runtime"); assert_eq!(accepted["worker_id"], worker_id); assert!(accepted["diagnostics"].as_array().unwrap().is_empty()); let transcript_route = app .clone() .oneshot( Request::builder() .method("GET") .uri(format!( "/api/runtimes/embedded-worker-runtime/workers/{worker_id}/transcript?start=0&limit=10" )) .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(transcript_route.status(), StatusCode::NOT_FOUND); let wrong_runtime = app .clone() .oneshot( Request::builder() .method("POST") .uri(format!( "/api/runtimes/unknown-runtime/workers/{worker_id}/input" )) .header(CONTENT_TYPE, "application/json") .body(Body::from( serde_json::to_vec(&json!({ "kind": "user", "content": "wrong runtime" })) .unwrap(), )) .unwrap(), ) .await .unwrap(); assert_eq!(wrong_runtime.status(), StatusCode::NOT_FOUND); let projected = format!("{}{}{}{}", embedded_summary, spawned, worker, accepted); for forbidden in [ dir.path().to_string_lossy().as_ref(), "metadata.json", "socket", "session", "token", "credential", "provider", ] { assert!( !projected.contains(forbidden), "embedded api projection leaked forbidden term: {forbidden}: {projected}" ); } } #[tokio::test] async fn proxies_worker_protocol_ws_as_raw_events() { let (runtime, worker_ref, endpoint) = spawn_runtime_worker().await; let source = RuntimeObservationSourceConfig { runtime_id: "runtime-a".into(), worker_id: "worker-a".into(), endpoint, bearer_token: None, }; let (url, _dir) = spawn_workspace_proxy(source).await; let (mut stream, _) = connect_async(&url).await.unwrap(); assert!(matches!( next_client_frame(&mut stream).await, protocol::Event::Snapshot { .. } )); stream .send(Message::Text( serde_json::to_string(&protocol::Method::ListCompletions { kind: protocol::CompletionKind::File, prefix: String::new(), }) .unwrap() .into(), )) .await .unwrap(); assert!(matches!( next_client_frame(&mut stream).await, protocol::Event::Completions { .. } )); runtime .observe_worker_event( &worker_ref, protocol::Event::TextDelta { text: "live".into(), }, ) .unwrap(); assert!(matches!( next_client_frame(&mut stream).await, protocol::Event::TextDelta { .. } )); } #[tokio::test] async fn proxy_maps_runtime_worker_not_found_http_404_to_protocol_error_event() { let (_runtime, _worker_ref, endpoint) = spawn_runtime_worker().await; let endpoint = endpoint.replace("/protocol/ws", "/missing-worker/protocol/ws"); let source = RuntimeObservationSourceConfig { runtime_id: "runtime-a".into(), worker_id: "worker-a".into(), endpoint, bearer_token: None, }; let (url, _dir) = spawn_workspace_proxy(source).await; let (mut stream, _) = connect_async(&url).await.unwrap(); assert!(matches!( next_client_frame(&mut stream).await, protocol::Event::Error { .. } )); } async fn next_client_frame( stream: &mut tokio_tungstenite::WebSocketStream< tokio_tungstenite::MaybeTlsStream, >, ) -> protocol::Event { let message = stream.next().await.unwrap().unwrap(); let Message::Text(text) = message else { panic!("expected text frame"); }; serde_json::from_str(&text).unwrap() } async fn spawn_runtime_worker() -> ( worker_runtime::Runtime, worker_runtime::identity::WorkerRef, String, ) { let (runtime, worker_ref) = runtime_with_worker(); let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let runtime_addr = runtime_listener.local_addr().unwrap(); tokio::spawn({ let runtime = runtime.clone(); async move { worker_runtime::http_server::serve_runtime_http(runtime, runtime_listener, None) .await .unwrap() } }); let endpoint = format!( "ws://{runtime_addr}/v1/workers/{}/protocol/ws", worker_ref.worker_id ); (runtime, worker_ref, endpoint) } async fn spawn_workspace_proxy( source: RuntimeObservationSourceConfig, ) -> (String, tempfile::TempDir) { let dir = tempfile::tempdir().unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); let mut config = test_server_config(dir.path()); let runtime_id = source.runtime_id.clone(); let worker_id = source.worker_id.clone(); config.runtime_event_sources.push(source); let api = WorkspaceApi::new_with_execution_backend( config, Arc::new(store), Arc::new(DeterministicExecutionBackend::default()), ) .await .unwrap(); let app_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let app_addr = app_listener.local_addr().unwrap(); tokio::spawn(async move { axum::serve(app_listener, build_router(api)).await.unwrap() }); ( format!("ws://{app_addr}/api/runtimes/{runtime_id}/workers/{worker_id}/protocol/ws"), dir, ) } #[tokio::test] async fn passkey_registration_rejects_unverified_credential_response() { let dir = tempfile::tempdir().unwrap(); let app = test_app(dir.path()).await; let registration_options = post_json( app.clone(), "/api/auth/passkeys/registration/options", json!({ "handle": "alice", "display_name": "Alice" }), ) .await; assert!(registration_options["public_key"].is_object()); let challenge_id = registration_options["challenge_id"].as_str().unwrap(); let response = app .clone() .oneshot( Request::builder() .method("POST") .uri("/api/auth/passkeys/registration/complete") .header(CONTENT_TYPE, "application/json") .body(Body::from( serde_json::to_vec(&json!({ "challenge_id": challenge_id, "credential": { "id": "credential-1", "rawId": "Y3JlZGVudGlhbC0x", "type": "public-key", "response": { "clientDataJSON": "e30", "attestationObject": "e30" } } })) .unwrap(), )) .unwrap(), ) .await .unwrap(); assert_ne!(response.status(), StatusCode::OK); let login_without_registered_passkey = app .oneshot( Request::builder() .method("POST") .uri("/api/auth/passkeys/login/options") .header(CONTENT_TYPE, "application/json") .body(Body::from( serde_json::to_vec(&json!({ "handle": "alice" })).unwrap(), )) .unwrap(), ) .await .unwrap(); assert_ne!(login_without_registered_passkey.status(), StatusCode::OK); } async fn get_json(app: Router, uri: &str) -> Value { let response = app .oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap()) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK, "{uri}"); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); serde_json::from_slice(&bytes).unwrap() } async fn request_json( app: Router, method: &str, uri: &str, body: Option, expected_status: StatusCode, ) -> Value { let mut builder = Request::builder().method(method).uri(uri); let request_body = if let Some(body) = body { builder = builder.header(CONTENT_TYPE, "application/json"); Body::from(serde_json::to_vec(&body).unwrap()) } else { Body::empty() }; let response = app .oneshot(builder.body(request_body).unwrap()) .await .unwrap(); assert_eq!(response.status(), expected_status, "{method} {uri}"); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); serde_json::from_slice(&bytes).unwrap_or_else( |_| serde_json::json!({ "message": String::from_utf8_lossy(&bytes).to_string() }), ) } async fn post_json(app: Router, uri: &str, body: Value) -> Value { let response = app .oneshot( Request::builder() .method("POST") .uri(uri) .header(CONTENT_TYPE, "application/json") .body(Body::from(serde_json::to_vec(&body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK, "{uri}"); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); serde_json::from_slice(&bytes).unwrap() } fn write_ticket( database_path: &Path, workspace_id: &str, title: &str, state: ticket::TicketWorkflowState, ) { use ticket::TicketBackend as _; let backend = ticket::SqliteTicketBackend::new(database_path, workspace_id); let mut input = ticket::NewTicket::new(title); input.workflow_state = Some(state); backend.create(input).unwrap(); } fn write_objective(root: &Path, id: &str, title: &str, state: &str) { let objective_dir = root.join(".yoi/objectives").join(id); std::fs::create_dir_all(&objective_dir).unwrap(); std::fs::write( objective_dir.join("item.md"), format!( r#"--- title: "{title}" state: "{state}" created_at: "2026-01-01T00:00:00Z" updated_at: "2026-01-02T00:00:00Z" linked_tickets: ["00000000001J2"] --- Objective body. "#, ), ) .unwrap(); std::fs::write( objective_dir.join("memory-architecture-overview.md"), "# Memory architecture\n\nResource body.\n", ) .unwrap(); } }